diff --git a/EIPS/eip-1046.md b/EIPS/eip-1046.md index 82907d56cbeeff..0c21e0e0c6f2cd 100644 --- a/EIPS/eip-1046.md +++ b/EIPS/eip-1046.md @@ -1,222 +1 @@ ---- -eip: 1046 -title: tokenURI Interoperability -description: Extends ERC-20 with an ERC-721-like tokenURI, and extends ERC-721 and ERC-1155 with interoperability -author: Tommy Nicholas (@tomasienrbc), Matt Russo (@mateosu), John Zettler (@JohnZettler), Matt Condon (@shrugs), Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/eip-1046-erc-20-metadata-extension/13036 -status: Final -type: Standards Track -category: ERC -created: 2018-04-13 -requires: 20, 721, 1155 ---- - -## Abstract - -[ERC-721](./eip-721.md) introduced a `tokenURI` function for non-fungible tokens to handle miscellaneous metadata such as: - -- thumbnail image -- title -- description -- special asset properties -- etc. - -This ERC adds a `tokenURI` function to [ERC-20](./eip-20.md), and extends [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) to enable interoperability between all three types of token URI. - -## Motivation - -See the note about the metadata extension in [ERC-721](./eip-721.md#rationale). The same arguments apply to ERC-20. - -Being able to use similar mechanisms to extract metadata for ERC-20, ERC-721, ERC-1155, and future standards is useful for determining: - -- What type of token a contract is (if any); -- How to display a token to a user, either in an asset listing page or on a dedicated token page; and -- Determining the capabilities of the token - -## 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. - -### Interoperability Metadata - -The following TypeScript interface is used in later sections: - -```typescript -/** - * Interoperability metadata. - * This can be extended by other proposals. - * - * All fields MUST be optional. - * **Not every field has to be a boolean.** Any optional JSON-serializable object can be used by extensions. - */ -interface InteroperabilityMetadata { - /** - * This MUST be true if this is ERC-1046 Token Metadata, otherwise, this MUST be omitted. - * Setting this to true indicates to wallets that the address should be treated as an ERC-20 token. - **/ - erc1046?: boolean | undefined; - - /** - * This MUST be true if this is ERC-721 Token Metadata, otherwise, this MUST be omitted. - * Setting this to true indicates to wallets that the address should be treated as a ERC-721 token. - **/ - erc721?: boolean | undefined; - - /** - * This MUST be true if this is ERC-1155 Token Metadata, otherwise, this MUST be omitted. - * Setting this to true indicates to wallets that the address should be treated as a ERC-1155 token. - **/ - erc1155?: boolean | undefined; -} -``` - -### ERC-20 Extension - -#### ERC-20 Interface Extension - -Compliant contracts MUST implement the following Solidity interface: - -```solidity -pragma solidity ^0.8.0; - -/// @title ERC-20 Metadata Extension -interface ERC20TokenMetadata /* is ERC20 */ { - /// @notice Gets an ERC-721-like token URI - /// @dev The resolved data MUST be in JSON format and support ERC-1046's ERC-20 Token Metadata Schema - function tokenURI() external view returns (string); -} -``` - -#### ERC-20 Token Metadata Schema - -The resolved JSON of the `tokenURI` described in the ERC-20 Interface Extension section MUST conform to the following TypeScript interface: - -```typescript -/** - * Asset Metadata - */ -interface ERC20TokenMetadata { - /** - * Interoperabiliy, to differentiate between different types of tokens and their corresponding URIs. - **/ - interop: InteroperabilityMetadata; - - /** - * The name of the ERC-20 token. - * If the `name()` function is present in the ERC-20 token and returns a nonempty string, these MUST be the same value. - */ - name?: string; - - /** - * The symbol of the ERC-20 token. - * If the `symbol()` function is present in the ERC-20 token and returns a nonempty string, these MUST be the same value. - */ - symbol?: string; - - /** - * The decimals of the ERC-20 token. - * If the `decimals()` function is present in the ERC-20 token, these MUST be the same value. - * Defaults to 18 if neither this parameter nor the ERC-20 `decimals()` function are present. - */ - decimals?: number; - - /** - * Provides a short one-paragraph description of the ERC-20 token, without any markup or newlines. - */ - description?: string; - - /** - * A URI pointing to a resource with mime type `image/*` that represents this token. - * If the image is a bitmap, it SHOULD have a width between 320 and 1080 pixels - * The image SHOULD have an aspect ratio between 1.91:1 and 4:5 inclusive. - */ - image?: string; - - /** - * One or more URIs each pointing to a resource with mime type `image/*` that represents this token. - * If an image is a bitmap, it SHOULD have a width between 320 and 1080 pixels - * Images SHOULD have an aspect ratio between 1.91:1 and 4:5 inclusive. - */ - images?: string[]; - - /** - * One or more URIs each pointing to a resource with mime type `image/*` that represent an icon for this token. - * If an image is a bitmap, it SHOULD have a width between 320 and 1080 pixels, and MUST have a height equal to its width - * Images MUST have an aspect ratio of 1:1, and use a transparent background - */ - icons?: string[]; -} -``` - -### ERC-721 Extension - -#### Extension to the ERC-721 Metadata Schema - -Contracts that implement ERC-721 and use its token metadata URI SHOULD to use the following TypeScript extension to the metadata URI: - -```typescript -interface ERC721TokenMetadataInterop extends ERC721TokenMetadata { - /** - * Interoperabiliy, to avoid confusion between different token URIs - **/ - interop: InteroperabilityMetadata; -} -``` - -### ERC-1155 Extension - -#### ERC-1155 Interface Extension - -[ERC-1155](./eip-1155.md)-compliant contracts using the metadata extension SHOULD implement the following Solidity interface: - -```solidity -pragma solidity ^0.8.0; - -/// @title ERC-1155 Metadata URI Interoperability Extension -interface ERC1155TokenMetadataInterop /* is ERC1155 */ { - /// @notice Gets an ERC-1046-compliant ERC-1155 token URI - /// @param tokenId The token ID to get the URI of - /// @dev The resolved data MUST be in JSON format and support ERC-1046's Extension to the ERC-1155 Token Metadata Schema - /// This MUST be the same URI as the `uri(tokenId)` function, if present. - function tokenURI(uint256 tokenId) external view returns (string); -} -``` - -#### Extension to the ERC-1155 Metadata Schema - -Contracts that implement ERC-1155 and use its token metadata URI are RECOMMENDED to use the following extension to the metadata URI. Contracts that implement the interface described in the ERC-1155 Interface Extension section MUST use the following TypeScript extension: - -```typescript -interface ERC1155TokenMetadataInterop extends ERC1155TokenMetadata { - /** - * Interoperabiliy, to avoid confusion between different token URIs - **/ - interop: InteroperabilityMetadata; -} -``` - -### Miscellaneous Recommendations - -To save gas, it is RECOMMENDED for compliant contracts not to implement the `name()`, `symbol()`, or `decimals()` functions, and instead to only include them in the metadata URI. Additionally, for ERC-20 tokens, if the decimals is `18`, then it is NOT RECOMMENDED to include the `decimals` field in the metadata. - -## Rationale - -This ERC makes adding metadata to ERC-20 tokens more straightforward for developers, with minimal to no disruption to the overall ecosystem. Using the same parameter name makes it easier to reuse code. - -Additionally, the recommendations not to use ERC-20's `name`, `symbol`, and `decimals` functions save gas. - -Built-in interoperability is useful as otherwise it might not be easy to differentiate the type of the token. Interoperability could be done using [ERC-165](./eip-165.md), but static calls are time-inefficient for wallets and websites, and is generally inflexible. Instead, including interoperability data in the token URI increases flexibility while also giving a performance increase. - -## Backwards Compatibility - -This EIP is fully backwards compatible as its implementation simply extends the functionality of ERC-20 tokens and is optional. Additionally, it makes backward compatible recommendations for ERC-721 and ERC-1155 tokens. - -## Security Considerations - -### Server-Side Request Forgery (SSRF) - -Wallets should be careful about making arbitrary requests to URLs. As such, it is recommended for wallets to sanitize the URI by whitelisting specific schemes and ports. A vulnerable wallet could be tricked into, for example, modifying data on a locally-hosted redis database. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1046.md diff --git a/EIPS/eip-1056.md b/EIPS/eip-1056.md index cb468d8eb173b4..2084821bd62191 100644 --- a/EIPS/eip-1056.md +++ b/EIPS/eip-1056.md @@ -1,286 +1 @@ ---- -eip: 1056 -title: Ethereum Lightweight Identity -author: Pelle Braendgaard , Joel Torstensson -type: Standards Track -category: ERC -discussions-to: https://github.com/ethereum/EIPs/issues/1056 -status: Stagnant -created: 2018-05-03 ---- - -## Simple Summary - -A registry for key and attribute management of lightweight blockchain identities. - -## Abstract - -This ERC describes a standard for creating and updating identities with a limited use of blockchain resources. An identity can have an unlimited number of `delegates` and `attributes` associated with it. Identity creation is as simple as creating a regular key pair ethereum account, which means that it's free (no gas costs) and all ethereum accounts are valid identities. Furthermore this ERC is fully [DID compliant](https://w3c-ccg.github.io/did-spec/). - -## Motivation - -As we have been developing identity systems for the last couple of years at uPort it has become apparent that the cost of identity creation is a large issue. The previous Identity proposal [ERC-725](./eip-725.md) faces this exact issue. Our requirements when creating this ERC is that identity creation should be free, and should be possible to do in an offline environment (e.g. refugee scenario). However it must also be possible to rotate keys without changing the primary identifier of the identity. The identity system should be fit to use off-chain as well as on-chain. - -## Definitions - -* `Identifier`: a piece of data that uniquely identifies the identity, an ethereum address - -* `delegate`: an address that is delegated for a specific time to perform some sort of function on behalf of an identity - -* `delegateType`: the type of a delegate, is determined by a protocol or application higher up - Examples: - - * `did-jwt` - * `raiden` - -* `attribute`: a piece of data associated with the identity - -## Specification - -This ERC specifies a contract called `EthereumDIDRegistry` that is deployed once and can then be commonly used by everyone. - -### Identity ownership - -By default an identity is owned by itself, meaning whoever controls the ethereum account with that address. The owner can be updated to a new key pair account or to a multisig account etc. - -#### identityOwner - -Returns the owner of the given identity. - -```js -function identityOwner(address identity) public view returns(address); -``` - -#### changeOwner - -Sets the owner of the given identity to another ethereum account. - -```js -function changeOwner(address identity, address newOwner) public; -``` - -#### changeOwnerSigned - -Same as above but with raw signature. - - -```js -function changeOwnerSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, address newOwner) public; -``` - -### Delegate management - -Delegates can be used both on- and off-chain. They all have a `delegateType` which can be used to specify the purpose of the delegate. - -#### validDelegate - -Returns true if the given `delegate` is a delegate with type `delegateType` of `identity`. - -```js -function validDelegate(address identity, bytes32 delegateType, address delegate) public view returns(bool); -``` - -#### addDelegate - -Adds a new delegate with the given type. `validity` indicates the number of seconds that the delegate will be valid for, after which it will no longer be a delegate of `identity`. - -```js -function addDelegate(address identity, bytes32 delegateType, address delegate, uint validity) public; -``` - - -#### addDelegateSigned - -Same as above but with raw signature. - - -```js -function addDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate, uint validity) public; -``` - - -#### revokeDelegate - -Revokes the given `delegate` for the given `identity`. - - -```js -function revokeDelegate(address identity, bytes32 delegateType, address delegate) public; -``` - - -#### revokeDelegateSigned - -Same as above but with raw signature. - - -```js -function revokeDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate) public; -``` - - -### Attribute management - -Attributes contain simple data about the identity. They can be managed only by the owner of the identity. - - -#### setAttribute - -Sets an attribute with the given `name` and `value`, valid for `validity` seconds. - - -```js -function setAttribute(address identity, bytes32 name, bytes value, uint validity) public; -``` - - -#### setAttributeSigned - -Same as above but with raw signature. - - -```js -function setAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes value, uint validity) public; -``` - - -#### revokeAttrubte - -Revokes an attribute. - - -```js -function revokeAttribute(address identity, bytes32 name, bytes value) public; -``` - - -#### revokeAttributeSigned - -Same as above but with raw signature. - - -```js -function revokeAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes value) public; -``` - - -### Events - -#### DIDOwnerChanged - -MUST be triggered when `changeOwner` or `changeOwnerSigned` was successfully called. - - -```js -event DIDOwnerChanged( - address indexed identity, - address owner, - uint previousChange -); -``` - - -#### DIDDelegateChanged - -MUST be triggered when a change to a delegate was successfully made. - - -```js -event DIDDelegateChanged( - address indexed identity, - bytes32 delegateType, - address delegate, - uint validTo, - uint previousChange -); -``` - - -#### DIDAttritueChanged - -MUST be triggered when a change to an attribute was successfully made. - - -```js -event DIDAttributeChanged( - address indexed identity, - bytes32 name, - bytes value, - uint validTo, - uint previousChange -); -``` - - -### Efficient lookup of events through linked identity events - -Contract Events are a useful feature for storing data from smart contracts exclusively for off-chain use. Unfortunately current ethereum implementations provide a very inefficient lookup mechanism. By using linked events that always link to the previous block with a change for the identity, we can solve this problem with much improved performance. Each identity has its previously changed block stored in the `changed` mapping. - - - -1. Lookup `previousChange` block for identity - -2. Lookup all events for given identity address using web3, but only for the `previousChange` block - -3. Do something with event - -4. Find `previousChange` from the event and repeat - - - -Example code: - - -```js -const history = [] -previousChange = await didReg.changed(identity) -while (previousChange) { - const filter = await didReg.allEvents({topics: [identity], fromBlock: previousChange, toBlock: previousChange}) - const events = await getLogs(filter) - previousChange = undefined - for (let event of events) { - history.unshift(event) - previousChange = event.args.previousChange - } -} -``` - - -### Building a DID document for an identity - -The primary owner key should be looked up using `identityOwner(identity)`. This should be the first of the publicKeys listed. Iterate through the `DIDDelegateChanged` events to build a list of additional keys and authentication sections as needed. The list of delegateTypes to include is still to be determined. Iterate through `DIDAttributeChanged` events for service entries, encryption public keys and other public names. The attribute names are still to be determined. - - -## Rationale - -For on-chain interactions Ethereum has a built in account abstraction that can be used regardless of whether the account is a smart contract or a key pair. Any transaction has a `msg.sender` as the verified send of the transaction. - - -Since each Ethereum transaction has to be funded, there is a growing trend of on-chain transactions that are authenticated via an externally created signature and not by the actual transaction originator. This allows 3rd party funding services or receiver pays without any fundamental changes to the underlying Ethereum architecture. These kinds of transactions have to be signed by an actual key pair and thus can not be used to represent smart contract based Ethereum accounts. - - -We propose a way of a Smart Contract or regular key pair delegating signing for various purposes to externally managed key pairs. This allows a smart contract to be represented both on-chain as well as off-chain or in payment channels through temporary or permanent delegates. - - -## Backwards Compatibility - -All ethereum accounts are valid identities (and DID compatible) using this standard. This means that any wallet provider that uses key pair accounts already supports the bare minimum of this standard, and can implement `delegate` and `attribute` functionality by simply using the `ethr-did` referenced below. As the **DID Auth** standard solidifies it also means that all of these wallets will be compatible with the [DID decentralized login system](https://github.com/decentralized-identity). - - -## Implementation - -[ethr-did-registry](https://github.com/uport-project/ethr-did-registry/blob/develop/contracts/EthereumDIDRegistry.sol) (`EthereumDIDRegistry` contract implementation) - -[ethr-did-resolver](https://github.com/uport-project/ethr-did-resolver) (DID compatible resolver) - -[ethr-did](https://github.com/uport-project/ethr-did) (javascript library for using the identity) - - -### Deployment - -The address for the `EthereumDIDRegistry` is `0xdca7ef03e98e0dc2b855be647c39abe984fcf21b` on Mainnet, Ropsten, Rinkeby and Kovan. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1056.md diff --git a/EIPS/eip-1062.md b/EIPS/eip-1062.md index 0f304b6cb2c8b7..da3a288fe50cb0 100644 --- a/EIPS/eip-1062.md +++ b/EIPS/eip-1062.md @@ -1,84 +1 @@ ---- -eip: 1062 -title: Formalize IPFS hash into ENS(Ethereum Name Service) resolver -author: Phyrex Tsai , Portal Network Team -discussions-to: https://ethereum-magicians.org/t/eip-1062-formalize-ipfs-hash-into-ens-ethereum-name-service-resolver/281 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-05-02 ---- - -## Simple Summary -To specify the mapping protocol between resources stored on IPFS and ENS(Ethereum Naming Service). - -## Abstract -The following standard details the implementation of how to combine the IPFS cryptographic hash unique fingerprint with ENS public resolver. This standard provides a functionality to get and set IPFS online resources to ENS resolver. - -We think that this implementation is not only aim to let more developers and communities to provide more use cases, but also leverage the human-readable features to gain more user adoption accessing decentralized resources. We considered the IPFS ENS resolver mapping standard a cornerstone for building future Web3.0 service. - -## Motivation -To build fully decentralized web service, it’s necessary to have a decentralized file storage system. Here comes the IPFS, for three following advantages : -- Address large amounts of data, and has unique cryptographic hash for every record. -- Since IPFS is also based on peer to peer network, it can be really helpful to deliver large amounts of data to users, with safer way and lower the millions of cost for the bandwidth. -- IPFS stores files in high efficient way via tracking version history for every file, and removing the duplications across the network. - -Those features makes perfect match for integrating into ENS, and these make users can easily access content through ENS, and show up in the normal browser. - - -## Specification -The condition now is that the IPFS file fingerprint using base58 and in the meantime, the Ethereum uses hex in API to encode the binary data. So that need a way to process the condition requires not only we need to transfer from IPFS to Ethereum, but also need to convert it back. - -To solve these requirements, we can use binary buffer bridging that gap. -When mapping the IPFS base58 string to ENS resolver, first we convert the Base58 to binary buffer, turn the buffer to hex encrypted format, and save to the contract. Once we want to get the IPFS resources address represented by the specific ENS, we can first find the mapping information stored as hex format before, extract the hex format to binary buffer, and finally turn that to IPFS Base58 address string. - - -## Rationale -To implement the specification, need two methods from ENS public resolver contract, when we want to store IPFS file fingerprint to contract, convert the Base58 string identifier to the hex format and invoke the `setMultihash` method below : - -```solidity -function setMultihash(bytes32 node, bytes hash) public only_owner(node); -``` - -Whenever users need to visit the ENS content, we call the `multihash` method to get the IPFS hex data, transfer to the Base58 format, and return the IPFS resources to use. - -```solidity -function multihash(bytes32 node) public view returns (bytes); -``` - -## Test Cases - -To implement the way to transfer from base58 to hex format and the reverse one, using the ‘multihashes’ library to deal with the problem. -The library link : [https://www.npmjs.com/package/multihashes](https://www.npmjs.com/package/multihashes) -To implement the method transfer from IPFS(Base58) to hex format : - -```javascript -import multihash from 'multihashes' - -export const toHex = function(ipfsHash) { - let buf = multihash.fromB58String(ipfsHash); - return '0x' + multihash.toHexString(buf); -} -``` - -To implement the method transfer from hex format to IPFS(Base58) : - -```javascript -import multihash from 'multihashes' - -export const toBase58 = function(contentHash) { - let hex = contentHash.substring(2) - let buf = multihash.fromHexString(hex); - return multihash.toB58String(buf); -} -``` - -## Implementation -The use case can be implemented as browser extension. Users can easily download the extension, and easily get decentralized resources by just typing the ENS just like we normally type the DNS to browser the website. Solve the current pain for normal people can not easily visit the total decentralized website. - -The workable implementation repository : [https://github.com/PortalNetwork/portal-network-browser-extension](https://github.com/PortalNetwork/portal-network-browser-extension) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1062.md diff --git a/EIPS/eip-1066.md b/EIPS/eip-1066.md index 5b29e2dd32c981..7eeda6947cbc75 100644 --- a/EIPS/eip-1066.md +++ b/EIPS/eip-1066.md @@ -1,598 +1 @@ ---- -eip: 1066 -title: Status Codes -author: Brooklyn Zelenka (@expede), Tom Carchrae (@carchrae), Gleb Naumenko (@naumenkogs) -discussions-to: https://ethereum-magicians.org/t/erc-1066-ethereum-status-codes-esc/ -status: Stagnant -type: Standards Track -category: ERC -created: 2018-05-05 ---- - -## Simple Summary - -Broadly applicable status codes for smart contracts. - -## Abstract - -This standard outlines a common set of status codes in a similar vein to HTTP statuses. This provides a shared set of signals to allow smart contracts to react to situations autonomously, expose localized error messages to users, and so on. - -The current state of the art is to either `revert` on anything other than a clear success (ie: require human intervention), or return a low-context `true` or `false`. Status codes are similar-but-orthogonal to `revert`ing with a reason, but aimed at automation, debugging, and end-user feedback (including translation). _They are fully compatible with both `revert` and `revert`-with-reason._ - -As is the case with HTTP, having a standard set of known codes has many benefits for developers. They remove friction from needing to develop your own schemes for every contract, makes inter-contract automation easier, and makes it easier to broadly understand which of the finite states your request produced. Importantly, it makes it much easier to distinguish between expected errors states, truly exceptional conditions that require halting execution, normal state transitions, and various success cases. - -## Motivation - -### Semantic Density - -HTTP status codes are widely used for this purpose. BEAM languages use atoms and tagged tuples to signify much the same information. Both provide a lot of information both to the programmer (debugging for instance), and to the program that needs to decide what to do next. - -Status codes convey a much richer set of information [than Booleans](https://existentialtype.wordpress.com/2011/03/15/boolean-blindness/), and are able to be reacted to autonomously unlike arbitrary strings. - -### User Experience (UX) - -_End users get little to no feedback, and there is no translation layer._ - -Since ERC1066 status codes are finite and known in advance, we can leverage [ERC-1444](./eip-1444.md) to provide global, human-readable sets of status messages. These may also be translated into any language, differing levels of technical detail, added as `revert` messages, natspecs, and so on. - -Status codes convey a much richer set of information than Booleans, and are able to be reacted to autonomously unlike arbitrary strings. - -### Developer Experience (DX) - -_Developers currently have very little context exposed by their smart contracts._ - -At time of writing, other than stepping through EVM execution and inspecting memory dumps directly, it is very difficult to understand what is happening during smart contract execution. By returning more context, developers can write well-decomposed tests and assert certain codes are returned as an expression of where the smart contract got to. This includes status codes as bare values, `event`s, and `revert`s. - -Having a fixed set of codes also makes it possible to write common helper functions to react in common ways to certain signals. This can live off- or on-chain library, lowering the overhead in building smart contracts, and helping raise code quality with trusted shared components. - -We also see a desire for this [in transactions](./eip-658.md), and there's no reason that these status codes couldn't be used by the EVM itself. - -### Smart Contract Autonomy - -_Smart contracts don’t know much about the result of a request beyond pass/fail; they can be smarter with more context._ - -Smart contracts are largely intended to be autonomous. While each contract may define a specific interface, having a common set of semantic codes can help developers write code that can react appropriately to various situations. - -While clearly related, status codes are complementary to `revert`-with-reason. Status codes are not limited to rolling back the transaction, and may represent known error states without halting execution. They may also represent off-chain conditions, supply a string to revert, signal time delays, and more. - -All of this enables contracts to share a common vocabulary of state transitions, results, and internal changes, without having to deeply understand custom status enums or the internal business logic of collaborator contracts. - -## Specification - -### Format - -Codes are returned either on their own, or as the first value of a multiple return. - -```solidity -// Status only - -function isInt(uint num) public pure returns (byte status) { - return hex"01"; -} - -// Status and value - -uint8 private counter; - -function safeIncrement(uint8 interval) public returns (byte status, uint8 newCounter) { - uint8 updated = counter + interval; - - if (updated >= counter) { - counter = updated; - return (hex"01", updated); - } else { - return (hex"00", counter); - } -} -``` - -### Code Table - -Codes break nicely into a 16x16 matrix, represented as a 2-digit hex number. The high nibble represents the code's kind or "category", and the low nibble contains the state or "reason". We present them below as separate tables per range for explanatory and layout reasons. - -**NB: Unspecified codes are _not_ free for arbitrary use, but rather open for further specification.** - -#### `0x0*` Generic - -General codes. These double as bare "reasons", since `0x01 == 1`. - -| Code | Description | -|--------|-----------------------------------------| -| `0x00` | Failure | -| `0x01` | Success | -| `0x02` | Awaiting Others | -| `0x03` | Accepted | -| `0x04` | Lower Limit or Insufficient | -| `0x05` | Receiver Action Requested | -| `0x06` | Upper Limit | -| `0x07` | [reserved] | -| `0x08` | Duplicate, Unnecessary, or Inapplicable | -| `0x09` | [reserved] | -| `0x0A` | [reserved] | -| `0x0B` | [reserved] | -| `0x0C` | [reserved] | -| `0x0D` | [reserved] | -| `0x0E` | [reserved] | -| `0x0F` | Informational or Metadata | - -#### `0x1*` Permission & Control - -Also used for common state machine actions (ex. "stoplight" actions). - -| Code | Description | -|--------|---------------------------------------------------| -| `0x10` | Disallowed or Stop | -| `0x11` | Allowed or Go | -| `0x12` | Awaiting Other's Permission | -| `0x13` | Permission Requested | -| `0x14` | Too Open / Insecure | -| `0x15` | Needs Your Permission or Request for Continuation | -| `0x16` | Revoked or Banned | -| `0x17` | [reserved] | -| `0x18` | Not Applicable to Current State | -| `0x19` | [reserved] | -| `0x1A` | [reserved] | -| `0x1B` | [reserved] | -| `0x1C` | [reserved] | -| `0x1D` | [reserved] | -| `0x1E` | [reserved] | -| `0x1F` | Permission Details or Control Conditions | - -#### `0x2*` Find, Inequalities & Range - -This range is broadly intended for finding and matching. Data lookups and order matching are two common use cases. - -| Code | Description | -|--------|-------------------------------------| -| `0x20` | Not Found, Unequal, or Out of Range | -| `0x21` | Found, Equal or In Range | -| `0x22` | Awaiting Match | -| `0x23` | Match Request Sent | -| `0x24` | Below Range or Underflow | -| `0x25` | Request for Match | -| `0x26` | Above Range or Overflow | -| `0x27` | [reserved] | -| `0x28` | Duplicate, Conflict, or Collision | -| `0x29` | [reserved] | -| `0x2A` | [reserved] | -| `0x2B` | [reserved] | -| `0x2C` | [reserved] | -| `0x2D` | [reserved] | -| `0x2E` | [reserved] | -| `0x2F` | Matching Meta or Info | - -#### `0x3*` Negotiation & Governance - -Negotiation, and very broadly the flow of such transactions. Note that "other party" may be more than one actor (not necessarily the sender). - -| Code | Description | -|--------|-----------------------------------------| -| `0x30` | Sender Disagrees or Nay | -| `0x31` | Sender Agrees or Yea | -| `0x32` | Awaiting Ratification | -| `0x33` | Offer Sent or Voted | -| `0x34` | Quorum Not Reached | -| `0x35` | Receiver's Ratification Requested | -| `0x36` | Offer or Vote Limit Reached | -| `0x37` | [reserved] | -| `0x38` | Already Voted | -| `0x39` | [reserved] | -| `0x3A` | [reserved] | -| `0x3B` | [reserved] | -| `0x3C` | [reserved] | -| `0x3D` | [reserved] | -| `0x3E` | [reserved] | -| `0x3F` | Negotiation Rules or Participation Info | - -#### `0x4*` Availability & Time - -Service or action availability. - -| Code | Description | -|--------|------------------------------------------------------| -| `0x40` | Unavailable | -| `0x41` | Available | -| `0x42` | Paused | -| `0x43` | Queued | -| `0x44` | Not Available Yet | -| `0x45` | Awaiting Your Availability | -| `0x46` | Expired | -| `0x47` | [reserved] | -| `0x48` | Already Done | -| `0x49` | [reserved] | -| `0x4A` | [reserved] | -| `0x4B` | [reserved] | -| `0x4C` | [reserved] | -| `0x4D` | [reserved] | -| `0x4E` | [reserved] | -| `0x4F` | Availability Rules or Info (ex. time since or until) | - -#### `0x5*` Tokens, Funds & Finance - -Special token and financial concepts. Many related concepts are included in other ranges. - -| Code | Description | -|--------|---------------------------------| -| `0x50` | Transfer Failed | -| `0x51` | Transfer Successful | -| `0x52` | Awaiting Payment From Others | -| `0x53` | Hold or Escrow | -| `0x54` | Insufficient Funds | -| `0x55` | Funds Requested | -| `0x56` | Transfer Volume Exceeded | -| `0x57` | [reserved] | -| `0x58` | Funds Not Required | -| `0x59` | [reserved] | -| `0x5A` | [reserved] | -| `0x5B` | [reserved] | -| `0x5C` | [reserved] | -| `0x5D` | [reserved] | -| `0x5E` | [reserved] | -| `0x5F` | Token or Financial Information | - -#### `0x6*` TBD - -Currently unspecified. (Full range reserved) - -#### `0x7*` TBD - -Currently unspecifie. (Full range reserved) - -#### `0x8*` TBD - -Currently unspecified. (Full range reserved) - -#### `0x9*` TBD - -Currently unspecified. (Full range reserved) - -#### `0xA*` Application-Specific Codes - -Contracts may have special states that they need to signal. This proposal only outlines the broadest meanings, but implementers may have very specific meanings for each, as long as they are coherent with the broader definition. - -| Code | Description | -|--------|----------------------------------------| -| `0xA0` | App-Specific Failure | -| `0xA1` | App-Specific Success | -| `0xA2` | App-Specific Awaiting Others | -| `0xA3` | App-Specific Acceptance | -| `0xA4` | App-Specific Below Condition | -| `0xA5` | App-Specific Receiver Action Requested | -| `0xA6` | App-Specific Expiry or Limit | -| `0xA7` | [reserved] | -| `0xA8` | App-Specific Inapplicable Condition | -| `0xA9` | [reserved] | -| `0xAA` | [reserved] | -| `0xAB` | [reserved] | -| `0xAC` | [reserved] | -| `0xAD` | [reserved] | -| `0xAE` | [reserved] | -| `0xAF` | App-Specific Meta or Info | - -#### `0xB*` TBD - -Currently unspecified. (Full range reserved) - -#### `0xC*` TBD - -Currently unspecified. (Full range reserved) - -#### `0xD*` TBD - -Currently unspecified. (Full range reserved) - -#### `0xE*` Encryption, Identity & Proofs - -Actions around signatures, cryptography, signing, and application-level authentication. - -The meta code `0xEF` is often used to signal a payload describing the algorithm or process used. - -| Code | Description | -|--------|-------------------------------------| -| `0xE0` | Decrypt Failure | -| `0xE1` | Decrypt Success | -| `0xE2` | Awaiting Other Signatures or Keys | -| `0xE3` | Signed | -| `0xE4` | Unsigned or Untrusted | -| `0xE5` | Signature Required | -| `0xE6` | Known to be Compromised | -| `0xE7` | [reserved] | -| `0xE8` | Already Signed or Not Encrypted | -| `0xE9` | [reserved] | -| `0xEA` | [reserved] | -| `0xEB` | [reserved] | -| `0xEC` | [reserved] | -| `0xED` | [reserved] | -| `0xEE` | [reserved] | -| `0xEF` | Cryptography, ID, or Proof Metadata | - -#### `0xF*` Off-Chain - -For off-chain actions. Much like th `0x0*: Generic` range, `0xF*` is very general, and does little to modify the reason. - -Among other things, the meta code `0xFF` may be used to describe what the off-chain process is. - -| Code | Description | -|--------|-----------------------------------| -| `0xF0` | Off-Chain Failure | -| `0xF1` | Off-Chain Success | -| `0xF2` | Awaiting Off-Chain Process | -| `0xF3` | Off-Chain Process Started | -| `0xF4` | Off-Chain Service Unreachable | -| `0xF5` | Off-Chain Action Required | -| `0xF6` | Off-Chain Expiry or Limit Reached | -| `0xF7` | [reserved] | -| `0xF8` | Duplicate Off-Chain Request | -| `0xF9` | [reserved] | -| `0xFA` | [reserved] | -| `0xFB` | [reserved] | -| `0xFC` | [reserved] | -| `0xFD` | [reserved] | -| `0xFE` | [reserved] | -| `0xFF` | Off-Chain Info or Meta | - -### As a Grid - -| | `0x0*` General | `0x1*` Permission & Control | `0x2*` Find, Inequalities & Range | `0x3*` Negotiation & Governance | `0x4*` Availability & Time | `0x5*` Tokens, Funds & Finance | `0x6*` TBD | `0x7*` TBD | `0x8*` TBD | `0x9*` TBD | `0xA*` Application-Specific Codes | `0xB*` TBD | `0xC*` TBD | `0xD*` TBD | `0xE*` Encryption, Identity & Proofs | `0xF*` Off-Chain | -|--------|------------------------------------------------|----------------------------------------------------------|--------------------------------------------|------------------------------------------------|-------------------------------------------------------------|----------------------------------------|-------------------|-------------------|-------------------|-------------------|-----------------------------------------------|-------------------|-------------------|-------------------|--------------------------------------------|------------------------------------------| -| `0x*0` | `0x00` Failure | `0x10` Disallowed or Stop | `0x20` Not Found, Unequal, or Out of Range | `0x30` Sender Disagrees or Nay | `0x40` Unavailable | `0x50` Transfer Failed | `0x60` [reserved] | `0x70` [reserved] | `0x80` [reserved] | `0x90` [reserved] | `0xA0` App-Specific Failure | `0xB0` [reserved] | `0xC0` [reserved] | `0xD0` [reserved] | `0xE0` Decrypt Failure | `0xF0` Off-Chain Failure | -| `0x*1` | `0x01` Success | `0x11` Allowed or Go | `0x21` Found, Equal or In Range | `0x31` Sender Agrees or Yea | `0x41` Available | `0x51` Transfer Successful | `0x61` [reserved] | `0x71` [reserved] | `0x81` [reserved] | `0x91` [reserved] | `0xA1` App-Specific Success | `0xB1` [reserved] | `0xC1` [reserved] | `0xD1` [reserved] | `0xE1` Decrypt Success | `0xF1` Off-Chain Success | -| `0x*2` | `0x02` Awaiting Others | `0x12` Awaiting Other's Permission | `0x22` Awaiting Match | `0x32` Awaiting Ratification | `0x42` Paused | `0x52` Awaiting Payment From Others | `0x62` [reserved] | `0x72` [reserved] | `0x82` [reserved] | `0x92` [reserved] | `0xA2` App-Specific Awaiting Others | `0xB2` [reserved] | `0xC2` [reserved] | `0xD2` [reserved] | `0xE2` Awaiting Other Signatures or Keys | `0xF2` Awaiting Off-Chain Process | -| `0x*3` | `0x03` Accepted | `0x13` Permission Requested | `0x23` Match Request Sent | `0x33` Offer Sent or Voted | `0x43` Queued | `0x53` Hold or Escrow | `0x63` [reserved] | `0x73` [reserved] | `0x83` [reserved] | `0x93` [reserved] | `0xA3` App-Specific Acceptance | `0xB3` [reserved] | `0xC3` [reserved] | `0xD3` [reserved] | `0xE3` Signed | `0xF3` Off-Chain Process Started | -| `0x*4` | `0x04` Lower Limit or Insufficient | `0x14` Too Open / Insecure | `0x24` Below Range or Underflow | `0x34` Quorum Not Reached | `0x44` Not Available Yet | `0x54` Insufficient Funds | `0x64` [reserved] | `0x74` [reserved] | `0x84` [reserved] | `0x94` [reserved] | `0xA4` App-Specific Below Condition | `0xB4` [reserved] | `0xC4` [reserved] | `0xD4` [reserved] | `0xE4` Unsigned or Untrusted | `0xF4` Off-Chain Service Unreachable | -| `0x*5` | `0x05` Receiver Action Required | `0x15` Needs Your Permission or Request for Continuation | `0x25` Request for Match | `0x35` Receiver's Ratification Requested | `0x45` Awaiting Your Availability | `0x55` Funds Requested | `0x65` [reserved] | `0x75` [reserved] | `0x85` [reserved] | `0x95` [reserved] | `0xA5` App-Specific Receiver Action Requested | `0xB5` [reserved] | `0xC5` [reserved] | `0xD5` [reserved] | `0xE5` Signature Required | `0xF5` Off-Chain Action Required | -| `0x*6` | `0x06` Upper Limit | `0x16` Revoked or Banned | `0x26` Above Range or Overflow | `0x36` Offer or Vote Limit Reached | `0x46` Expired | `0x56` Transfer Volume Exceeded | `0x66` [reserved] | `0x76` [reserved] | `0x86` [reserved] | `0x96` [reserved] | `0xA6` App-Specific Expiry or Limit | `0xB6` [reserved] | `0xC6` [reserved] | `0xD6` [reserved] | `0xE6` Known to be Compromised | `0xF6` Off-Chain Expiry or Limit Reached | -| `0x*7` | `0x07` [reserved] | `0x17` [reserved] | `0x27` [reserved] | `0x37` [reserved] | `0x47` [reserved] | `0x57` [reserved] | `0x67` [reserved] | `0x77` [reserved] | `0x87` [reserved] | `0x97` [reserved] | `0xA7` [reserved] | `0xB7` [reserved] | `0xC7` [reserved] | `0xD7` [reserved] | `0xE7` [reserved] | `0xF7` [reserved] | -| `0x*8` | `0x08` Duplicate, Unnecessary, or Inapplicable | `0x18` Not Applicable to Current State | `0x28` Duplicate, Conflict, or Collision | `0x38` Already Voted | `0x48` Already Done | `0x58` Funds Not Required | `0x68` [reserved] | `0x78` [reserved] | `0x88` [reserved] | `0x98` [reserved] | `0xA8` App-Specific Inapplicable Condition | `0xB8` [reserved] | `0xC8` [reserved] | `0xD8` [reserved] | `0xE8` Already Signed or Not Encrypted | `0xF8` Duplicate Off-Chain Request | -| `0x*9` | `0x09` [reserved] | `0x19` [reserved] | `0x29` [reserved] | `0x39` [reserved] | `0x49` [reserved] | `0x59` [reserved] | `0x69` [reserved] | `0x79` [reserved] | `0x89` [reserved] | `0x99` [reserved] | `0xA9` [reserved] | `0xB9` [reserved] | `0xC9` [reserved] | `0xD9` [reserved] | `0xE9` [reserved] | `0xF9` [reserved] | -| `0x*A` | `0x0A` [reserved] | `0x1A` [reserved] | `0x2A` [reserved] | `0x3A` [reserved] | `0x4A` [reserved] | `0x5A` [reserved] | `0x6A` [reserved] | `0x7A` [reserved] | `0x8A` [reserved] | `0x9A` [reserved] | `0xAA` [reserved] | `0xBA` [reserved] | `0xCA` [reserved] | `0xDA` [reserved] | `0xEA` [reserved] | `0xFA` [reserved] | -| `0x*B` | `0x0B` [reserved] | `0x1B` [reserved] | `0x2B` [reserved] | `0x3B` [reserved] | `0x4B` [reserved] | `0x5B` [reserved] | `0x6B` [reserved] | `0x7B` [reserved] | `0x8B` [reserved] | `0x9B` [reserved] | `0xAB` [reserved] | `0xBB` [reserved] | `0xCB` [reserved] | `0xDB` [reserved] | `0xEB` [reserved] | `0xFB` [reserved] | -| `0x*C` | `0x0C` [reserved] | `0x1C` [reserved] | `0x2C` [reserved] | `0x3C` [reserved] | `0x4C` [reserved] | `0x5C` [reserved] | `0x6C` [reserved] | `0x7C` [reserved] | `0x8C` [reserved] | `0x9C` [reserved] | `0xAC` [reserved] | `0xBC` [reserved] | `0xCC` [reserved] | `0xDC` [reserved] | `0xEC` [reserved] | `0xFC` [reserved] | -| `0x*D` | `0x0D` [reserved] | `0x1D` [reserved] | `0x2D` [reserved] | `0x3D` [reserved] | `0x4D` [reserved] | `0x5D` [reserved] | `0x6D` [reserved] | `0x7D` [reserved] | `0x8D` [reserved] | `0x9D` [reserved] | `0xAD` [reserved] | `0xBD` [reserved] | `0xCD` [reserved] | `0xDD` [reserved] | `0xED` [reserved] | `0xFD` [reserved] | -| `0x*E` | `0x0E` [reserved] | `0x1E` [reserved] | `0x2E` [reserved] | `0x3E` [reserved] | `0x4E` [reserved] | `0x5E` [reserved] | `0x6E` [reserved] | `0x7E` [reserved] | `0x8E` [reserved] | `0x9E` [reserved] | `0xAE` [reserved] | `0xBE` [reserved] | `0xCE` [reserved] | `0xDE` [reserved] | `0xEE` [reserved] | `0xFE` [reserved] | -| `0x*F` | `0x0F` Informational or Metadata | `0x1F` Permission Details or Control Conditions | `0x2F` Matching Meta or Info | `0x3F` Negotiation Rules or Participation Info | `0x4F` Availability Rules or Info (ex. time since or until) | `0x5F` Token or Financial Information | `0x6F` [reserved] | `0x7F` [reserved] | `0x8F` [reserved] | `0x9F` [reserved] | `0xAF` App-Specific Meta or Info | `0xBF` [reserved] | `0xCF` [reserved] | `0xDF` [reserved] | `0xEF` Cryptography, ID, or Proof Metadata | `0xFF` Off-Chain Info or Meta | - -### Example Function Change - -```solidity -uint256 private startTime; -mapping(address => uint) private counters; - -// Before -function increase() public returns (bool _available) { - if (now < startTime && counters[msg.sender] == 0) { - return false; - }; - - counters[msg.sender] += 1; - return true; -} - -// After -function increase() public returns (byte _status) { - if (now < start) { return hex"44"; } // Not yet available - if (counters[msg.sender] == 0) { return hex"10"; } // Not authorized - - counters[msg.sender] += 1; - return hex"01"; // Success -} -``` - -### Example Sequence Diagrams - -``` -0x03 = Waiting -0x31 = Other Party (ie: not you) Agreed -0x41 = Available -0x44 = Not Yet Available - - - Exchange - - -AwesomeCoin DEX TraderBot - + + + - | | buy(AwesomeCoin) | - | | <------------------------+ - | buy() | | - | <---------------------+ | - | | | - | Status [0x44] | | - +---------------------> | Status [0x44] | - | +------------------------> | - | | | - | | isDoneYet() | - | | <------------------------+ - | | | - | | Status [0x44] | - | +------------------------> | - | | | - | | | - | Status [0x41] | | - +---------------------> | | - | | | - | buy() | | - | <---------------------+ | - | | | - | | | - | Status [0x31] | | - +---------------------> | Status [0x31] | - | +------------------------> | - | | | - | | | - | | | - | | | - + + + -``` - - - -``` -0x01 = Generic Success -0x10 = Disallowed -0x11 = Allowed - - Token Validation - - - Buyer RegulatedToken TokenValidator IDChecker SpendLimiter - + + + + + - | buy() | | | | - +------------------------> | check() | | | - | +-----------------------> | check() | | - | | +-----------------------> | | - | | | | | - | | | Status [0x10] | | - | | Status [0x10] | <-----------------------+ | - | revert() | <-----------------------+ | | - | <------------------------+ | | | - | | | | | -+---------------------------+ | | | | -| | | | | | -| Updates ID with provider | | | | | -| | | | | | -+---------------------------+ | | | | - | | | | | - | buy() | | | | - +------------------------> | check() | | | - | +-----------------------> | check() | | - | | +-----------------------> | | - | | | | | - | | | Status [0x11] | | - | | | <-----------------------+ | - | | | | | - | | | | check() | - | | +-------------------------------------------> | - | | | | | - | | | | Status [0x11] | - | | Status [0x11] | <-------------------------------------------+ - | Status [0x01] | <-----------------------+ | | - | <------------------------+ | | | - | | | | | - | | | | | - | | | | | - + + + + + -``` - -## Rationale - -### Encoding - -Status codes are encoded as a `byte`. Hex values break nicely into high and low nibbles: `category` and `reason`. For instance, `0x01` stands for general success (ie: `true`) and `0x00` for general failure (ie: `false`). - -As a general approach, all even numbers are blocking conditions (where the receiver does not have control), and odd numbers are nonblocking (the receiver is free to contrinue as they wish). This aligns both a simple bit check with the common encoding of Booleans. - -`bytes1` is very lightweight, portable, easily interoperable with `uint8`, cast from `enum`s, and so on. - -#### Alternatives - -Alternate schemes include `bytes32` and `uint8`. While these work reasonably well, they have drawbacks. - -`uint8` feels even more similar to HTTP status codes, and enums don't require as much casting. However does not break as evenly as a square table (256 doesn't look as nice in base 10). - -Packing multiple codes into a single `bytes32` is nice in theory, but poses additional challenges. Unused space may be interpreted as `0x00 Failure`, you can only efficiently pack four codes at once, and there is a challenge in ensuring that code combinations are sensible. Forcing four codes into a packed representation encourages multiple status codes to be returned, which is often more information than strictly necessarily. This can lead to paradoxical results (ex `0x00` and `0x01` together), or greater resources allocated to interpreting 2564 (4.3 billion) permutations. - -### Multiple Returns - -While there may be cases where packing a byte array of status codes may make sense, the simplest, most forwards-compatible method of transmission is as the first value of a multiple return. - -Familiarity is also a motivating factor. A consistent position and encoding together follow the principle of least surprise. It is both viewable as a "header" in the HTTP analogy, or like the "tag" in BEAM tagged tuples. - -### Human Readable - -Developers should not be required to memorize 256 codes. However, they break nicely into a table. Cognitive load is lowered by organizing the table into categories and reasons. `0x10` and `0x11` belong to the same category, and `0x04` shares a reason with `0x24` - -While this repository includes helper enums, we have found working directly in the hex values to be quite natural. Status code `0x10` is just as comfortable as HTTP 401, for example. - -#### Localizations - -One commonly requested application of this spec is human-readable translations of codes. This has been moved to its own proposal: [ERC-1444](./eip-1444.md), primarily due to a desire to keep both specs focused. - -### Extensibility - -The `0xA` category is reserved for application-specific statuses. In the case that 256 codes become insufficient, `bytes1` may be embedded in larger byte arrays. - -### EVM Codes - -The EVM also returns a status code in transactions; specifically `0x00` and `0x01`. This proposal both matches the meanings of those two codes, and could later be used at the EVM level. - -### Empty Space - -Much like how HTTP status codes have large unused ranges, there are totally empty sections in this proposal. The intent is to not impose a complete set of codes up front, and to allow users to suggest uses for these spaces as time progresses. - -### Beyond Errors - -This spec is intended to be much more than a set of common errors. One design goal is to enable easier contract-to-contract communication, protocols built on top of status codes, and flows that cross off-chain. Many of these cases include either expected kinds of exception state (as opposed to true errors), neutral states, time logic, and various successes. - -Just like how HTTP 200 has a different meaning from HTTP 201, ERC-1066 status codes can relay information between contract beyond simply pass or fail. They can be thought of as the edges in a graph that has smart contracts as nodes. - -### Fully `revert`able - -_This spec is fully compatible with `revert`-with-reason and does not intend to supplant it in any way._ Both by reverting with a common code, the developer can determine what went wrong from a set of known error states. - -Further, by leveraging ERC-1066 and a translation table (such as in ERC-1444) in conjunction, developers and end users alike can receive fully automated human-readable error messages in the language and phrasing of their choice. - -### Nibble Order - -Nibble order makes no difference to the machine, and is purely mnemonic. This design was originally in opposite order, but changed it for a few convenience factors. Since it's a different scheme from HTTP, it may feel strange initially, but becomes very natural after a couple hours of use. - -#### Short Forms - -Generic is `0x0*`, general codes are consistent with their integer representations - -```solidity -hex"1" == hex"01" == 1 // with casting -``` - -#### Contract Categories - -Many applications will always be part of the same category. For instance, validation will generally be in the `0x10` range. - -```solidity -contract Whitelist { - mapping(address => bool) private whitelist; - uint256 private deadline; - byte constant private prefix = hex"10"; - - check(address _, address _user) returns (byte _status) { - if (now >= deadline) { return prefix | 5; } - if (whitelist[_user]) { return prefix | 1; } - return prefix; - } -} -``` - -#### Helpers - -This above also means that working with app-specific enums is slightly easier, and also saves gas (fewer operations required). - -```solidity -enum Sleep { - Awake, - Asleep, - BedOccupied, - WindingDown -} - -// From the helper library - -function appCode(Sleep _state) returns (byte code) { - return byte(160 + _state); // 160 = 0xA0 -} - -// Versus - -function appCode(Sleep _state) returns (byte code) { - return byte((16 * _state) + 10); // 10 = 0xA -} -``` - -## Implementation - -Reference cases and helper libraries (Solidity and JS) can be found at: -* [Source Code](https://github.com/fission-suite/fission-codes/) -* [Package on npm](https://www.npmjs.com/package/fission-codes/) - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1066.md diff --git a/EIPS/eip-1077.md b/EIPS/eip-1077.md index 1efe6a144ddf8b..174bcb2b1e8254 100644 --- a/EIPS/eip-1077.md +++ b/EIPS/eip-1077.md @@ -1,229 +1 @@ ---- -eip: 1077 -title: Gas relay for contract calls -author: Alex Van de Sande , Ricardo Guilherme Schmidt (@3esmit) -discussions-to: https://ethereum-magicians.org/t/erc1077-and-1078-the-magic-of-executable-signed-messages-to-login-and-do-actions/351 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-05-04 -requires: 20, 191, 1271, 1344 ---- - - -## Simple Summary - -A standard interface for gas abstraction in top of smart contracts. - -Allows users to offer [EIP-20] token for paying the gas used in a call. - -## Abstract - -A main barrier for adoption of DApps is the requirement of multiple tokens for executing in chain actions. Allowing users to sign messages to show intent of execution, but allowing a third party relayer to execute them can circumvent this problem, while ETH will always be required for ethereum transactions, it's possible for smart contract to take [EIP-191] signatures and forward a payment incentive to an untrusted party with ETH for executing the transaction. - -## Motivation - -Standardizing a common format for them, as well as a way in which the user allows the transaction to be paid in tokens, gives app developers a lot of flexibility and can become the main way in which app users interact with the Blockchain. - - -## Specification - -### Methods - -#### executeGasRelay - -Executes `_execData` with current `lastNonce()` and pays `msg.sender` the gas used in specified `_gasToken`. - -```solidity -function executeGasRelay(bytes calldata _execData, uint256 _gasPrice, uint256 _gasLimit, address _gasToken, address _gasRelayer, bytes calldata _signature) external; -``` - -### executeGasRelayMsg - -Returns the `executeGasRelay` message used for signing messages.. - -```solidity -function executeGasRelayMsg(uint256 _nonce, bytes memory _execData, uint256 _gasPrice, uint256 _gasLimit, address _gasToken, address _gasRelayer) public pure returns (bytes memory); -``` - -#### executeGasRelayERC191Msg - -Returns the [EIP-191] of `executeGasRelayMsg` used for signing messages and for verifying the execution. - -```solidity -function executeGasRelayERC191Msg(uint256 _nonce, bytes memory _execData, uint256 _gasPrice, uint256 _gasLimit, address _gasToken, address _gasRelayer) public view returns (bytes memory); -``` - -#### lastNonce - -Returns the current nonce for the gas relayed messages. - -```solidity -function lastNonce() public returns (uint nonce); -``` - -### Signed Message - -The signed message require the following fields: - -* Nonce: A nonce *or* a timestamp; -* Execute Data: the bytecode to be executed by the account contract; -* Gas Price: The gas price (paid in the selected token); -* Gas Limit: The gas reserved to the relayed execution; -* Gas Token: A token in which the gas will be paid (leave 0 for ether); -* Gas Relayer: the beneficiary of gas refund for this call (leave 0 for `block.coinbase`) . - -#### Signing the message - -The message **MUST** be signed as [EIP-191] standard, and the called contract **MUST** also implement [EIP-1271] which must validate the signed messages. - -Messages **MUST** be signed by the owner of the account contract executing. If the owner is a contract, it must implement [EIP-1271] interface and forward validation to it. - -In order to be compliant, the transaction **MUST** request to sign a "messageHash" that is a concatenation of multiple fields. - -The fields **MUST** be constructed as this method: - -The first and second fields are to make it [EIP-191] compliant. Starting a transaction with `byte(0x19)` ensure the signed data from being a [valid ethereum transaction](https://github.com/ethereum/wiki/wiki/RLP). The second argument is a version control byte. The third being the validator address (the account contract address) according to version 0 of [EIP-191]. The remaining arguments being the application specific data for the gas relay: chainID as per [EIP-1344], execution nonce, execution data, agreed gas Price, gas limit of gas relayed call, gas token to pay back and gas relayer authorized to receive reward. - -The [EIP-191] message must be constructed as following: -```solidity -keccak256( - abi.encodePacked( - byte(0x19), //ERC-191 - the initial 0x19 byte - byte(0x0), //ERC-191 - the version byte - address(this), //ERC-191 - version data (validator address) - chainID, - bytes4( - keccak256("executeGasRelay(uint256,bytes,uint256,uint256,address,address)") - ), - _nonce, - _execData, - _gasPrice, - _gasLimit, - _gasToken, - _gasRelayer - ) -) -``` - -## Rationale - -User pain points: - -* users don't want to think about ether -* users don't want to think about backing up private keys or seed phrases -* users want to be able to pay for transactions using what they already have on the system, be apple pay, xbox points or even a credit card -* Users don’t want to sign a new transaction at every move -* Users don’t want to download apps/extensions (at least on the desktop) to connect to their apps - -App developer pain points: -* Many apps use their own token and would prefer to use those as the main accounting -* Apps want to be able to have apps in multiple platforms without having to share private keys between devices or have to spend transaction costs moving funds between them -* Token developers want to be able for their users to be able to move funds and pay fees in the token -* While the system provides fees and incentives for miners, there are no inherent business model for wallet developers (or other apps that initiate many transactions) - -Using signed messages, specially combined with an account contract that holds funds, and multiple disposable ether-less keys that can sign on its behalf, solves many of these pain points. - -### Multiple signatures - -More than one signed transaction with the same parameter can be executed by this function at the same time, by passing all signatures in the `messageSignatures` field. That field will split the signature in multiple 72 character individual signatures and evaluate each one. This is used for cases in which one action might require the approval of multiple parties, in a single transaction. - -If multiple signatures are required, then all signatures should then be *ordered by account* and the account contract should implement signatures checks locally (`JUMP`) on [EIP-1271] interface which might forward (`STATIC_CALL`) the [EIP-1271] signature check to owner contract. - -### Keep track of nonces: - -Note that `executeGasRelay` function does not take a `_nonce` as parameter. The contract knows what is the current nonce, and can only execute the transactions in order, therefore there is no reason - -Nonces work similarly to normal ethereum transactions: a transaction can only be executed if it matches the last nonce + 1, and once a transaction has occurred, the `lastNonce` will be updated to the current one. This prevents transactions to be executed out of order or more than once. - -Contracts may accept transactions without nonce (nonce = 0). The contract then must keep the full hash of the transaction to prevent it from being replayed. This would allows contracts to have more flexibilities as you can sign a transaction that can be executed out of order or not at all, but it uses more memory for each transaction. It can be used, for instance, for transactions that the user wants to schedule in the future but cannot know its future nonce, or transactions that are made for state channel contracts that are not guaranteed to be executed or are only executed when there's some dispute. - -### Execute transaction - -After signature validation, the evaluation of `_execBytes` is up to the account contract implementation, it's role of the wallet to properly use the account contract and it's gas relay method. -A common pattern is to expose an interface which can be only called by the contract itself. The `_execBytes` could entirely forward the call in this way, as example: `address(this).call.gas(_gasLimit)(_execData);` -Where `_execData` could call any method of the contract itself, for example: - -- `call(address to, uint256 value, bytes data)`: allow any type of ethereum call be performed; -- `create(uint256 value, bytes deployData)`: allows create contract -- `create2(uint256 value, bytes32 salt, bytes deployData)`: allows create contract with deterministic address -- `approveAndCall(address token, address to, uint256 value, bytes data)`: allows safe approve and call of an ERC20 token. -- `delegatecall(address codeBase, bytes data)`: allows executing code stored on other contract -- `changeOwner(address newOwner)`: Some account contracts might allow change of owner -- `foo(bytes bar)`: Some account contracts might have custom methods of any format. - -The standardization of account contracts is not scope of this ERC, and is presented here only for illustration on possible implementations. -Using a self call to evaluate `_execBytes` is not mandatory, depending on the account contract logic, the evaluation could be done locally. - -### Gas accounting and refund - -The implementing contract must keep track of the gas spent. One way to do it is to first call `gasLeft()` at the beginning of the function and then after executing the desired action and compare the difference. - -The contract then will make a token transfer (or ether, if `tokenAddress` is nil) in the value of `gasSpent * gasPrice` to the `_gasRelayer`, that is the account that deployed the message. - -If `_gasRelayer` is zero, then the funds **MUST** go to `block.coinbase`. - -If there are not enough funds, or if the total surpasses `gasLimit` then the transaction **MUST** revert. - -If the executed transaction fails internally, nonces should still be updated and gas needs to be paid. - -Contracts are not obligated to support ether or any other token they don’t want and can be implemented to only accept refunds in a few tokens of their choice. - -### Usage examples - -This scheme opens up a great deal of possibilities on interaction as well as different experiments on business models: - -* Apps can create individual identities contract for their users which holds the actual funds and then create a different private key for each device they log into. Other apps can use the same identity and just ask to add permissioned public keys to manage the device, so that if one individual key is lost, no ether is lost. -* An app can create its own token and only charge their users in its internal currency for any ethereum transaction. The currency units can be rounded so it looks more similar to to actual amount of transactions: a standard transaction always costs 1 token, a very complex transaction costs exactly 2, etc. Since the app is the issuer of the transactions, they can do their own Sybil verifications and give a free amount of currency units to new users to get them started. -* A game company creates games with a traditional monthly subscription, either by credit card or platform-specific microtransactions. Private keys never leave the device and keep no ether and only the public accounts are sent to the company. The game then signs transactions on the device with gas price 0, sends them to the game company which checks who is an active subscriber and batches all transactions and pays the ether themselves. If the company goes bankrupt, the gamers themselves can set up similar subscription systems or just increase the gas price. End result is a **ethereum based game in which gamers can play by spending apple, google or xbox credits**. -* A standard token is created that doesn’t require its users to have ether, and instead allows tokens to be transferred by paying in tokens. A wallet is created that signs messages and send them via whisper to the network, where other nodes can compete to download the available transactions, check the current gas price, and select those who are paying enough tokens to cover the cost. **The result is a token that the end users never need to keep any ether and can pay fees in the token itself.** -* A DAO is created with a list of accounts of their employees. Employees never need to own ether, instead they sign messages, send them to whisper to a decentralized list of relayers which then deploy the transactions. The DAO contract then checks if the transaction is valid and sends ether to the deployers. Employees have an incentive not to use too many of the companies resources because they’re identifiable. The result is that the users of the DAO don't need to keep ether, and **the contract ends up paying for it's own gas usage**. - -## Backwards Compatibility - -There is no issues with backwards compatibility, however for future upgrades, as `_execData` contains arbitrary data evaluated by the account contract, it's up to the contract to handle properly this data and therefore contracts can gas relay any behavior with the current interface. - -## Test Cases - -TBD - -## Implementation - -One initial implementation of such a contract can be found at [Status.im account-contracts repository](https://github.com/status-im/account-contracts/blob/develop/contracts/account/AccountGasAbstract.sol) - -Other version is implemented as Gnosis Safe variant in: https://github.com/status-im/safe-contracts - -### Similar implementations - -The idea of using signed messages as executable intent has been around for a while and many other projects are taking similar approaches, which makes it a great candidate for a standard that guarantees interoperability: - -* [EIP-877](https://github.com/ethereum/EIPs/pull/877) An attempt of doing the same but with a change in the protocol -* [Status](https://github.com/status-im/ideas/issues/73) -* [Aragon](https://github.com/aragonlabs/pay-protocol) (this might not be the best link to show their work in this area) -* [Token Standard Functions for Preauthorized Actions](https://github.com/ethereum/EIPs/issues/662) -* [Token Standard Extension 865](https://github.com/ethereum/EIPs/issues/865) -* [Iuri Matias: Transaction Relay](https://github.com/iurimatias/TransactionRelay) -* [uPort: Meta transactions](https://github.com/uport-project/uport-identity#send-a-meta-tx) -* [uPort: safe Identities](https://github.com/uport-project/uport-identity/blob/develop/docs/txRelay.md) -* [Gnosis safe contracts](https://github.com/gnosis/safe-contracts) - -Swarm city uses a similar proposition for etherless transactions, called [Gas Station Service](https://github.com/swarmcity/SCLabs-gasstation-service), but it's a different approach. Instead of using signed messages, a traditional ethereum transaction is signed on an etherless account, the transaction is then sent to a service that immediately sends the exact amount of ether required and then publishes the transaction. - -## Security Considerations - -Deployers of transactions (relayers) should be able to call untrusted contracts, which provides no guarantees that the contract they are interacting with correctly implements the standard and they will be reimbursed for gas. To prevent being fooled by bad implementations, relayers must **estimate the outcome of a transaction**, and only include/sign transactions which have a desired outcome. - -Is also interest of relayers to maintaining a private reputation of contracts they interact with, as well as keep track of which tokens and for which `gasPrice` they’re willing to deploy transactions. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - -## References - -* [Universal Logins talk at UX Unconf, Toronto](https://www.youtube.com/watch?v=qF2lhJzngto) - -[EIP-20]: ./eip-20.md -[EIP-191]: ./eip-191.md -[EIP-1271]: ./eip-1271.md -[EIP-1344]: ./eip-1344.md +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1077.md diff --git a/EIPS/eip-1078.md b/EIPS/eip-1078.md index 7990475fbcd98b..690f9a682eabd3 100644 --- a/EIPS/eip-1078.md +++ b/EIPS/eip-1078.md @@ -1,121 +1 @@ ---- -eip: 1078 -title: Universal login / signup using ENS subdomains -author: Alex Van de Sande -discussions-to: https://ethereum-magicians.org/t/erc1077-and-1078-the-magic-of-executable-signed-messages-to-login-and-do-actions/351 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-05-04 -requires: 191, 681, 725, 1077 ---- - -## Abstract - -This presents a method to replace the usual signup/login design pattern with a minimal ethereum native scheme, that doesn’t require passwords, backing up private keys nor typing seed phrases. From the user point of view it will be very similar to patterns they’re already used to with second factor authentication (without relying in a central server), but for dapp developers it requires a new way to think about ethereum transactions. - - -## Simple Summary - -The unique identifier of the user is a contract which implements both Identity and the Executable Signed Messages ERCs. The user should not need provide this address directly, only a ens name pointing to it. These types of contracts are indirectly controlled by private keys that can sign messages indicating intents, which are then deployed to the contract by a third party (or a decentralized network of deployers). - -In this context, therefore, a device "logging into" an app using an identity, means that the device will generate a private key locally and then request an authorization to add that key as one of the signers of that identity, with a given set of permissions. Since that private key is only used for signing messages, it is not required to hold ether, tokens or assets, and if lost, it can be simply be replaced by a new one – the user's funds are kept on the identity contract. - -In this context, ethereum accounts are used in a manner more similar to auth tokens, rather than unique keys. - -The login process is as follows: - -#### 1) Request a name from the user - -The first step of the process is to request from the user the ENS name that points to their identity. If the user doesn’t have a login set up, the app should–if they have an integrated identity manager–provide an option to provide a subdomain or a name they own. - -**UX Note:** there are many ways to provide this interface, the app can ask if they want to signup/login before hand or simply directly ask them to type the name. Note that since it’s trivial to verify if a username exists, your app should adapt to it gracefully and not require the user to type their name twice. If they ask to signup and provide a name that exists then ask them if they want to login using that name, or similarly if they ask to connect to an existing name but type a non-existent name show them a nice alert and ask them if they want to create that name now. Don’t force them to type the same name twice in two different fields. - -#### 2.a) Create a new identity - -If the user doesn’t have an identity, the app should provide the option to create one for them. Each app must have one or more domains they control which they can create immediate subdomains on demand. The app therefore will make these actions on the background: - -1. Generate a private key which it will keep saved locally on the device or browser, the safest way possible. -2. Create (or set up) an identity contract which supports both ERC720 and ERC1077 -3. Register the private key created on step 1 as the *only* admin key of the contract (the app must not add any app-controlled key, except as recovery option - see 5) -4. Register the requested subdomain and transfer its ownership to the contract (while the app controls the main domain and may keep the option to reassign them at will, the ownership of the subdomain itself should belong to the identity, therefore allowing them to transfer it) -5. (Optionally) Register a recovery method on the contract, which allows the user to regain access to the contract in case the main key is lost. - -All those steps can be designed to be set up in a single ethereum transaction. Since this step is not free, the app reserves the right to charge for registering users, or require the user to be verified in a sybil resistant manner of the app’s choosing (captcha, device ID registration, proof of work, etc) - -The user shouldn’t be forced to wait for transaction confirmation times. Instead, have an indicator somewhere on the app the shows the progress and then allow the user to interact with your app normally. It’s unlikely that they’ll need the identity in the first few minutes and if something goes wrong (username gets registered at the same time), you can then ask the user for an action. - -**Implementation note:** in order to save gas, some of these steps can be done in advance. The app can automatically deploy a small number of contracts when the gas price is low, and set up all their main variables to be 0xFFFFFF...FFFFF. These should be considered ‘vacant’ and when the user registers one, they will get a gas discount for freeing up space on the chain. This has the added benefit of allowing the user a choice in contract address/icon. - -#### 2.b) Connect to an existing identity - -If the user wants to connect with an existing identity, then the first thing the app needs to understand is what level of privilege it’s going to ask for: - -**Manager** the higher level, allows the key to initiate or sign transactions that change the identity itself, like adding or removing keys. An app should only require this level if it integrates an identity manager. Depending on how the identity is set up, it might require signature from more keys before these transactions can be deployed. - -**Action** this level allows the key to initiate or sign transactions on address other than itself. It can move funds, ether, assets etc. An app should only require this level of privilege if it’s a general purpose wallet or browser for sending ethereum transactions. Depending on how the identity is set up, it might require signature from more keys before these transactions can be deployed. - -**Encryption** the lower level has no right to initiate any transactions, but it can be used to represent the user in specific instances or off-chain signed messages. It’s the ideal level of privilege for games, chat or social media apps, as they can be used to sign moves, send messages, etc. If a game requires actual funds (say, to start a game with funds in stake) then it should still use the encryption level, and then require the main wallet/browser of the user to sign messages using the ethereum URI standard. - -Once the desired level is known, the app must take these steps: - -1. **Generate a private key** which it will keep saved locally on the device or browser, the safest way possible. -2. **Query ens** to figure the existing address of the identity -3. **Generate the bytecode** for a transaction calling the function `addKey(PUBLICKEY,LEVEL)`. -4. **Broadcast a transaction request on a whisper channel** or some other decentralized network of peers. Details on this step require further discussions -1. **If web3 is available** then attempt calling web3.eth.sendTransaction. This can be automatic or prompted by user action. -1. **Attempt calling a URI** if the app supports [URL format for transaction requests EIP](./eip-681.md) then attempt calling this. This can be automatic or prompted by user action. -1. **Show a QR code**: with an EIP681 formatted URL. That QR code can be clickable to attempt to retry the other options, but it should be done last: if step 1 works, the user should receive a notification on their compatible device and won't need to use the QR code. - -Here's an example of a EIP681 compatible address to add a public key generated locally in the app: - -`ethereum:bob.example.eth?function=addKey(address='0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',uint=1)` - -If adding the new key requires multiple signatures, or if the app receiving that request exclusiveky deals with executable signed messages and has no ether on itself, then it should follow the steps in the next section on how to request transactions. - -As before, the user shouldn’t be forced to wait for transaction confirmation times. Instead, have an indicator somewhere on the app the shows the progress and then allow the user to interact with your app normally. - - - -#### 3) Request transactions - -After step 2, the end result should be that your app should have the identity address of the user, their main ens name and a private key, whose public account is listed on the identity as one of their keys, with roles being either manager, action or encryption. Now it can start using that information to sign and execute transactions. - -**Not all transactions need to be on chain**, actually most common uses of signed messages should be off chain. If you have a chat app, for instance, you can use the local key for signing messages and sending it to the other parties, and they can just query the identity contract to see if that key actually comes from the user. If you have a game with funds at stake, only the first transaction moving funds and setting up the initial game needs to be executed by the identity: at each turn the players can sign a hash of the current state of the board and at the end, the last two plays can be used to determine the winner. Notice that keys can be revoked at any time, so your app should take that in consideration, for instance saving all keys at the start of the game. Keys that only need this lower level of privilege, should be set at level 4 (encryption). - -Once you decided you actually need an on-chain transaction, follow these steps: - -1. **Figure out the TO, FROM, VALUE and DATA**. These are the basics of any ethereum transaction. `from` is the compatible contract you want the transaction to be deployed from. -2. **Check the privilege level you need:** if the `to` and `from` fields are the same contract, ie, if the transaction requires the identity to act upon itself (for instance, when adding or removing a key) then you need level 1 (management), otherwise it's 2 (action). Verify if the key your app owns correspond to the required level. -3. **Verify how many keys are required** by calling `requiredSignatures(uint level)` on the target contract -4. **Figure out gasLimit**: Estimate the gas cost of the desired transaction, and add a margin (recommended: add 100k gas) -5. **Figure out gasToken and gasPrice**: Check the current gas price considering network congestions and the market price of the token the user is going to pay with. Leave gasToken as 0 for ether. Leave gasPrice as 0 if you are deploying it yourself and subsidizing the costs elsewhere. -6. **Sign an executable signed transaction** by following that standard. - -After having all the signed executable message, we need to deploy it to the chain. If the transaction only requires a single signature, then the app provider can deploy it themselves. Send the transaction to the `from` address and attempt to call the function `executeSigned`, using the parameters and signature you just collected. - -If the transaction need to collect more signatures or the app doesn't have a deployable server, the app should follow these steps: - -1. **Broadcast the transaction on a whisper channel** or some other decentralized network of peers. Details on this step require further discussions -2. **If web3 is available** then attempt calling web3.eth.personal_sign. This can be automatic or prompted by user action. -3. **Show a QR code**: with the signed transaction and the new data to be signed. That QR code can be clickable to attempt to retry the other options, but it should be done last: if step 1 works, the user should receive a notification on their compatible device and won't need to use the QR code. - -The goal is to keep broadcasting signatures via whisper until a node that is willing to deploy them is able to collect all messages. - -Once you've followed the above steps, watch the transaction pool to any transaction to that address and then take the user to your app. Once you seen the desired transaction, you can stop showing the QRcode and proceed with the app, while keeping some indication that the transaction is in progress. Subscribe to the event `ExecutedSigned` of the desired contract: once you see the transaction with the nonce, you can call it a success. If you see a different transaction with the same or higher nonce (or timestamp) then you consider the transaction permanently failed and restart the process. - - -### Implementation - -No working examples of this implementation exists, but many developers have expressed interest in adopting it. This section will be edited in the future to reflect that. - -### Conclusion and future improvements - -This scheme would allow much more lighter apps, that don't require to hold ether, and can keep unlocked private keys on the device to be able to send messages and play games without requesting user prompt every time. More work is needed to standardize common decentralized messaging protocols as well as open source tools for deployment nodes, in order to create a decentralized and reliable layer for message deployment. - -### References - -* [Universal Logins talk at UX Unconf, Toronto](https://www.youtube.com/watch?v=qF2lhJzngto) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1078.md diff --git a/EIPS/eip-1080.md b/EIPS/eip-1080.md index d704fc840655ea..29856c92ef01a1 100644 --- a/EIPS/eip-1080.md +++ b/EIPS/eip-1080.md @@ -1,184 +1 @@ ---- -eip: 1080 -title: Recoverable Token -author: Bradley Leatherwood -discussions-to: https://ethereum-magicians.org/t/erc-1080-recoverabletoken-standard/364 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-05-02 ---- - -## Simple Summary - -A standard interface for tokens that support chargebacks, theft prevention, and lost & found resolutions. - -## Abstract - -The following standard allows for the implementation of a standard API for tokens extending ERC-20 or ERC-791. This standard provides basic functionality to recover stolen or lost accounts, as well as provide for the chargeback of tokens. - -## Motivation - -To mitigate the effects of reasonably provable token or asset loss or theft and to help resolve other conflicts. Ethereum's protocol should not be modified because of loss, theft, or conflicts, but it is possible to solve these problems in the smart contract layer. - -## Specification - -## RecoverableToken - -### Methods - -#### claimLost - -Reports the `lostAccount` address as being lost. MUST trigger the `AccountClaimedLost` event. - -After the time configured in `getLostAccountRecoveryTimeInMinutes` the implementer MUST provide a mechanism for determining the correct owner of the tokens held and moving the tokens to a new account. - -Account recoveries must trigger the `AccountRecovered` event. - -``` js -function claimLost(address lostAccount) returns (bool success) -``` - -#### cancelLostClaim - -Reports the `msg.sender`'s account as being not being lost. MUST trigger the `AccountClaimedLostCanceled` event. - -MUST fail if an account recovery process has already begun. - -Otherwise, this method MUST stop a dispute from being started to recover funds. - -``` js -function claimLost() returns (bool success) -``` - -#### reportStolen - -Reports the current address as being stolen. MUST trigger the `AccountFrozen` event. -Successful calls MUST result in the `msg.sender`'s tokens being frozen. - -The implementer MUST provide a mechanism for determining the correct owner of the tokens held and moving the tokens to a new account. - -Account recoveries must trigger the `AccountRecovered` event. - -``` js -function reportStolen() returns (bool success) -``` - -#### chargeback - -Requests a reversal of transfer on behalf of `msg.sender`. - -The implementer MUST provide a mechanism for determining the correct owner of the tokens disputed and moving the tokens to the correct account. - -MUST comply with sender's chargeback window as value configured by `setPendingTransferTimeInMinutes`. - -``` js -function chargeback(uint256 pendingTransferNumber) returns (bool success) -``` - -#### getPendingTransferTimeInMinutes - -Get the time an account has to chargeback a transfer. - -``` js -function getPendingTransferTime(address account) view returns (uint256 minutes) -``` - -#### setPendingTransferTimeInMinutes - -Sets the time `msg.sender`'s account has to chargeback a transfer. - -MUST NOT change the time if the account has any pending transfers. - -``` js -function setPendingTransferTime(uint256 minutes) returns (bool success) -``` - -#### getLostAccountRecoveryTimeInMinutes - -Get the time account has to wait before a lost account dispute can start. - -``` js -function getLostAccountRecoveryTimeInMinutes(address account) view returns (uint256 minutes) -``` - -#### setLostAccountRecoveryTimeInMinutes - -Sets the time `msg.sender`'s account has to sit before a lost account dispute can start. - -MUST NOT change the time if the account has open disputes. - -``` js -function setLostAccountRecoveryTimeInMinutes(uint256 minutes) returns (bool success) -``` - -### Events - -#### AccountRecovered - -The recovery of an account that was lost or stolen. - -``` js -event AccountClaimedLost(address indexed account, address indexed newAccount) -``` - -#### AccountClaimedLostCanceled - -An account claimed as being lost. - -``` js -event AccountClaimedLost(address indexed account) -``` - -#### AccountClaimedLost - -An account claimed as being lost. - -``` js -event AccountClaimedLost(address indexed account) -``` - -#### PendingTransfer - -A record of a transfer pending. - -``` js -event PendingTransfer(address indexed from, address indexed to, uint256 value, uint256 pendingTransferNumber) -``` - -#### ChargebackRequested - -A record of a chargeback being requested. - -``` js -event ChargebackRequested(address indexed from, address indexed to, uint256 value, uint256 pendingTransferNumber) -``` - -#### Chargeback - -A record of a transfer being reversed. - -``` js -event Chargeback(address indexed from, address indexed to, uint256 value, uint256 indexed pendingTransferNumber) -``` - -#### AccountFrozen - -A record of an account being frozen. MUST trigger when an account is frozen. - -``` js -event AccountFrozen(address indexed reported) -``` - -## Rationale - -* A recoverable token standard can provide configurable safety for users or contracts who desire this safety. -* Implementations of this standard will give users the ability to select a dispute resolution process on an opt-in basis and benefit the community by decreasing the necessity of consideration of token recovery actions. - - -## Implementation - -Pending. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1080.md diff --git a/EIPS/eip-1081.md b/EIPS/eip-1081.md index f73b2c7011410b..dfe55f9b999fc4 100644 --- a/EIPS/eip-1081.md +++ b/EIPS/eip-1081.md @@ -1,121 +1 @@ ---- -eip: 1081 -title: Standard Bounties -author: Mark Beylin , Kevin Owocki , Ricardo Guilherme Schmidt (@3esmit) -discussions-to: https://gitter.im/bounties-network/Lobby -status: Stagnant -type: Standards Track -category: ERC -created: 2018-05-14 -requires: 20 ---- - -## Simple Summary -A standard contract and interface for issuing bounties on Ethereum, usable for any type of task, paying in any ERC20 token or in ETH. - -## Abstract -In order to encourage cross-platform interoperability of bounties on Ethereum, and for easier reputational tracking, StandardBounties can facilitate the administration of funds in exchange for deliverables corresponding to a completed task, in a publicly auditable and immutable fashion. - -## Motivation -In the absence of a standard for bounties on Ethereum, it would be difficult for platforms to collaborate and share the bounties which users create (thereby recreating the walled gardens which currently exist on Web2.0 task outsourcing platforms). A standardization of these interactions across task types also makes it far easier to track various reputational metrics (such as how frequently you pay for completed submissions, or how frequently your work gets accepted). - -## Specification -After studying bounties as they've existed for thousands of years (and after implementing and processing over 300 of them on main-net in beta), we've discovered that there are 3 core steps to every bounty: -- a bounty is **issued**: an `issuer` specifies the requirements for the task, describing the desired outcome, and how much they would be willing to pay for the completion of that task (denoted in one or several tokens). -- a bounty is **fulfilled**: a bounty `fulfiller` may see the bounty, complete the task, and produce a deliverable which is itself the desired outcome of the task, or simply a record that it was completed. Hashes of these deliverables should be stored immutably on-chain, to serve as proof after the fact. -- a fulfillment is **accepted**: a bounty `issuer` or `arbiter` may select one or more submissions to be accepted, thereby releasing payment to the bounty fulfiller(s), and transferring ownership over the given deliverable to the `issuer`. - -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. - - `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 -- `changeArbiter(address _arbiter)`: This may be called by the `issuer` to change to a new `arbiter` if need be -- `changeData(string _data)`: This may be called by the `issuer` to change just the `data` -- `changeDeadline(uint _deadline)`: This may be called by the `issuer` to change just the `deadline` - -Optional Functions: -- `acceptAndFulfill(address[] _fulfillers, uint[] _numerators, uint _denomenator, string _data, StandardToken[] _payoutTokens, uint[] _tokenAmounts)`: During the course of the development of this standard, we discovered the desire for fulfillers to avoid paying gas fees on their own, entrusting the bounty's `issuer` to make the submission for them, and at the same time accept it. This is useful since it still immutably stores the exchange of tokens for completed work, but avoids the need for new bounty fulfillers to have any ETH to pay for gas costs in advance of their earnings. -- `changeMasterCopy(StandardBounty _masterCopy)`: For `issuer`s to be able to change the masterCopy which their proxy contract relies on, if the proxy design pattern is being employed. -- `refundableContribute(uint[] _amounts, StandardToken[] _tokens)`: While non-refundable contributions may be sent to a bounty simply by transferring those tokens to the address where it resides, one may also desire to contribute to a bounty with the option to refund their contribution, should the bounty never receive a correct submission which is paid out. -`refundContribution(uint _contributionId)`: If a bounty hasn't yet paid out to any correct submissions and is past it's deadline, those individuals who employed the `refundableContribute` function may retrieve their funds from the contract. - -**Schemas** -Persona Schema: -``` -{ - name: // optional - A string representing the name of the persona - email: // optional - A string representing the preferred contact email of the persona - githubUsername: // optional - A string representing the github username of the persona - address: // required - A string web3 address of the persona -} -``` -Bounty issuance `data` Schema: -``` -{ - payload: { - title: // A string representing the title of the bounty - description: // A string representing the description of the bounty, including all requirements - issuer: { - // persona for the issuer of the bounty - }, - funders:[ - // array of personas of those who funded the issue. - ], - categories: // an array of strings, representing the categories of tasks which are being requested - tags: // an array of tags, representing various attributes of the bounty - created: // the timestamp in seconds when the bounty was created - tokenSymbol: // the symbol for the token which the bounty pays out - tokenAddress: // the address for the token which the bounty pays out (0x0 if ETH) - - // ------- add optional fields here ------- - sourceFileName: // A string representing the name of the file - sourceFileHash: // The IPFS hash of the file associated with the bounty - sourceDirectoryHash: // The IPFS hash of the directory which can be used to access the file - webReferenceURL: // The link to a relevant web reference (ie github issue) - }, - meta: { - platform: // a string representing the original posting platform (ie 'gitcoin') - schemaVersion: // a string representing the version number (ie '0.1') - schemaName: // a string representing the name of the schema (ie 'standardSchema' or 'gitcoinSchema') - } -} -``` -Bounty `fulfillment` data Schema: - -``` -{ - payload: { - description: // A string representing the description of the fulfillment, and any necessary links to works - sourceFileName: // A string representing the name of the file being submitted - sourceFileHash: // A string representing the IPFS hash of the file being submitted - sourceDirectoryHash: // A string representing the IPFS hash of the directory which holds the file being submitted - fulfillers: { - // personas for the individuals whose work is being submitted - } - - // ------- add optional fields here ------- - }, - meta: { - platform: // a string representing the original posting platform (ie 'gitcoin') - schemaVersion: // a string representing the version number (ie '0.1') - schemaName: // a string representing the name of the schema (ie 'standardSchema' or 'gitcoinSchema') - } -} -``` -## Rationale -The development of this standard began a year ago, with the goal of encouraging interoperability among bounty implementations on Ethereum. The initial version had significantly more restrictions: a bounty's `data` could not be changed after issuance (it seemed unfair for bounty `issuer`s to change the requirements after work is underway), and the bounty payout could not be changed (all funds needed to be deposited in the bounty contract before it could accept submissions). - -The initial version was also far less extensible, and only allowed for fixed payments to a given set of fulfillments. This new version makes it possible for funds to be split among several correct submissions, for submissions to be shared among several contributors, and for payouts to not only be in a single token as before, but in as many tokens as the `issuer` of the bounty desires. These design decisions were made after the 8+ months which Gitcoin, the Bounties Network, and Status Open Bounty have been live and meaningfully facilitating bounties for repositories in the Web3.0 ecosystem. - -## Test Cases -Tests for our implementation can be found here: https://github.com/Bounties-Network/StandardBounties/tree/develop/test - -## Implementation -A reference implementation can be found here: https://github.com/Bounties-Network/StandardBounties/blob/develop/contracts/StandardBounty.sol -**Although this code has been tested, it has not yet been audited or bug-bountied, so we cannot make any assertions about it's correctness, nor can we presently encourage it's use to hold funds on the Ethereum mainnet.** - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1081.md diff --git a/EIPS/eip-1123.md b/EIPS/eip-1123.md index 06ca527c46d66b..e29b1ff0683149 100644 --- a/EIPS/eip-1123.md +++ b/EIPS/eip-1123.md @@ -1,1881 +1 @@ ---- -eip: 1123 -title: Revised Ethereum Smart Contract Packaging Standard -author: g. nicholas d’andrea (@gnidan), Piper Merriam (@pipermerriam), Nick Gheorghita (@njgheorghita), Danny Ryan (@djrtwo) -discussions-to: https://github.com/ethereum/EIPs/issues/1123 -status: Withdrawn -type: Standards Track -category: ERC -created: 2018-06-01 ---- - -This ERC has been abandoned in favor of the EthPM V3 smart contract packaging standard defined in [ERC-2678](./eip-2678.md) - -Simple Summary -============== - -A data format describing a smart contract software package. - - -Abstract -========== - -This EIP defines a data format for *package manifest* documents, -representing a package of one or more smart contracts, optionally -including source code and any/all deployed instances across multiple -networks. Package manifests are minified JSON objects, to be distributed -via content addressable storage networks, such as IPFS. - -This document presents a natural language description of a formal -specification for version **2** of this format. - - -Motivation -========== - -This standard aims to encourage the Ethereum development ecosystem -towards software best practices around code reuse. By defining an open, -community-driven package data format standard, this effort seeks to -provide support for package management tools development by offering a -general-purpose solution that has been designed with observed common -practices in mind. - -As version 2 of this specification, this standard seeks to address a -number of areas of improvement found for the previous version (defined -in -[EIP-190](./eip-190.md)). -This version: - -- Generalizes storage URIs to represent any content addressable URI - scheme, not only IPFS. - -- Renames *release lockfile* to *package manifest*. - -- Adds support for languages other than Solidity by generalizing the - compiler information format. - -- Redefines link references to be more flexible, to represent - arbitrary gaps in bytecode (besides only addresses), in a more - straightforward way. - -- Forces format strictness, requiring that package manifests contain - no extraneous whitespace, and sort object keys in alphabetical - order, to prevent hash mismatches. - - -
- -Specification -============= - -This document defines the specification for an EthPM package manifest. A -package manifest provides metadata about a [Package](#term-package), and -in most cases should provide sufficient information about the packaged -contracts and its dependencies to do bytecode verification of its -contracts. - -> **Note** -> -> A [hosted -> version](https://ethpm.github.io/ethpm-spec) of this -> specification is available via GitHub Pages. This EIP and the hosted -> HTML document were both autogenerated from the same documentation -> source. - - -Guiding Principles ------------------- - -This specification makes the following assumptions about the document -lifecycle. - -1. Package manifests are intended to be generated programmatically by - package management software as part of the release process. - -2. Package manifests will be consumed by package managers during tasks - like installing package dependencies or building and deploying new - releases. - -3. Package manifests will typically **not** be stored alongside the - source, but rather by package registries *or* referenced by package - registries and stored in something akin to IPFS. - - -Conventions ------------ - - -### RFC2119 - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, -“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this -document are to be interpreted as described in RFC 2119. - -- - - -### Prefixed vs Unprefixed - -A [prefixed](#term-prefixed) hexadecimal value begins with `0x`. -[Unprefixed](#term-unprefixed) values have no prefix. Unless otherwise -specified, all hexadecimal values **should** be represented with the -`0x` prefix. - - ---- - - - - - - - - - - -

Prefixed

0xdeadbeef

Unprefixed

deadbeef

- - -Document Format ---------------- - -The canonical format is a single JSON object. Packages **must** conform -to the following serialization rules. - -- The document **must** be tightly packed, meaning no linebreaks or - extra whitespace. - -- The keys in all objects must be sorted alphabetically. - -- Duplicate keys in the same object are invalid. - -- The document **must** use - [UTF-8](https://en.wikipedia.org/wiki/UTF-8) - encoding. - -- The document **must** not have a trailing newline. - - -Document Specification ----------------------- - -The following fields are defined for the package. Custom fields **may** -be included. Custom fields **should** be prefixed with `x-` to prevent -name collisions with future versions of the specification. - - ---- - - - - - - - - - - -

See Also

Formalized (JSON-Schema) version of this specification: package.spec.json

Jump To

Definitions

- - -
- -### EthPM Manifest Version: `manifest_version` - -The `manifest_version` field defines the specification version that this -document conforms to. Packages **must** include this field. - - ---- - - - - - - - - - - - - - - - - - - -

Required

Yes

Key

manifest_version

Type

String

Allowed Values

2

- - -
- -### Package Name: `package_name` - -The `package_name` field defines a human readable name for this package. -Packages **must** include this field. Package names **must** begin with -a lowercase letter and be comprised of only lowercase letters, numeric -characters, and the dash character `-`. Package names **must** not -exceed 214 characters in length. - - ---- - - - - - - - - - - - - - - - - - - -

Required

Yes

Key

package_name

Type

String

Format

must match the regular expression ^[a-zA-Z][a-zA-Z0-9_]{0,255}$

- - -### Package Meta: `meta` - -The `meta` field defines a location for metadata about the package which -is not integral in nature for package installation, but may be important -or convenient to have on-hand for other reasons. This field **should** -be included in all Packages. - - ---- - - - - - - - - - - - - - - -

Required

No

Key

meta

Type

Package Meta Object

- - -### Version: `version` - -The `version` field declares the version number of this release. This -value **must** be included in all Packages. This value **should** -conform to the [semver](https://semver.org/) version -numbering specification. - - ---- - - - - - - - - - - - - - - -

Required

Yes

Key

version

Type

String

- - -### Sources: `sources` - -The `sources` field defines a source tree that **should** comprise the -full source tree necessary to recompile the contracts contained in this -release. Sources are declared in a key/value mapping. - - ---- - - - - - - - - - - - - - - -

Key

sources

Type

Object (String: String)

Format

See Below.

- - -#### Format - -Keys **must** be relative filesystem paths beginning with a `./`. - -Paths **must** resolve to a path that is within the current working -directory. - -Values **must** conform to *one of* the following formats. - -- Source string. - -- [Content Addressable URI](#term-content-addressable-uri). - -When the value is a source string the key should be interpreted as a -file path. - -- If the resulting document is a directory the key should be - interpreted as a directory path. - -- If the resulting document is a file the key should be interpreted as - a file path. - - -### Contract Types: `contract_types` - -The `contract_types` field holds the [Contract -Types](#term-contract-type) which have been included in this release. -[Packages](#term-package) **should** only include contract types that -can be found in the source files for this package. Packages **should -not** include contract types from dependencies. Packages **should not** -include abstract contracts in the contract types section of a release. - - ---- - - - - - - - - - - - - - - -

Key

contract_types

Type

Object (String: Contract Type Object)

Format

Keys must be valid Contract Aliases.

-

Values must conform to the Contract Type Object definition.

- - -### Deployments: `deployments` - -The `deployments` field holds the information for the chains on which -this release has [Contract Instances](#term-contract-instance) as well -as the [Contract Types](#term-contract-type) and other deployment -details for those deployed contract instances. The set of chains defined -by the `*BIP122 URI <#bip122-uris>*` keys for this object **must** be -unique. - - ---- - - - - - - - - - - - - - - -

Key

deployments

Type

Object (String: Object(String: Contract Instance Object))

Format

See Below.

- - -#### Format - -Keys **must** be a valid BIP122 URI chain definition. - -Values **must** be objects which conform to the following format. - -- Keys **must** be valid [Contract Instance - Names](#term-contract-instance-name). - -- Values **must** be a valid [Contract Instance - Object](#contract-instance-object). - - -### Build Dependencies: `build_dependencies` - -The `build_dependencies` field defines a key/value mapping of Ethereum -[Packages](#term-package) that this project depends on. - - ---- - - - - - - - - - - - - - - - - - - -

Required

No

Key

build_dependencies

Type

Object (String: String)

Format

Keys must be valid package names matching the regular expression [a-z][-a-z0-9]{0,213}.

-

Values must be valid IPFS URIs which resolve to a valid package.

- - -Definitions ------------ - -Definitions for different objects used within the Package. All objects -allow custom fields to be included. Custom fields **should** be prefixed -with `x-` to prevent name collisions with future versions of the -specification. - - - - -### The *Link Reference* Object - -A [Link Reference](#term-link-reference) object has the following -key/value pairs. All link references are assumed to be associated with -some corresponding [Bytecode](#term-bytecode). - - -#### Offsets: `offsets` - -The `offsets` field is an array of integers, corresponding to each of -the start positions where the link reference appears in the bytecode. -Locations are 0-indexed from the beginning of the bytes representation -of the corresponding bytecode. This field is invalid if it references a -position that is beyond the end of the bytecode. - - ---- - - - - - - - - - - -

Required

Yes

Type

Array

- - -#### Length: `length` - -The `length` field is an integer which defines the length in bytes of -the link reference. This field is invalid if the end of the defined link -reference exceeds the end of the bytecode. - - ---- - - - - - - - - - - -

Required

Yes

Type

Integer

- - -#### Name: `name` - -The `name` field is a string which **must** be a valid -[Identifier](#term-identifier). Any link references which **should** be -linked with the same link value **should** be given the same name. - - ---- - - - - - - - - - - - - - - -

Required

No

Type

String

Format

must conform to the Identifier format.

- - - - -### The *Link Value* Object - -Describes a single [Link Value](#term-link-value). - -A **Link Value object** is defined to have the following key/value -pairs. - - -
- -#### Offsets: `offsets` - -The `offsets` field defines the locations within the corresponding -bytecode where the `value` for this link value was written. These -locations are 0-indexed from the beginning of the bytes representation -of the corresponding bytecode. - - ---- - - - - - - - - - - - - - - -

Required

Yes

Type

Integer

Format

See Below.

- -**Format** - -Array of integers, where each integer **must** conform to all of the -following. - -- greater than or equal to zero - -- strictly less than the length of the unprefixed hexadecimal - representation of the corresponding bytecode. - - -#### Type: `type` - -The `type` field defines the `value` type for determining what is -encoded when [linking](#term-linking) the corresponding bytecode. - - ---- - - - - - - - - - - - - - - -

Required

Yes

Type

String

Allowed Values

"literal" for bytecode literals

-

"reference" for named references to a particular Contract Instance

- - -#### Value: `value` - -The `value` field defines the value which should be written when -[linking](#term-linking) the corresponding bytecode. - - ---- - - - - - - - - - - - - - - -

Required

Yes

Type

String

Format

Determined based on type, see below.

- -**Format** - -For static value *literals* (e.g. address), value **must** be a *byte -string* - -To reference the address of a [Contract -Instance](#term-contract-instance) from the current package the value -should be the name of that contract instance. - -- This value **must** be a valid contract instance name. - -- The chain definition under which the contract instance that this - link value belongs to must contain this value within its keys. - -- This value **may not** reference the same contract instance that - this link value belongs to. - -To reference a contract instance from a [Package](#term-package) from -somewhere within the dependency tree the value is constructed as -follows. - -- Let `[p1, p2, .. pn]` define a path down the dependency tree. - -- Each of `p1, p2, pn` **must** be valid package names. - -- `p1` **must** be present in keys of the `build_dependencies` for the - current package. - -- For every `pn` where `n > 1`, `pn` **must** be present in the keys - of the `build_dependencies` of the package for `pn-1`. - -- The value is represented by the string - `::<...>::` where all of ``, - ``, `` are valid package names and `` is - a valid [Contract Name](#term-contract-name). - -- The `` value **must** be a valid [Contract - Instance Name](#term-contract-instance-name). - -- Within the package of the dependency defined by ``, all of the - following must be satisfiable: - - - There **must** be *exactly* one chain defined under the - `deployments` key which matches the chain definition that this - link value is nested under. - - - The `` value **must** be present in the keys - of the matching chain. - - -### The *Bytecode* Object - -A bytecode object has the following key/value pairs. - - -#### Bytecode: `bytecode` - -The `bytecode` field is a string containing the `0x` prefixed -hexadecimal representation of the bytecode. - - ---- - - - - - - - - - - - - - - -

Required

Yes

Type

String

Format

0x prefixed hexadecimal.

- - -#### Link References: `link_references` - -The `link_references` field defines the locations in the corresponding -bytecode which require [linking](#term-linking). - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Array

Format

All values must be valid Link Reference objects. See also below.

- -**Format** - -This field is considered invalid if *any* of the [Link -References](#term-link-reference) are invalid when applied to the -corresponding `bytecode` field, *or* if any of the link references -intersect. - -Intersection is defined as two link references which overlap. - - -#### Link Dependencies: `link_dependencies` - -The `link_dependencies` defines the [Link Values](#term-link-value) that -have been used to link the corresponding bytecode. - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Array

Format

All values must be valid Link Value objects. See also below.

- -**Format** - -Validation of this field includes the following: - -- Two link value objects **must not** contain any of the same values - for `offsets`. - -- Each [link value object](#link-value-object) **must** have a - corresponding [link reference object](#link-reference-object) under - the `link_references` field. - -- The length of the resolved `value` **must** be equal to the `length` - of the corresponding [Link Reference](#term-link-reference). - - -
- -### The *Package Meta* Object - -The *Package Meta* object is defined to have the following key/value -pairs. - - -#### Authors: `authors` - -The `authors` field defines a list of human readable names for the -authors of this package. Packages **may** include this field. - - ---- - - - - - - - - - - - - - - -

Required

No

Key

authors

Type

Array (String)

- - -#### License: `license` - -The `license` field declares the license under which this package is -released. This value **should** conform to the -[SPDX](https://en.wikipedia.org/wiki/Software_Package_Data_Exchange) -format. Packages **should** include this field. - - ---- - - - - - - - - - - - - - - -

Required

No

Key

license

Type

String

- - -#### Description: `description` - -The `description` field provides additional detail that may be relevant -for the package. Packages **may** include this field. - - ---- - - - - - - - - - - - - - - -

Required

No

Key

description

Type

String

- - -#### Keywords: `keywords` - -The `keywords` field provides relevant keywords related to this package. - - ---- - - - - - - - - - - - - - - -

Required

No

Key

keywords

Type

List of Strings

- - -#### Links: `links` - -The `links` field provides URIs to relevant resources associated with -this package. When possible, authors **should** use the following keys -for the following common resources. - -- `website`: Primary website for the package. - -- `documentation`: Package Documentation - -- `repository`: Location of the project source code. - - ---- - - - - - - - - - - -

Key

links

Type

Object (String: String)

- - -
- -### The *Contract Type* Object - -A *Contract Type* object is defined to have the following key/value -pairs. - - -#### Contract Name: `contract_name` - -The `contract_name` field defines the [Contract -Name](#term-contract-name) for this [Contract -Type](#term-contract-type). - - ---- - - - - - - - - - - - - - - -

Required

If the Contract Name and Contract Alias are not the same.

Type

String

Format

must be a valid Contract Name.

- - -#### Deployment Bytecode: `deployment_bytecode` - -The `deployment_bytecode` field defines the bytecode for this [Contract -Type](#term-contract-type). - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Object

Format

must conform to the Bytecode Object format.

- - -#### Runtime Bytecode: `runtime_bytecode` - -The `runtime_bytecode` field defines the unlinked `0x`-prefixed runtime -portion of [Bytecode](#term-bytecode) for this [Contract -Type](#term-contract-type). - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Object

Format

must conform to the Bytecode Object format.

- - -#### ABI: `abi` - - ---- - - - - - - - - - - - - - - -

Required

No

Type

List

Format

must conform to the Ethereum Contract ABI JSON format.

- - -#### Natspec: `natspec` - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Object

Format

The union of the UserDoc and DevDoc formats.

- - -#### Compiler: `compiler` - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Object

Format

must conform to the Compiler Information object format.

- - -
- -### The *Contract Instance* Object - -A **Contract Instance Object** represents a single deployed [Contract -Instance](#term-contract-instance) and is defined to have the following -key/value pairs. - - -#### Contract Type: `contract_type` - -The `contract_type` field defines the [Contract -Type](#term-contract-type) for this [Contract -Instance](#term-contract-instance). This can reference any of the -contract types included in this [Package](#term-package) *or* any of the -contract types found in any of the package dependencies from the -`build_dependencies` section of the [Package -Manifest](#term-package-manifest). - - ---- - - - - - - - - - - - - - - -

Required

Yes

Type

String

Format

See Below.

- -**Format** - -Values for this field **must** conform to *one of* the two formats -herein. - -To reference a contract type from this Package, use the format -``. - -- The `` value **must** be a valid [Contract - Alias](#term-contract-alias). - -- The value **must** be present in the keys of the `contract_types` - section of this Package. - -To reference a contract type from a dependency, use the format -`:`. - -- The `` value **must** be present in the keys of the - `build_dependencies` of this Package. - -- The `` value **must** be be a valid [Contract - Alias](#term-contract-alias). - -- The resolved package for `` must contain the - `` value in the keys of the `contract_types` - section. - - -#### Address: `address` - -The `address` field defines the [Address](#term-address) of the -[Contract Instance](#term-contract-instance). - - ---- - - - - - - - - - - - - - - -

Required

Yes

Type

String

Format

Hex encoded 0x prefixed Ethereum address matching the regular expression 0x[0-9a-fA-F]{40}.

- - -#### Transaction: `transaction` - -The `transaction` field defines the transaction hash in which this -[Contract Instance](#term-contract-instance) was created. - - ---- - - - - - - - - - - - - - - -

Required

No

Type

String

Format

0x prefixed hex encoded transaction hash.

- - -#### Block: `block` - -The `block` field defines the block hash in which this the transaction -which created this *contract instance* was mined. - - ---- - - - - - - - - - - - - - - -

Required

No

Type

String

Format

0x prefixed hex encoded block hash.

- - -
- -#### Runtime Bytecode: `runtime_bytecode` - -The `runtime_bytecode` field defines the runtime portion of bytecode for -this [Contract Instance](#term-contract-instance). When present, the -value from this field supersedes the `runtime_bytecode` from the -[Contract Type](#term-contract-type) for this [Contract -Instance](#term-contract-instance). - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Object

Format

must conform to the Bytecode Object format.

- -Every entry in the `link_references` for this bytecode **must** have a -corresponding entry in the `link_dependencies` section. - - -#### Compiler: `compiler` - -The `compiler` field defines the compiler information that was used -during compilation of this [Contract Instance](#term-contract-instance). -This field **should** be present in all [Contract -Types](#term-contract-type) which include `bytecode` or -`runtime_bytecode`. - - ---- - - - - - - - - - - - - - - -

Required

No

Type

Object

Format

must conform to the Compiler Information Object format.

- - -
- -### The *Compiler Information* Object - -The `compiler` field defines the compiler information that was used -during compilation of this [Contract Instance](#term-contract-instance). -This field **should** be present in all contract instances that locally -declare `runtime_bytecode`. - -A *Compiler Information* object is defined to have the following -key/value pairs. - - -#### Name `name` - -The `name` field defines which compiler was used in compilation. - - ---- - - - - - - - - - - - - - - -

Required

Yes

Key

name

Type

String

- - -#### Version: `version` - -The `version` field defines the version of the compiler. The field -**should** be OS agnostic (OS not included in the string) and take the -form of either the stable version in -[semver](https://semver.org/) format or if built on a -nightly should be denoted in the form of `-` ex: -`0.4.8-commit.60cc1668`. - - ---- - - - - - - - - - - - - - - -

Required

Yes

Key

version

Type

String

- - -#### Settings: `settings` - -The `settings` field defines any settings or configuration that was used -in compilation. For the `"solc"` compiler, this **should** conform to -the [Compiler Input and Output -Description](https://solidity.readthedocs.io/en/latest/using-the-compiler.html#compiler-input-and-output-json-description). - - ---- - - - - - - - - - - - - - - -

Required

No

Key

settings

Type

Object

- - -### BIP122 URIs - -BIP122 URIs are used to define a blockchain via a subset of the -[BIP-122](https://github.com/bitcoin/bips/blob/master/bip-0122.mediawiki) -spec. - - blockchain:///block/ - -The `` represents the blockhash of the first block on the -chain, and `` represents the hash of the -latest block that’s been reliably confirmed (package managers should be -free to choose their desired level of confirmations). - - -Rationale -========= - -The following use cases were considered during the creation of this -specification. - - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

owned

A package which contains contracts which are not meant to be used by themselves but rather as base contracts to provide functionality to other contracts through inheritance.

transferable

A package which has a single dependency.

standard-token

A package which contains a reusable contract.

safe-math-lib

A package which contains deployed instance of one of the package contracts.

piper-coin

A package which contains a deployed instance of a reusable contract from a dependency.

escrow

A package which contains a deployed instance of a local contract which is linked against a deployed instance of a local library.

wallet

A package with a deployed instance of a local contract which is linked against a deployed instance of a library from a dependency.

wallet-with-send

A package with a deployed instance which links against a deep dependency.

- -Each use case builds incrementally on the previous one. - -A full listing of [Use -Cases](https://ethpm.github.io/ethpm-spec/use-cases.html) -can be found on the hosted version of this specification. - - -Glossary -========== - - -
- -ABI ---- - -The JSON representation of the application binary interface. See the -official -[specification](https://solidity.readthedocs.io/en/develop/abi-spec.html) -for more information. - - -
- -Address -------- - -A public identifier for an account on a particular chain - - -
- -Bytecode --------- - -The set of EVM instructions as produced by a compiler. Unless otherwise -specified this should be assumed to be hexadecimal encoded, representing -a whole number of bytes, and [prefixed](#term-prefixed) with `0x`. - -Bytecode can either be linked or unlinked. (see -[Linking](#term-linking)) - - ---- - - - - - - - - - - -

Unlinked Bytecode

The hexadecimal representation of a contract’s EVM instructions that contains sections of code that requires linking for the contract to be functional.

-

The sections of code which are unlinked must be filled in with zero bytes.

-

Example: 0x606060405260e06000730000000000000000000000000000000000000000634d536f

Linked Bytecode

The hexadecimal representation of a contract’s EVM instructions which has had all Link References replaced with the desired Link Values.

-

Example: 0x606060405260e06000736fe36000604051602001526040518160e060020a634d536f

- - -
- -Chain Definition ----------------- - -This definition originates from [BIP122 -URI](https://github.com/bitcoin/bips/blob/master/bip-0122.mediawiki). - -A URI in the format `blockchain:///block/` - -- `chain_id` is the unprefixed hexadecimal representation of the - genesis hash for the chain. - -- `block_hash` is the unprefixed hexadecimal representation of the - hash of a block on the chain. - -A chain is considered to match a chain definition if the the genesis -block hash matches the `chain_id` and the block defined by `block_hash` -can be found on that chain. It is possible for multiple chains to match -a single URI, in which case all chains are considered valid matches - - -
- -Content Addressable URI ------------------------ - -Any URI which contains a cryptographic hash which can be used to verify -the integrity of the content found at the URI. - -The URI format is defined in RFC3986 - -It is **recommended** that tools support IPFS and Swarm. - - -
- -Contract Alias --------------- - -This is a name used to reference a specific [Contract -Type](#term-contract-type). Contract aliases **must** be unique within a -single [Package](#term-package). - -The contract alias **must** use *one of* the following naming schemes: - -- `` - -- `[]` - -The `` portion **must** be the same as the [Contract -Name](#term-contract-name) for this contract type. - -The `[]` portion **must** match the regular expression -`\[[-a-zA-Z0-9]{1,256}]`. - - -
- -Contract Instance ------------------ - -A contract instance a specific deployed version of a [Contract -Type](#term-contract-type). - -All contract instances have an [Address](#term-address) on some specific -chain. - - -
- -Contract Instance Name ----------------------- - -A name which refers to a specific [Contract -Instance](#term-contract-instance) on a specific chain from the -deployments of a single [Package](#term-package). This name **must** be -unique across all other contract instances for the given chain. The name -must conform to the regular expression `[a-zA-Z][a-zA-Z0-9_]{0,255}` - -In cases where there is a single deployed instance of a given [Contract -Type](#term-contract-type), package managers **should** use the -[Contract Alias](#term-contract-alias) for that contract type for this -name. - -In cases where there are multiple deployed instances of a given contract -type, package managers **should** use a name which provides some added -semantic information as to help differentiate the two deployed instances -in a meaningful way. - - -
- -Contract Name -------------- - -The name found in the source code that defines a specific [Contract -Type](#term-contract-type). These names **must** conform to the regular -expression `[a-zA-Z][-a-zA-Z0-9_]{0,255}`. - -There can be multiple contracts with the same contract name in a -projects source files. - - -
- -Contract Type -------------- - -Refers to a specific contract in the package source. This term can be -used to refer to an abstract contract, a normal contract, or a library. -Two contracts are of the same contract type if they have the same -bytecode. - -Example: - - contract Wallet { - ... - } - -A deployed instance of the `Wallet` contract would be of of type -`Wallet`. - - -
- -Identifier ----------- - -Refers generally to a named entity in the [Package](#term-package). - -A string matching the regular expression `[a-zA-Z][-_a-zA-Z0-9]{0,255}` - - - - -Link Reference --------------- - -A location within a contract’s bytecode which needs to be linked. A link -reference has the following properties. - - ---- - - - - - - - - - - - - - - -

offset

Defines the location within the bytecode where the link reference begins.

length

Defines the length of the reference.

name

(optional.) A string to identify the reference

- - - - -Link Value ----------- - -A link value is the value which can be inserted in place of a [Link -Reference](#term-link-reference) - - -
- -Linking -------- - -The act of replacing [Link References](#term-link-reference) with [Link -Values](#term-link-value) within some [Bytecode](#term-bytecode). - - -
- -Package -------- - -Distribution of an application’s source or compiled bytecode along with -metadata related to authorship, license, versioning, et al. - -For brevity, the term **Package** is often used metonymously to mean -[Package Manifest](#term-package-manifest). - - -
- -Package Manifest ----------------- - -A machine-readable description of a package (See -[Specification](#package-specification) for information about the format -for package manifests.) - - -
- -Prefixed --------- - -[Bytecode](#term-bytecode) string with leading `0x`. - - ---- - - - - - - -

Example

0xdeadbeef

- - -
- -Unprefixed ----------- - -Not [Prefixed](#term-prefixed). - - ---- - - - - - - -

Example

deadbeef

- - -Backwards Compatibility -======================= - -This specification supports backwards compatibility by use of the -[manifest\_version](#manifest-version) property. This -specification corresponds to version `2` as the value for that field. - - -Implementations -=============== - -This submission aims to coincide with development efforts towards -widespread implementation in commonly-used development tools. - -The following tools are known to have begun or are nearing completion of -a supporting implementation. - -- [Truffle](https://trufflesuite.com/) - -- [Populus](https://populus.readthedocs.io/en/latest/) - -- [Embark](https://embark.status.im/) - -Full support in implementation **may** require [Further -Work](#further-work), specified below. - - -Further Work -============ - -This EIP addresses only the data format for package descriptions. -Excluded from the scope of this specification are: - -- Package registry interface definition - -- Tooling integration, or how packages are stored on disk. - -These efforts **should** be considered separate, warranting future -dependent EIP submssions. - - -Acknowledgements -================ - -The authors of this document would like to thank the original authors of -[EIP-190](./eip-190.md), -[ETHPrize](http://ethprize.io/) for their funding -support, all community -[contributors](https://github.com/ethpm/ethpm-spec/graphs/contributors), -and the Ethereum community at large. - - -Copyright -========= - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1123.md diff --git a/EIPS/eip-1129.md b/EIPS/eip-1129.md index ca6acdcd754975..925e934176cdc3 100644 --- a/EIPS/eip-1129.md +++ b/EIPS/eip-1129.md @@ -1,144 +1 @@ ---- -eip: 1129 -title: Standardised DAPP announcements -author: Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/eip-sda-standardised-dapp-announcements/508?u=thunderdeliverer -status: Stagnant -type: Standards Track -category: ERC -created: 2018-05-31 ---- - -## Simple Summary -Standardisation of announcements in DAPPs and services on Ethereum network. This ERC provides proposed mechanics to increase the quality of service provided by DAPP developers and service providers, by setting a framework for announcements. Be it transitioning to a new smart contract or just freezing the service for some reason. - -## Abstract -The proposed ERC defines format on how to post announcements about the service as well as how to remove them. It also defines mechanics on posting permissions and human friendly interface. - -## Motivation -Currently there are no guidelines on how to notify the users of the service status in the DAPPs. This is especially obvious in ERC20 and it's derivates. If the service is impeded by any reason it is good practice to have some sort of guidelines on how to announce that to the user. The standardisation would also provide traceability of the service's status. - -## Specification - -### Structures - -#### Announcer - -Stores information about the announcement maker. The `allowedToPost` stores posting permissions and is used for modifiers limiting announcement posting only to authorised entities. The `name` is used for human friendly identifier of the author to be stored. - -``` js -struct Announcer{ - bool allowedToPost; - string name; -} -``` - - -#### Announcement - -Stores information about the individual announcement. The human friendly author identifier is stored in `author`. Ethereum address associated with the author is stored in `authorAddress`. The announcement itself is stored in `post`. - -``` js -struct Announcement{ - string author; - address authorAddress; - string post; -} -``` - - - -### Methods -#### the number of ammouncements - -Returns the number of announcement currently active. - -OPTIONAL - this method can be used to provide quicker information for the UI, but could also be retrieved from `numberOfMessages` variable. - -``` js -function theNumberOfAnnouncements() public constant returns(uint256 _numberOfAnnouncements) -``` - - -#### read posts - -Returns the specified announcement as well as human friendly poster identificator (name or nickname). - -``` js -function readPosts(uint256 _postNumber) public constant returns(string _author, string _post) -``` - - -#### give posting permission - -Sets posting permissions of the address `_newAnnouncer` to `_postingPrivileges` and can also be used to revoke those permissions. The `_posterName` is human friendly author identificator used in the announcement data. - -``` js -function givePostingPermission(address _newAnnouncer, bool _postingPrivileges, string _posterName) public onlyOwner returns(bool success) -``` - - -#### can post - -Checks if the entity that wants to post an announcement has the posting privilieges. - -``` js -modifier canPost{ - require(posterData[msg.sender].allowedToPost); - _; -} -``` - - -#### post announcement - -Lets user post announcements, but only if they have their posting privileges set to `true`. The announcement is sent in `_message` variable. - -``` js -function postAnnouncement(string _message) public canPost -``` - - -#### remove announcement - -Removes an announcement with `_messageNumber` announcement identifier and rearranges the mapping so there are no empty slots. The `_removalReason` is used to update users if the issue that caused the announcement is resolved or what are the next steps from the service provider / DAPP development team. - -``` js -function removeAnnouncement(uint256 _messageNumber, string _removalReason) public -``` - - - -### Events - -#### New announcement - -MUST trigger when new announcement is created. - -Every time there is a new announcement it should be advertised in this event. It holds the information about author `author` and the announcement istelf `message`. - -``` js -event NewAnnouncement(string author, string message) -``` - - -#### Removed announcement - -MUST trigger when an announcement is removed. - -Every time an announcement is removed it should be advertised in this event. It holds the information about author `author`, the announcement itself `message`, the reason for removal or explanation of the solution `reason` and the address of the entity that removed the announcement `remover`. - -``` js -event RemovedAnnouncement(string author, string message, string reason, address remover); -``` - -## Rationale -The proposed solution was designed with UX in mind . It provides mechanics that serve to present the announcements in the user friendly way. It is meant to be deployed as a Solidity smart contract on Ethereum network. - -## Test Cases -The proposed version is deployed on Ropsten testnet all of the information can be found [here](https://ropsten.etherscan.io/address/0xb04f67172b9733837e59ebaf03d277279635c8e6#readContract). - -## Implementation - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1129.md diff --git a/EIPS/eip-1132.md b/EIPS/eip-1132.md index 373a0dd4c3ffe3..2ee741c7553ee8 100644 --- a/EIPS/eip-1132.md +++ b/EIPS/eip-1132.md @@ -1,152 +1 @@ ---- -eip: 1132 -title: Extending ERC20 with token locking capability -author: nitika-goel -type: Standards Track -category: ERC -status: Stagnant -created: 2018-06-03 -discussions-to: https://github.com/ethereum/EIPs/issues/1132 ---- - -## Simple Summary - -An extension to the ERC20 standard with methods for time-locking of tokens within a contract. - -## Abstract - -This proposal provides basic functionality to time-lock tokens within an ERC20 smart contract for multiple utilities without the need of transferring tokens to an external escrow smart contract. It also allows fetching balance of locked and transferable tokens. - -Time-locking can also be achieved via staking (#900), but that requires transfer of tokens to an escrow contract / stake manager, resulting in the following six concerns: - -1. additional trust on escrow contract / stake manager -2. additional approval process for token transfer -3. increased ops costs due to gas requirements in transfers -4. tough user experience as the user needs to claim the amount back from external escrows -5. inability for the user to track their true token balance / token activity -6. inability for the user to utilize their locked tokens within the token ecosystem. - -## Motivation - -dApps often require tokens to be time-locked against transfers for letting members 1) adhere to vesting schedules and 2) show skin in the game to comply with the underlying business process. I realized this need while building Nexus Mutual and GovBlocks. - -In [Nexus Mutual](https://nexusmutual.io), claim assessors are required to lock their tokens before passing a vote for claims assessment. This is important as it ensures assessors’ skin in the game. The need here was that once a claim assessor locks his tokens for ‘n’ days, he should be able to cast multiple votes during that period of ‘n’ days, which is not feasible with staking mechanism. There are other scenarios like skills/identity verification or participation in gamified token curated registries where time-locked tokens are required as well. - -In [GovBlocks](https://govblocks.io), I wanted to allow dApps to lock member tokens for governance, while still allowing members to use those locked tokens for other activities within the dApp business. This is also the case with DGX governance model where they’ve proposed quarterly token locking for participation in governance activities of DGX. - -In addition to locking functionality, I have proposed a `Lock()` and `Unlock()` event, just like the `Transfer()` event , to track token lock and unlock status. From token holder’s perspective, it gets tough to manage token holdings if certain tokens are transferred to another account for locking, because whenever `balanceOf()` queries are triggered on token holder’s account – the result does not include locked tokens. A `totalBalanceOf()` function intends to solve this problem. - -The intention with this proposal is to enhance the ERC20 standard with token-locking capability so that dApps can time-lock tokens of the members without having to transfer tokens to an escrow / stake manager and at the same time allow members to use the locked tokens for multiple utilities. - -## Specification - -I’ve extended the ERC20 interface with the following enhancements: - -### Locking of tokens -```solidity -/** - * @dev Locks a specified amount of tokens against an address, - * for a specified reason and time - * @param _reason The reason to lock tokens - * @param _amount Number of tokens to be locked - * @param _time Lock time in seconds - */ -function lock(bytes32 _reason, uint256 _amount, uint256 _time) public returns (bool) -``` - -### Fetching number of tokens locked under each utility -```solidity -/** - * @dev Returns tokens locked for a specified address for a - * specified reason - * - * @param _of The address whose tokens are locked - * @param _reason The reason to query the lock tokens for - */ - tokensLocked(address _of, bytes32 _reason) view returns (uint256 amount) -``` - -### Fetching number of tokens locked under each utility at a future timestamp -```solidity -/** - * @dev Returns tokens locked for a specified address for a - * specified reason at a specific time - * - * @param _of The address whose tokens are locked - * @param _reason The reason to query the lock tokens for - * @param _time The timestamp to query the lock tokens for - */ - function tokensLockedAtTime(address _of, bytes32 _reason, uint256 _time) public view returns (uint256 amount) -``` - -### Fetching number of tokens held by an address -```solidity -/** - * @dev @dev Returns total tokens held by an address (locked + transferable) - * @param _of The address to query the total balance of - */ -function totalBalanceOf(address _of) view returns (uint256 amount) -``` - -### Extending lock period -```solidity -/** - * @dev Extends lock for a specified reason and time - * @param _reason The reason to lock tokens - * @param _time Lock extension time in seconds - */ - function extendLock(bytes32 _reason, uint256 _time) public returns (bool) -``` - -### Increasing number of tokens locked -```solidity -/** - * @dev Increase number of tokens locked for a specified reason - * @param _reason The reason to lock tokens - * @param _amount Number of tokens to be increased - */ - function increaseLockAmount(bytes32 _reason, uint256 _amount) public returns (bool) -``` -### Fetching number of unlockable tokens under each utility -```solidity -/** - * @dev Returns unlockable tokens for a specified address for a specified reason - * @param _of The address to query the the unlockable token count of - * @param _reason The reason to query the unlockable tokens for - */ - function tokensUnlockable(address _of, bytes32 _reason) public view returns (uint256 amount) - ``` -### Fetching number of unlockable tokens -```solidity -/** - * @dev Gets the unlockable tokens of a specified address - * @param _of The address to query the the unlockable token count of - */ - function getUnlockableTokens(address _of) public view returns (uint256 unlockableTokens) -``` -### Unlocking tokens -```solidity -/** - * @dev Unlocks the unlockable tokens of a specified address - * @param _of Address of user, claiming back unlockable tokens - */ - function unlock(address _of) public returns (uint256 unlockableTokens) -``` - -### Lock event recorded in the token contract -`event Locked(address indexed _of, uint256 indexed _reason, uint256 _amount, uint256 _validity)` - -### Unlock event recorded in the token contract -`event Unlocked(address indexed _of, uint256 indexed _reason, uint256 _amount)` - -## Test Cases - -Test cases are available at [https://github.com/nitika-goel/lockable-token](https://github.com/nitika-goel/lockable-token). - -## Implementation - -- Complete implementation available at https://github.com/nitika-goel/lockable-token -- [GovBlocks](https://govblocks.io) Project specific implementation available at https://github.com/somish/govblocks-protocol/blob/Locking/contracts/GBTStandardToken.sol - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1132.md diff --git a/EIPS/eip-1154.md b/EIPS/eip-1154.md index 417ae863f31b72..a71971e3fa6763 100644 --- a/EIPS/eip-1154.md +++ b/EIPS/eip-1154.md @@ -1,110 +1 @@ ---- -eip: 1154 -title: Oracle Interface -author: Alan Lu (@cag) -discussions-to: https://github.com/ethereum/EIPs/issues/1161 -status: Withdrawn -type: Standards Track -category: ERC -created: 2018-06-13 ---- - -## Simple Summary -A standard interface for oracles. - -## Abstract -In order for ethereum smart contracts to interact with off-chain systems, oracles must be used. These oracles report values which are normally off-chain, allowing smart contracts to react to the state of off-chain systems. A distinction and a choice is made between push and pull based oracle systems. Furthermore, a standard interface for oracles is described here, allowing different oracle implementations to be interchangeable. - -## Motivation -The Ethereum ecosystem currently has many different oracle implementations available, but they do not provide a unified interface. Smart contract systems would be locked into a single set of oracle implementations, or they would require developers to write adapters/ports specific to the oracle system chosen in a given project. - -Beyond naming differences, there is also the issue of whether or not an oracle report-resolving transaction _pushes_ state changes by calling affected contracts, or changes the oracle state allowing dependent contracts to _pull_ the updated value from the oracle. These differing system semantics could introduce inefficiencies when adapting between them. - -Ultimately, the value in different oracle systems comes from their underlying resolution mechanics, and points where these systems are virtually identical should be standardized. - -These oracles may be used for answering questions about "real-world events", where each ID can be correlated with a specification of a question and its answers (so most likely for prediction markets, basically). - -Another use case could be for decision-making processes, where the results given by the oracle represent decisions made by the oracle (e.g. futarchies). DAOs may require their use in decision making processes. - -Both the ID and the results are intentionally unstructured so that things like time series data (via splitting the ID) and different sorts of results (like one of a few, any subset of up to 256, or some value in a range with up to 256 bits of granularity) can be represented. - -## Specification - -
-
Oracle
-
An entity which reports data to the blockchain.
- -
Oracle consumer
-
A smart contract which receives data from an oracle.
- -
ID
-
A way of indexing the data which an oracle reports. May be derived from or tied to a question for which the data provides the answer.
- -
Result
-
Data associated with an id which is reported by an oracle. This data oftentimes will be the answer to a question tied to the id. Other equivalent terms that have been used include: answer, data, outcome.
- -
Report
-
A pair (ID, result) which an oracle sends to an oracle consumer.
-
- -```solidity -interface OracleConsumer { - function receiveResult(bytes32 id, bytes result) external; -} -``` - -`receiveResult` MUST revert if the `msg.sender` is not an oracle authorized to provide the `result` for that `id`. - -`receiveResult` MUST revert if `receiveResult` has been called with the same `id` before. - -`receiveResult` MAY revert if the `id` or `result` cannot be handled by the consumer. - -Consumers MUST coordinate with oracles to determine how to encode/decode results to and from `bytes`. For example, `abi.encode` and `abi.decode` may be used to implement a codec for results in Solidity. `receiveResult` SHOULD revert if the consumer receives a unexpected result format from the oracle. - -The oracle can be any Ethereum account. - -## Rationale -The specs are currently very similar to what is implemented by ChainLink (which can use any arbitrarily-named callback) and Oraclize (which uses `__callback`). - -With this spec, the oracle _pushes_ state to the consumer, which must react accordingly to the updated state. An alternate _pull_-based interface can be prescribed, as follows: - -### Alternate Pull-based Interface -Here are alternate specs loosely based on Gnosis prediction market contracts v1. Reality Check also exposes a similar endpoint (`getFinalAnswer`). - -```solidity -interface Oracle { - function resultFor(bytes32 id) external view returns (bytes result); -} -``` - -`resultFor` MUST revert if the result for an `id` is not available yet. - -`resultFor` MUST return the same result for an `id` after that result is available. - -### Push vs Pull -Note that push-based interfaces may be adapted into pull-based interfaces. Simply deploy an oracle consumer which stores the result received and implements `resultFor` accordingly. - -Similarly, every pull-based system can be adapted into a push-based system: just add a method on the oracle smart contract which takes an oracle consumer address and calls `receiveResult` on that address. - -In both cases, an additional transaction would have to be performed, so the choice to go with push or pull should be based on the dominant use case for these oracles. - -In the simple case where a single account has the authority to decide the outcome of an oracle question, there is no need to deploy an oracle contract and store the outcome on that oracle contract. Similarly, in the case where the outcome comes down to a vote, existing multisignature wallets can be used as the authorized oracle. - -#### Multiple Oracle Consumers -In the case that many oracle consumers depend on a single oracle result and all these consumers expect the result to be pushed to them, the push and pull adaptations mentioned before may be combined if the pushing oracle cannot be trusted to send the same result to every consumer (in a sense, this forwards the trust to the oracle adaptor implementation). - -In a pull-based system, each of the consumers would have to be called to pull the result from the oracle contract, but in the proposed push-based system, the adapted oracle would have to be called to push the results to each of the consumers. - -Transaction-wise, both systems are roughly equivalent in efficiency in this scenario, but in the push-based system, there's a need for the oracle consumers to store the results again, whereas in the pull-based system, the consumers may continue to refer to the oracle for the results. Although this may be somewhat less efficient, requiring the consumers to store the results can also provide security guarantees, especially with regards to result immutability. - -#### Result Immutability -In both the proposed specification and the alternate specification, results are immutable once they are determined. This is due to the expectation that typical consumers will require results to be immutable in order to determine a resulting state consistently. With the proposed push-based system, the consumer enforces the result immutability requirement, whereas in the alternate pull-based system, either the oracle would have to be trusted to implement the spec correctly and enforce the immutability requirement, or the consumer would also have to handle result immutability. - -For data which mutates over time, the `id` field may be structured to specify "what" and "when" for the data (using 128 bits to specify "when" is still safe for many millennia). - -## Implementation - -* [Tidbit](https://github.com/levelkdev/tidbit) tracks this EIP. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1154.md diff --git a/EIPS/eip-1155.md b/EIPS/eip-1155.md index 42ca3a7e5bcb25..104b60913117ca 100644 --- a/EIPS/eip-1155.md +++ b/EIPS/eip-1155.md @@ -1,710 +1 @@ ---- -eip: 1155 -title: Multi Token Standard -author: Witek Radomski , Andrew Cooke , Philippe Castonguay (@phabc) , James Therien , Eric Binet , Ronan Sandford (@wighawag) -type: Standards Track -category: ERC -status: Final -created: 2018-06-17 -discussions-to: https://github.com/ethereum/EIPs/issues/1155 -requires: 165 ---- - -## Simple Summary - -A standard interface for contracts that manage multiple token types. A single deployed contract may include any combination of fungible tokens, non-fungible tokens or other configurations (e.g. semi-fungible tokens). - -## Abstract - -This standard outlines a smart contract interface that can represent any number of fungible and non-fungible token types. Existing standards such as ERC-20 require deployment of separate contracts per token type. The ERC-721 standard's token ID is a single non-fungible index and the group of these non-fungibles is deployed as a single contract with settings for the entire collection. In contrast, the ERC-1155 Multi Token Standard allows for each token ID to represent a new configurable token type, which may have its own metadata, supply and other attributes. - -The `_id` argument contained in each function's argument set indicates a specific token or token type in a transaction. - -## Motivation - -Tokens standards like ERC-20 and ERC-721 require a separate contract to be deployed for each token type or collection. This places a lot of redundant bytecode on the Ethereum blockchain and limits certain functionality by the nature of separating each token contract into its own permissioned address. With the rise of blockchain games and platforms like Enjin Coin, game developers may be creating thousands of token types, and a new type of token standard is needed to support them. However, ERC-1155 is not specific to games and many other applications can benefit from this flexibility. - -New functionality is possible with this design such as transferring multiple token types at once, saving on transaction costs. Trading (escrow / atomic swaps) of multiple tokens can be built on top of this standard and it removes the need to "approve" individual token contracts separately. It is also easy to describe and mix multiple fungible or non-fungible token types in a single contract. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -**Smart contracts implementing the ERC-1155 standard MUST implement all of the functions in the `ERC1155` interface.** - -**Smart contracts implementing the ERC-1155 standard MUST implement the ERC-165 `supportsInterface` function and MUST return the constant value `true` if `0xd9b67a26` is passed through the `interfaceID` argument.** - -```solidity -pragma solidity ^0.5.9; - -/** - @title ERC-1155 Multi Token Standard - @dev See https://eips.ethereum.org/EIPS/eip-1155 - Note: The ERC-165 identifier for this interface is 0xd9b67a26. - */ -interface ERC1155 /* is ERC165 */ { - /** - @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). - The `_from` argument MUST be the address of the holder whose balance is decreased. - The `_to` argument MUST be the address of the recipient whose balance is increased. - The `_id` argument MUST be the token type being transferred. - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). - */ - event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value); - - /** - @dev Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). - The `_from` argument MUST be the address of the holder whose balance is decreased. - The `_to` argument MUST be the address of the recipient whose balance is increased. - The `_ids` argument MUST be the list of tokens being transferred. - The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). - */ - event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values); - - /** - @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). - */ - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - - /** - @dev MUST emit when the URI is updated for a token ID. - URIs are defined in RFC 3986. - The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". - */ - event URI(string _value, uint256 indexed _id); - - /** - @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). - @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). - MUST revert if `_to` is the zero address. - MUST revert if balance of holder for token `_id` is lower than the `_value` sent. - MUST revert on any other error. - MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). - After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). - @param _from Source address - @param _to Target address - @param _id ID of the token type - @param _value Transfer amount - @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` - */ - function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external; - - /** - @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). - @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). - MUST revert if `_to` is the zero address. - MUST revert if length of `_ids` is not the same as length of `_values`. - MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. - MUST revert on any other error. - MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). - Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). - After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). - @param _from Source address - @param _to Target address - @param _ids IDs of each token type (order and length must match _values array) - @param _values Transfer amounts per token type (order and length must match _ids array) - @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` - */ - function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external; - - /** - @notice Get the balance of an account's tokens. - @param _owner The address of the token holder - @param _id ID of the token - @return The _owner's balance of the token type requested - */ - function balanceOf(address _owner, uint256 _id) external view returns (uint256); - - /** - @notice Get the balance of multiple account/token pairs - @param _owners The addresses of the token holders - @param _ids ID of the tokens - @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) - */ - function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory); - - /** - @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. - @dev MUST emit the ApprovalForAll event on success. - @param _operator Address to add to the set of authorized operators - @param _approved True if the operator is approved, false to revoke approval - */ - function setApprovalForAll(address _operator, bool _approved) external; - - /** - @notice Queries the approval status of an operator for a given owner. - @param _owner The owner of the tokens - @param _operator Address of authorized operator - @return True if the operator is approved, false if not - */ - function isApprovedForAll(address _owner, address _operator) external view returns (bool); -} -``` - -### ERC-1155 Token Receiver - -**Smart contracts MUST implement all of the functions in the `ERC1155TokenReceiver` interface to accept transfers. See "Safe Transfer Rules" for further detail.** - -**Smart contracts MUST implement the ERC-165 `supportsInterface` function and signify support for the `ERC1155TokenReceiver` interface to accept transfers. See "ERC1155TokenReceiver ERC-165 rules" for further detail.** - -```solidity -pragma solidity ^0.5.9; - -/** - Note: The ERC-165 identifier for this interface is 0x4e2312e0. -*/ -interface ERC1155TokenReceiver { - /** - @notice Handle the receipt of a single ERC1155 token type. - @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. - This function MUST return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` (i.e. 0xf23a6e61) if it accepts the transfer. - This function MUST revert if it rejects the transfer. - Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. - @param _operator The address which initiated the transfer (i.e. msg.sender) - @param _from The address which previously owned the token - @param _id The ID of the token being transferred - @param _value The amount of tokens being transferred - @param _data Additional data with no specified format - @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` - */ - function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4); - - /** - @notice Handle the receipt of multiple ERC1155 token types. - @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. - This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s). - This function MUST revert if it rejects the transfer(s). - Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. - @param _operator The address which initiated the batch transfer (i.e. msg.sender) - @param _from The address which previously owned the token - @param _ids An array containing ids of each token being transferred (order and length must match _values array) - @param _values An array containing amounts of each token being transferred (order and length must match _ids array) - @param _data Additional data with no specified format - @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` - */ - function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4); -} -``` - -### Safe Transfer Rules - -To be more explicit about how the standard `safeTransferFrom` and `safeBatchTransferFrom` functions MUST operate with respect to the `ERC1155TokenReceiver` hook functions, a list of scenarios and rules follows. - -#### Scenarios - -**_Scenario#1 :_** The recipient is not a contract. -* `onERC1155Received` and `onERC1155BatchReceived` MUST NOT be called on an EOA (Externally Owned Account). - -**_Scenario#2 :_** The transaction is not a mint/transfer of a token. -* `onERC1155Received` and `onERC1155BatchReceived` MUST NOT be called outside of a mint or transfer process. - -**_Scenario#3 :_** The receiver does not implement the necessary `ERC1155TokenReceiver` interface function(s). -* The transfer MUST be reverted with the one caveat below. - - If the token(s) being sent are part of a hybrid implementation of another standard, that particular standard's rules on sending to a contract MAY now be followed instead. See "Backwards Compatibility" section. - -**_Scenario#4 :_** The receiver implements the necessary `ERC1155TokenReceiver` interface function(s) but returns an unknown value. -* The transfer MUST be reverted. - -**_Scenario#5 :_** The receiver implements the necessary `ERC1155TokenReceiver` interface function(s) but throws an error. -* The transfer MUST be reverted. - -**_Scenario#6 :_** The receiver implements the `ERC1155TokenReceiver` interface and is the recipient of one and only one balance change (e.g. `safeTransferFrom` called). -* The balances for the transfer MUST have been updated before the `ERC1155TokenReceiver` hook is called on a recipient contract. -* The transfer event MUST have been emitted to reflect the balance changes before the `ERC1155TokenReceiver` hook is called on the recipient contract. -* One of `onERC1155Received` or `onERC1155BatchReceived` MUST be called on the recipient contract. -* The `onERC1155Received` hook SHOULD be called on the recipient contract and its rules followed. - - See "onERC1155Received rules" for further rules that MUST be followed. -* The `onERC1155BatchReceived` hook MAY be called on the recipient contract and its rules followed. - - See "onERC1155BatchReceived rules" for further rules that MUST be followed. - -**_Scenario#7 :_** The receiver implements the `ERC1155TokenReceiver` interface and is the recipient of more than one balance change (e.g. `safeBatchTransferFrom` called). -* All balance transfers that are referenced in a call to an `ERC1155TokenReceiver` hook MUST be updated before the `ERC1155TokenReceiver` hook is called on the recipient contract. -* All transfer events MUST have been emitted to reflect current balance changes before an `ERC1155TokenReceiver` hook is called on the recipient contract. -* `onERC1155Received` or `onERC1155BatchReceived` MUST be called on the recipient as many times as necessary such that every balance change for the recipient in the scenario is accounted for. - - The return magic value for every hook call MUST be checked and acted upon as per "onERC1155Received rules" and "onERC1155BatchReceived rules". -* The `onERC1155BatchReceived` hook SHOULD be called on the recipient contract and its rules followed. - - See "onERC1155BatchReceived rules" for further rules that MUST be followed. -* The `onERC1155Received` hook MAY be called on the recipient contract and its rules followed. - - See "onERC1155Received rules" for further rules that MUST be followed. - -**_Scenario#8 :_** You are the creator of a contract that implements the `ERC1155TokenReceiver` interface and you forward the token(s) onto another address in one or both of `onERC1155Received` and `onERC1155BatchReceived`. -* Forwarding should be considered acceptance and then initiating a new `safeTransferFrom` or `safeBatchTransferFrom` in a new context. - - The prescribed keccak256 acceptance value magic for the receiver hook being called MUST be returned after forwarding is successful. -* The `_data` argument MAY be re-purposed for the new context. -* If forwarding fails the transaction MAY be reverted. - - If the contract logic wishes to keep the ownership of the token(s) itself in this case it MAY do so. - -**_Scenario#9 :_** You are transferring tokens via a non-standard API call i.e. an implementation specific API and NOT `safeTransferFrom` or `safeBatchTransferFrom`. -* In this scenario all balance updates and events output rules are the same as if a standard transfer function had been called. - - i.e. an external viewer MUST still be able to query the balance via a standard function and it MUST be identical to the balance as determined by `TransferSingle` and `TransferBatch` events alone. -* If the receiver is a contract the `ERC1155TokenReceiver` hooks still need to be called on it and the return values respected the same as if a standard transfer function had been called. - - However while the `safeTransferFrom` or `safeBatchTransferFrom` functions MUST revert if a receiving contract does not implement the `ERC1155TokenReceiver` interface, a non-standard function MAY proceed with the transfer. - - See "Implementation specific transfer API rules". - - -#### Rules - -**_safeTransferFrom rules:_** -* Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section). -* MUST revert if `_to` is the zero address. -* MUST revert if balance of holder for token `_id` is lower than the `_value` sent to the recipient. -* MUST revert on any other error. -* MUST emit the `TransferSingle` event to reflect the balance change (see "TransferSingle and TransferBatch event rules" section). -* After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "onERC1155Received rules" section). - - The `_data` argument provided by the sender for the transfer MUST be passed with its contents unaltered to the `onERC1155Received` hook function via its `_data` argument. - -**_safeBatchTransferFrom rules:_** -* Caller must be approved to manage all the tokens being transferred out of the `_from` account (see "Approval" section). -* MUST revert if `_to` is the zero address. -* MUST revert if length of `_ids` is not the same as length of `_values`. -* MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. -* MUST revert on any other error. -* MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "TransferSingle and TransferBatch event rules" section). -* The balance changes and events MUST occur in the array order they were submitted (_ids[0]/_values[0] before _ids[1]/_values[1], etc). -* After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` or `onERC1155BatchReceived` on `_to` and act appropriately (see "onERC1155Received and onERC1155BatchReceived rules" section). - - The `_data` argument provided by the sender for the transfer MUST be passed with its contents unaltered to the `ERC1155TokenReceiver` hook function(s) via their `_data` argument. - -**_TransferSingle and TransferBatch event rules:_** -* `TransferSingle` SHOULD be used to indicate a single balance transfer has occurred between a `_from` and `_to` pair. - - It MAY be emitted multiple times to indicate multiple balance changes in the transaction, but note that `TransferBatch` is designed for this to reduce gas consumption. - - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). - - The `_from` argument MUST be the address of the holder whose balance is decreased. - - The `_to` argument MUST be the address of the recipient whose balance is increased. - - The `_id` argument MUST be the token type being transferred. - - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. - - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). See "Minting/creating and burning/destroying rules". - - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). See "Minting/creating and burning/destroying rules". -* `TransferBatch` SHOULD be used to indicate multiple balance transfers have occurred between a `_from` and `_to` pair. - - It MAY be emitted with a single element in the list to indicate a singular balance change in the transaction, but note that `TransferSingle` is designed for this to reduce gas consumption. - - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). - - The `_from` argument MUST be the address of the holder whose balance is decreased for each entry pair in `_ids` and `_values`. - - The `_to` argument MUST be the address of the recipient whose balance is increased for each entry pair in `_ids` and `_values`. - - The `_ids` array argument MUST contain the ids of the tokens being transferred. - - The `_values` array argument MUST contain the number of token to be transferred for each corresponding entry in `_ids`. - - `_ids` and `_values` MUST have the same length. - - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). See "Minting/creating and burning/destroying rules". - - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). See "Minting/creating and burning/destroying rules". -* The total value transferred from address `0x0` minus the total value transferred to `0x0` observed via the `TransferSingle` and `TransferBatch` events MAY be used by clients and exchanges to determine the "circulating supply" for a given token ID. -* To broadcast the existence of a token ID with no initial balance, the contract SHOULD emit the `TransferSingle` event from `0x0` to `0x0`, with the token creator as `_operator`, and a `_value` of 0. -* All `TransferSingle` and `TransferBatch` events MUST be emitted to reflect all the balance changes that have occurred before any call(s) to `onERC1155Received` or `onERC1155BatchReceived`. - - To make sure event order is correct in the case of valid re-entry (e.g. if a receiver contract forwards tokens on receipt) state balance and events balance MUST match before calling an external contract. - -**_onERC1155Received rules:_** -- The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). -* The `_from` argument MUST be the address of the holder whose balance is decreased. - - `_from` MUST be 0x0 for a mint. -* The `_id` argument MUST be the token type being transferred. -* The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. -* The `_data` argument MUST contain the information provided by the sender for the transfer with its contents unaltered. - - i.e. it MUST pass on the unaltered `_data` argument sent via the `safeTransferFrom` or `safeBatchTransferFrom` call for this transfer. -* The recipient contract MAY accept an increase of its balance by returning the acceptance magic value `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` - - If the return value is `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` the transfer MUST be completed or MUST revert if any other conditions are not met for success. -* The recipient contract MAY reject an increase of its balance by calling revert. - - If the recipient contract throws/reverts the transaction MUST be reverted. -* If the return value is anything other than `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` the transaction MUST be reverted. -* `onERC1155Received` (and/or `onERC1155BatchReceived`) MAY be called multiple times in a single transaction and the following requirements must be met: - - All callbacks represent mutually exclusive balance changes. - - The set of all calls to `onERC1155Received` and `onERC1155BatchReceived` describes all balance changes that occurred during the transaction in the order submitted. -* A contract MAY skip calling the `onERC1155Received` hook function if the transfer operation is transferring the token to itself. - -**_onERC1155BatchReceived rules:_** -- The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). -* The `_from` argument MUST be the address of the holder whose balance is decreased. - - `_from` MUST be 0x0 for a mint. -* The `_ids` argument MUST be the list of tokens being transferred. -* The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in `_ids`) the holder balance is decreased by and match what the recipient balance is increased by. -* The `_data` argument MUST contain the information provided by the sender for the transfer with its contents unaltered. - - i.e. it MUST pass on the unaltered `_data` argument sent via the `safeBatchTransferFrom` call for this transfer. -* The recipient contract MAY accept an increase of its balance by returning the acceptance magic value `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` - - If the return value is `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` the transfer MUST be completed or MUST revert if any other conditions are not met for success. -* The recipient contract MAY reject an increase of its balance by calling revert. - - If the recipient contract throws/reverts the transaction MUST be reverted. -* If the return value is anything other than `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` the transaction MUST be reverted. -* `onERC1155BatchReceived` (and/or `onERC1155Received`) MAY be called multiple times in a single transaction and the following requirements must be met: - - All callbacks represent mutually exclusive balance changes. - - The set of all calls to `onERC1155Received` and `onERC1155BatchReceived` describes all balance changes that occurred during the transaction in the order submitted. -* A contract MAY skip calling the `onERC1155BatchReceived` hook function if the transfer operation is transferring the token(s) to itself. - -**_ERC1155TokenReceiver ERC-165 rules:_** -* The implementation of the ERC-165 `supportsInterface` function SHOULD be as follows: - ```solidity - function supportsInterface(bytes4 interfaceID) external view returns (bool) { - return interfaceID == 0x01ffc9a7 || // ERC-165 support (i.e. `bytes4(keccak256('supportsInterface(bytes4)'))`). - interfaceID == 0x4e2312e0; // ERC-1155 `ERC1155TokenReceiver` support (i.e. `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) ^ bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`). - } - ``` -* The implementation MAY differ from the above but: - - It MUST return the constant value `true` if `0x01ffc9a7` is passed through the `interfaceID` argument. This signifies ERC-165 support. - - It MUST return the constant value `true` if `0x4e2312e0` is passed through the `interfaceID` argument. This signifies ERC-1155 `ERC1155TokenReceiver` support. - - It MUST NOT consume more than 10,000 gas. - - This keeps it below the ERC-165 requirement of 30,000 gas, reduces the gas reserve needs and minimises possible side-effects of gas exhaustion during the call. - -**_Implementation specific transfer API rules:_** -* If an implementation specific API function is used to transfer ERC-1155 token(s) to a contract, the `safeTransferFrom` or `safeBatchTransferFrom` (as appropriate) rules MUST still be followed if the receiver implements the `ERC1155TokenReceiver` interface. If it does not the non-standard implementation SHOULD revert but MAY proceed. -* An example: - 1. An approved user calls a function such as `function myTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values);`. - 2. `myTransferFrom` updates the balances for `_from` and `_to` addresses for all `_ids` and `_values`. - 3. `myTransferFrom` emits `TransferBatch` with the details of what was transferred from address `_from` to address `_to`. - 4. `myTransferFrom` checks if `_to` is a contract address and determines that it is so (if not, then the transfer can be considered successful). - 5. `myTransferFrom` calls `onERC1155BatchReceived` on `_to` and it reverts or returns an unknown value (if it had returned `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` the transfer can be considered successful). - 6. At this point `myTransferFrom` SHOULD revert the transaction immediately as receipt of the token(s) was not explicitly accepted by the `onERC1155BatchReceived` function. - 7. If however `myTransferFrom` wishes to continue it MUST call `supportsInterface(0x4e2312e0)` on `_to` and if it returns the constant value `true` the transaction MUST be reverted, as it is now known to be a valid receiver and the previous acceptance step failed. - - NOTE: You could have called `supportsInterface(0x4e2312e0)` at a previous step if you wanted to gather and act upon that information earlier, such as in a hybrid standards scenario. - 8. If the above call to `supportsInterface(0x4e2312e0)` on `_to` reverts or returns a value other than the constant value `true` the `myTransferFrom` function MAY consider this transfer successful. - - __NOTE__: this MAY result in unrecoverable tokens if sent to an address that does not expect to receive ERC-1155 tokens. -* The above example is not exhaustive but illustrates the major points (and shows that most are shared with `safeTransferFrom` and `safeBatchTransferFrom`): - - Balances that are updated MUST have equivalent transfer events emitted. - - A receiver address has to be checked if it is a contract and if so relevant `ERC1155TokenReceiver` hook function(s) have to be called on it. - - Balances (and events associated) that are referenced in a call to an `ERC1155TokenReceiver` hook MUST be updated (and emitted) before the `ERC1155TokenReceiver` hook is called. - - The return values of the `ERC1155TokenReceiver` hook functions that are called MUST be respected if they are implemented. - - Only non-standard transfer functions MAY allow tokens to be sent to a recipient contract that does NOT implement the necessary `ERC1155TokenReceiver` hook functions. `safeTransferFrom` and `safeBatchTransferFrom` MUST revert in that case (unless it is a hybrid standards implementation see "Backwards Compatibility"). - -**_Minting/creating and burning/destroying rules:_** -* A mint/create operation is essentially a specialized transfer and MUST follow these rules: - - To broadcast the existence of a token ID with no initial balance, the contract SHOULD emit the `TransferSingle` event from `0x0` to `0x0`, with the token creator as `_operator`, and a `_value` of 0. - - The "TransferSingle and TransferBatch event rules" MUST be followed as appropriate for the mint(s) (i.e. singles or batches) however the `_from` argument MUST be set to `0x0` (i.e. zero address) to flag the transfer as a mint to contract observers. - - __NOTE:__ This includes tokens that are given an initial balance in the contract. The balance of the contract MUST also be able to be determined by events alone meaning initial contract balances (for eg. in construction) MUST emit events to reflect those balances too. -* A burn/destroy operation is essentially a specialized transfer and MUST follow these rules: - - The "TransferSingle and TransferBatch event rules" MUST be followed as appropriate for the burn(s) (i.e. singles or batches) however the `_to` argument MUST be set to `0x0` (i.e. zero address) to flag the transfer as a burn to contract observers. - - When burning/destroying you do not have to actually transfer to `0x0` (that is impl specific), only the `_to` argument in the event MUST be set to `0x0` as above. -* The total value transferred from address `0x0` minus the total value transferred to `0x0` observed via the `TransferSingle` and `TransferBatch` events MAY be used by clients and exchanges to determine the "circulating supply" for a given token ID. -* As mentioned above mint/create and burn/destroy operations are specialized transfers and so will likely be accomplished with custom transfer functions rather than `safeTransferFrom` or `safeBatchTransferFrom`. If so the "Implementation specific transfer API rules" section would be appropriate. - - Even in a non-safe API and/or hybrid standards case the above event rules MUST still be adhered to when minting/creating or burning/destroying. -* A contract MAY skip calling the `ERC1155TokenReceiver` hook function(s) if the mint operation is transferring the token(s) to itself. In all other cases the `ERC1155TokenReceiver` rules MUST be followed as appropriate for the implementation (i.e. safe, custom and/or hybrid). - - -##### A solidity example of the keccak256 generated constants for the various magic values (these MAY be used by implementation): - -```solidity -bytes4 constant public ERC1155_ERC165 = 0xd9b67a26; // ERC-165 identifier for the main token standard. -bytes4 constant public ERC1155_ERC165_TOKENRECEIVER = 0x4e2312e0; // ERC-165 identifier for the `ERC1155TokenReceiver` support (i.e. `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) ^ bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`). -bytes4 constant public ERC1155_ACCEPTED = 0xf23a6e61; // Return value from `onERC1155Received` call if a contract accepts receipt (i.e `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`). -bytes4 constant public ERC1155_BATCH_ACCEPTED = 0xbc197c81; // Return value from `onERC1155BatchReceived` call if a contract accepts receipt (i.e `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`). -``` - -### Metadata - -The URI value allows for ID substitution by clients. If the string `{id}` exists in any URI, clients MUST replace this with the actual token ID in hexadecimal form. This allows for a large number of tokens to use the same on-chain string by defining a URI once, for that large number of tokens. - -* The string format of the substituted hexadecimal ID MUST be lowercase alphanumeric: `[0-9a-f]` with no 0x prefix. -* The string format of the substituted hexadecimal ID MUST be leading zero padded to 64 hex characters length if necessary. - -Example of such a URI: `https://token-cdn-domain/{id}.json` would be replaced with `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` if the client is referring to token ID 314592/0x4CCE0. - -#### Metadata Extensions - -The optional `ERC1155Metadata_URI` extension can be identified with the [ERC-165 Standard Interface Detection](./eip-165.md). - -If the optional `ERC1155Metadata_URI` extension is included: -* The ERC-165 `supportsInterface` function MUST return the constant value `true` if `0x0e89341c` is passed through the `interfaceID` argument. -* _Changes_ to the URI MUST emit the `URI` event if the change can be expressed with an event (i.e. it isn't dynamic/programmatic). - - An implementation MAY emit the `URI` event during a mint operation but it is NOT mandatory. An observer MAY fetch the metadata uri at mint time from the `uri` function if it was not emitted. -* The `uri` function SHOULD be used to retrieve values if no event was emitted. -* The `uri` function MUST return the same value as the latest event for an `_id` if it was emitted. -* The `uri` function MUST NOT be used to check for the existence of a token as it is possible for an implementation to return a valid string even if the token does not exist. - -```solidity -pragma solidity ^0.5.9; - -/** - Note: The ERC-165 identifier for this interface is 0x0e89341c. -*/ -interface ERC1155Metadata_URI { - /** - @notice A distinct Uniform Resource Identifier (URI) for a given token. - @dev URIs are defined in RFC 3986. - The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". - @return URI string - */ - function uri(uint256 _id) external view returns (string memory); -} -``` - -#### ERC-1155 Metadata URI JSON Schema - -This JSON schema is loosely based on the "ERC721 Metadata JSON Schema", but includes optional formatting to allow for ID substitution by clients. If the string `{id}` exists in any JSON value, it MUST be replaced with the actual token ID, by all client software that follows this standard. - -* The string format of the substituted hexadecimal ID MUST be lowercase alphanumeric: `[0-9a-f]` with no 0x prefix. -* The string format of the substituted hexadecimal ID MUST be leading zero padded to 64 hex characters length if necessary. - -```json -{ - "title": "Token Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this token represents" - }, - "decimals": { - "type": "integer", - "description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation." - }, - "description": { - "type": "string", - "description": "Describes the asset to which this token represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "properties": { - "type": "object", - "description": "Arbitrary properties. Values may be strings, numbers, object or arrays." - } - } -} -``` - -An example of an ERC-1155 Metadata JSON file follows. The properties array proposes some SUGGESTED formatting for token-specific display properties and metadata. - -```json -{ - "name": "Asset Name", - "description": "Lorem ipsum...", - "image": "https:\/\/s3.amazonaws.com\/your-bucket\/images\/{id}.png", - "properties": { - "simple_property": "example value", - "rich_property": { - "name": "Name", - "value": "123", - "display_value": "123 Example Value", - "class": "emphasis", - "css": { - "color": "#ffffff", - "font-weight": "bold", - "text-decoration": "underline" - } - }, - "array_property": { - "name": "Name", - "value": [1,2,3,4], - "class": "emphasis" - } - } -} -``` - -##### Localization - -Metadata localization should be standardized to increase presentation uniformity across all languages. As such, a simple overlay method is proposed to enable localization. If the metadata JSON file contains a `localization` attribute, its content MAY be used to provide localized values for fields that need it. The `localization` attribute should be a sub-object with three attributes: `uri`, `default` and `locales`. If the string `{locale}` exists in any URI, it MUST be replaced with the chosen locale by all client software. - -##### JSON Schema - -```json -{ - "title": "Token Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this token represents", - }, - "decimals": { - "type": "integer", - "description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation." - }, - "description": { - "type": "string", - "description": "Describes the asset to which this token represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "properties": { - "type": "object", - "description": "Arbitrary properties. Values may be strings, numbers, object or arrays.", - }, - "localization": { - "type": "object", - "required": ["uri", "default", "locales"], - "properties": { - "uri": { - "type": "string", - "description": "The URI pattern to fetch localized data from. This URI should contain the substring `{locale}` which will be replaced with the appropriate locale value before sending the request." - }, - "default": { - "type": "string", - "description": "The locale of the default data within the base JSON" - }, - "locales": { - "type": "array", - "description": "The list of locales for which data is available. These locales should conform to those defined in the Unicode Common Locale Data Repository (http://cldr.unicode.org/)." - } - } - } - } -} -``` - -##### Localized Sample - -Base URI: -```json -{ - "name": "Advertising Space", - "description": "Each token represents a unique Ad space in the city.", - "localization": { - "uri": "ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/{locale}.json", - "default": "en", - "locales": ["en", "es", "fr"] - } -} -``` - -es.json: -```json -{ - "name": "Espacio Publicitario", - "description": "Cada token representa un espacio publicitario único en la ciudad." -} -``` - -fr.json: -```json -{ - "name": "Espace Publicitaire", - "description": "Chaque jeton représente un espace publicitaire unique dans la ville." -} -``` - -### Approval - -The function `setApprovalForAll` allows an operator to manage one's entire set of tokens on behalf of the approver. To permit approval of a subset of token IDs, an interface such as [ERC-1761 Scoped Approval Interface](./eip-1761.md) is suggested. -The counterpart `isApprovedForAll` provides introspection into any status set by `setApprovalForAll`. - -An owner SHOULD be assumed to always be able to operate on their own tokens regardless of approval status, so should SHOULD NOT have to call `setApprovalForAll` to approve themselves as an operator before they can operate on them. - -## Rationale - -### Metadata Choices - -The `symbol` function (found in the ERC-20 and ERC-721 standards) was not included as we do not believe this is a globally useful piece of data to identify a generic virtual item / asset and are also prone to collisions. Short-hand symbols are used in tickers and currency trading, but they aren't as useful outside of that space. - -The `name` function (for human-readable asset names, on-chain) was removed from the standard to allow the Metadata JSON to be the definitive asset name and reduce duplication of data. This also allows localization for names, which would otherwise be prohibitively expensive if each language string was stored on-chain, not to mention bloating the standard interface. While this decision may add a small burden on implementers to host a JSON file containing metadata, we believe any serious implementation of ERC-1155 will already utilize JSON Metadata. - -### Upgrades - -The requirement to emit `TransferSingle` or `TransferBatch` on balance change implies that a valid implementation of ERC-1155 redeploying to a new contract address MUST emit events from the new contract address to replicate the deprecated contract final state. It is valid to only emit a minimal number of events to reflect only the final balance and omit all the transactions that led to that state. The event emit requirement is to ensure that the current state of the contract can always be traced only through events. To alleviate the need to emit events when changing contract address, consider using the proxy pattern, such as described in [EIP-2535](./eip-2535.md). This will also have the added benefit of providing a stable contract address for users. - -### Design decision: Supporting non-batch - -The standard supports `safeTransferFrom` and `onERC1155Received` functions because they are significantly cheaper for single token-type transfers, which is arguably a common use case. - -### Design decision: Safe transfers only - -The standard only supports safe-style transfers, making it possible for receiver contracts to depend on `onERC1155Received` or `onERC1155BatchReceived` function to be always called at the end of a transfer. - -### Guaranteed log trace - -As the Ethereum ecosystem continues to grow, many dapps are relying on traditional databases and explorer API services to retrieve and categorize data. The ERC-1155 standard guarantees that event logs emitted by the smart contract will provide enough data to create an accurate record of all current token balances. A database or explorer may listen to events and be able to provide indexed and categorized searches of every ERC-1155 token in the contract. - -### Approval - -The function `setApprovalForAll` allows an operator to manage one's entire set of tokens on behalf of the approver. It enables frictionless interaction with exchange and trade contracts. - -Restricting approval to a certain set of token IDs, quantities or other rules MAY be done with an additional interface or an external contract. The rationale is to keep the ERC-1155 standard as generic as possible for all use-cases without imposing a specific approval scheme on implementations that may not need it. Standard token approval interfaces can be used, such as the suggested [ERC-1761 Scoped Approval Interface](./eip-1761.md) which is compatible with ERC-1155. - -## Backwards Compatibility - -There have been requirements during the design discussions to have this standard be compatible with existing standards when sending to contract addresses, specifically ERC-721 at time of writing. -To cater for this scenario, there is some leeway with the revert logic should a contract not implement the `ERC1155TokenReceiver` as per "Safe Transfer Rules" section above, specifically "Scenario#3 : The receiver does not implement the necessary `ERC1155TokenReceiver` interface function(s)". - -Hence in a hybrid ERC-1155 contract implementation an extra call MUST be made on the recipient contract and checked before any hook calls to `onERC1155Received` or `onERC1155BatchReceived` are made. -Order of operation MUST therefore be: -1. The implementation MUST call the function `supportsInterface(0x4e2312e0)` on the recipient contract, providing at least 10,000 gas. -2. If the function call succeeds and the return value is the constant value `true` the implementation proceeds as a regular ERC-1155 implementation, with the call(s) to the `onERC1155Received` or `onERC1155BatchReceived` hooks and rules associated. -3. If the function call fails or the return value is NOT the constant value `true` the implementation can assume the recipient contract is not an `ERC1155TokenReceiver` and follow its other standard's rules for transfers. - -*__Note that a pure implementation of a single standard is recommended__* rather than a hybrid solution, but an example of a hybrid ERC-1155/ERC-721 contract is linked in the references section under implementations. - -An important consideration is that even if the tokens are sent with another standard's rules the *__ERC-1155 transfer events MUST still be emitted.__* This is so the balances can still be determined via events alone as per ERC-1155 standard rules. - -## Usage - -This standard can be used to represent multiple token types for an entire domain. Both fungible and non-fungible tokens can be stored in the same smart-contract. - -### Batch Transfers - -The `safeBatchTransferFrom` function allows for batch transfers of multiple token IDs and values. The design of ERC-1155 makes batch transfers possible without the need for a wrapper contract, as with existing token standards. This reduces gas costs when more than one token type is included in a batch transfer, as compared to single transfers with multiple transactions. - -Another advantage of standardized batch transfers is the ability for a smart contract to respond to the batch transfer in a single operation using `onERC1155BatchReceived`. - -It is RECOMMENDED that clients and wallets sort the token IDs and associated values (in ascending order) when posting a batch transfer, as some ERC-1155 implementations offer significant gas cost savings when IDs are sorted. See [Horizon Games - Multi-Token Standard](https://github.com/horizon-games/multi-token-standard) "packed balance" implementation for an example of this. - -### Batch Balance - -The `balanceOfBatch` function allows clients to retrieve balances of multiple owners and token IDs with a single call. - -### Enumerating from events - -In order to keep storage requirements light for contracts implementing ERC-1155, enumeration (discovering the IDs and values of tokens) must be done using event logs. It is RECOMMENDED that clients such as exchanges and blockchain explorers maintain a local database containing the token ID, Supply, and URI at the minimum. This can be built from each TransferSingle, TransferBatch, and URI event, starting from the block the smart contract was deployed until the latest block. - -ERC-1155 contracts must therefore carefully emit `TransferSingle` or `TransferBatch` events in any instance where tokens are created, minted, transferred or destroyed. - -### Non-Fungible Tokens - -The following strategies are examples of how you MAY mix fungible and non-fungible tokens together in the same contract. The standard does NOT mandate how an implementation must do this. - -##### Split ID bits - -The top 128 bits of the uint256 `_id` parameter in any ERC-1155 function MAY represent the base token ID, while the bottom 128 bits MAY represent the index of the non-fungible to make it unique. - -Non-fungible tokens can be interacted with using an index based accessor into the contract/token data set. Therefore to access a particular token set within a mixed data contract and a particular non-fungible within that set, `_id` could be passed as ``. - -To identify a non-fungible set/category as a whole (or a fungible) you COULD just pass in the base id via the `_id` argument as ``. If your implementation uses this technique this naturally means the index of a non-fungible SHOULD be 1-based. - -Inside the contract code the two pieces of data needed to access the individual non-fungible can be extracted with uint128(~0) and the same mask shifted by 128. - -```solidity -uint256 baseTokenNFT = 12345 << 128; -uint128 indexNFT = 50; - -uint256 baseTokenFT = 54321 << 128; - -balanceOf(msg.sender, baseTokenNFT); // Get balance of the base token for non-fungible set 12345 (this MAY be used to get balance of the user for all of this token set if the implementation wishes as a convenience). -balanceOf(msg.sender, baseTokenNFT + indexNFT); // Get balance of the token at index 50 for non-fungible set 12345 (should be 1 if user owns the individual non-fungible token or 0 if they do not). -balanceOf(msg.sender, baseTokenFT); // Get balance of the fungible base token 54321. -``` - -Note that 128 is an arbitrary number, an implementation MAY choose how they would like this split to occur as suitable for their use case. An observer of the contract would simply see events showing balance transfers and mints happening and MAY track the balances using that information alone. -For an observer to be able to determine type (non-fungible or fungible) from an ID alone they would have to know the split ID bits format on a implementation by implementation basis. - -The [ERC-1155 Reference Implementation](https://github.com/enjin/erc-1155) is an example of the split ID bits strategy. - -##### Natural Non-Fungible tokens - -Another simple way to represent non-fungibles is to allow a maximum value of 1 for each non-fungible token. This would naturally mirror the real world, where unique items have a quantity of 1 and fungible items have a quantity greater than 1. - -## References - -**Standards** -- [ERC-721 Non-Fungible Token Standard](./eip-721.md) -- [ERC-165 Standard Interface Detection](./eip-165.md) -- [ERC-1538 Transparent Contract Standard](./eip-1538.md) -- [JSON Schema](https://json-schema.org/) -- [RFC 2119 Key words for use in RFCs to Indicate Requirement Levels](https://www.ietf.org/rfc/rfc2119.txt) - -**Implementations** -- [ERC-1155 Reference Implementation](https://github.com/enjin/erc-1155) -- [Horizon Games - Multi-Token Standard](https://github.com/horizon-games/multi-token-standard) -- [Enjin Coin](https://enjincoin.io) ([GitHub](https://github.com/enjin)) -- [The Sandbox - Dual ERC-1155/721 Contract](https://github.com/pixowl/thesandbox-contracts/tree/master/src/Asset) - -**Articles & Discussions** -- [GitHub - Original Discussion Thread](https://github.com/ethereum/EIPs/issues/1155) -- [ERC-1155 - The Crypto Item Standard](https://blog.enjincoin.io/erc-1155-the-crypto-item-standard-ac9cf1c5a226) -- [Here Be Dragons - Going Beyond ERC-20 and ERC-721 To Reduce Gas Cost by ~80%](https://medium.com/horizongames/going-beyond-erc20-and-erc721-9acebd4ff6ef) -- [Blockonomi - Ethereum ERC-1155 Token Perfect for Online Games, Possibly More](https://blockonomi.com/erc1155-gaming-token/) -- [Beyond Gaming - Exploring the Utility of ERC-1155 Token Standard!](https://blockgeeks.com/erc-1155-token/) -- [ERC-1155: A new standard for The Sandbox](https://medium.com/sandbox-game/erc-1155-a-new-standard-for-the-sandbox-c95ee1e45072) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1155.md diff --git a/EIPS/eip-1167.md b/EIPS/eip-1167.md index ebb2d0a3278c3d..ab327f507583a7 100644 --- a/EIPS/eip-1167.md +++ b/EIPS/eip-1167.md @@ -1,115 +1 @@ ---- -eip: 1167 -title: Minimal Proxy Contract -author: Peter Murray (@yarrumretep), Nate Welch (@flygoing), Joe Messerman (@JAMesserman) -discussions-to: https://github.com/optionality/clone-factory/issues/10 -status: Final -type: Standards Track -category: ERC -created: 2018-06-22 -requires: 211 ---- - -## Simple Summary -To simply and cheaply clone contract functionality in an immutable way, this standard specifies a minimal bytecode implementation that delegates all calls to a known, fixed address. -## Abstract -By standardizing on a known minimal bytecode redirect implementation, this standard allows users and third party tools (e.g. Etherscan) to (a) simply discover that a contract will always redirect in a known manner and (b) depend on the behavior of the code at the destination contract as the behavior of the redirecting contract. Specifically, tooling can interrogate the bytecode at a redirecting address to determine the location of the code that will run - and can depend on representations about that code (verified source, third-party audits, etc). This implementation forwards all calls and 100% of the gas to the implementation contract and then relays the return value back to the caller. In the case where the implementation reverts, the revert is passed back along with the payload data (for revert with message). - -## Motivation -This standard supports use-cases wherein it is desirable to clone exact contract functionality with a minimum of side effects (e.g. memory slot stomping) and with low gas cost deployment of duplicate proxies. - -## Specification -The exact bytecode of the standard clone contract is this: `363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3` wherein the bytes at indices 10 - 29 (inclusive) are replaced with the 20 byte address of the master functionality contract. - -A reference implementation of this can be found at the [optionality/clone-factory](https://github.com/optionality/clone-factory) github repo. - -## Rationale -The goals of this effort have been the following: -- inexpensive deployment (low gas to deploy clones) -- support clone initialization in creation transaction (through factory contract model) -- simple clone bytecode to encourage directly bytecode interrogation (see CloneProbe.sol in the clone-factory project) -- dependable, locked-down behavior - this is not designed to handle upgradability, nor should it as the representation we are seeking is stronger. -- small operational overhead - adds a single call cost to each call -- handles error return bubbling for revert messages - -## Backwards Compatibility -There are no backwards compatibility issues. There may be some systems that are using earlier versions of the proxy contract bytecode. They will not be compliant with this standard. - -## Test Cases -Test cases include: -- invocation with no arguments -- invocation with arguments -- invocation with fixed length return values -- invocation with variable length return values -- invocation with revert (confirming reverted payload is transferred) - -Tests for these cases are included in the reference implementation project. - -## Implementation -Deployment bytecode is not included in this specification. One approach is defined in the proxy-contract reference implementation. - -### Standard Proxy -The disassembly of the standard deployed proxy contract code (from r2 and edited to include stack visualization) - -``` -| 0x00000000 36 calldatasize cds -| 0x00000001 3d returndatasize 0 cds -| 0x00000002 3d returndatasize 0 0 cds -| 0x00000003 37 calldatacopy -| 0x00000004 3d returndatasize 0 -| 0x00000005 3d returndatasize 0 0 -| 0x00000006 3d returndatasize 0 0 0 -| 0x00000007 36 calldatasize cds 0 0 0 -| 0x00000008 3d returndatasize 0 cds 0 0 0 -| 0x00000009 73bebebebebe. push20 0xbebebebe 0xbebe 0 cds 0 0 0 -| 0x0000001e 5a gas gas 0xbebe 0 cds 0 0 0 -| 0x0000001f f4 delegatecall suc 0 -| 0x00000020 3d returndatasize rds suc 0 -| 0x00000021 82 dup3 0 rds suc 0 -| 0x00000022 80 dup1 0 0 rds suc 0 -| 0x00000023 3e returndatacopy suc 0 -| 0x00000024 90 swap1 0 suc -| 0x00000025 3d returndatasize rds 0 suc -| 0x00000026 91 swap2 suc 0 rds -| 0x00000027 602b push1 0x2b 0x2b suc 0 rds -| ,=< 0x00000029 57 jumpi 0 rds -| | 0x0000002a fd revert -| `-> 0x0000002b 5b jumpdest 0 rds -\ 0x0000002c f3 return - -``` - -NOTE: as an effort to reduce gas costs as much as possible, the above bytecode depends on EIP-211 specification that `returndatasize` returns zero prior to any calls within the call-frame. `returndatasize` uses 1 less gas than `dup*`. - -### Vanity Address Optimization -Proxy deployment can be further optimized by installing the master contract at a vanity contract deployment address with leading zero-bytes. By generating a master contract vanity address that includes Z leading 0 bytes in its address, you can shorten the proxy bytecode by replacing the `push20` opcode with `pushN` (where N is 20 - Z) followed by the N non-zero address bytes. The revert jump address is decremented by Z in this case. Here is an example where Z = 4: -``` -| 0x00000000 36 calldatasize cds -| 0x00000001 3d returndatasize 0 cds -| 0x00000002 3d returndatasize 0 0 cds -| 0x00000003 37 calldatacopy -| 0x00000004 3d returndatasize 0 -| 0x00000005 3d returndatasize 0 0 -| 0x00000006 3d returndatasize 0 0 0 -| 0x00000007 36 calldatasize cds 0 0 0 -| 0x00000008 3d returndatasize 0 cds 0 0 0 -| 0x00000009 6fbebebebebe. push16 0xbebebebe 0xbebe 0 cds 0 0 0 -| 0x0000001a 5a gas gas 0xbebe 0 cds 0 0 0 -| 0x0000001b f4 delegatecall suc 0 -| 0x0000001c 3d returndatasize rds suc 0 -| 0x0000001d 82 dup3 0 rds suc 0 -| 0x0000001e 80 dup1 0 0 rds suc 0 -| 0x0000001f 3e returndatacopy suc 0 -| 0x00000020 90 swap1 0 suc -| 0x00000021 3d returndatasize rds 0 suc -| 0x00000022 91 swap2 suc 0 rds -| 0x00000023 6027 push1 0x27 0x27 suc 0 rds -| ,=< 0x00000025 57 jumpi 0 rds -| | 0x00000026 fd revert -| `-> 0x00000027 5b jumpdest 0 rds -\ 0x00000028 f3 return -``` -This saves 4 bytes of proxy contract size (savings on each deployment) and has zero impact on runtime gas costs. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1167.md diff --git a/EIPS/eip-1175.md b/EIPS/eip-1175.md index 133c3993ff53f6..1e86cbae2baf73 100644 --- a/EIPS/eip-1175.md +++ b/EIPS/eip-1175.md @@ -1,533 +1 @@ ---- -eip: 1175 -title: Wallet & shop standard for all tokens (erc20) -author: Jet Lim (@Nitro888) -discussions-to: https://github.com/ethereum/EIPs/issues/1182 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-06-21 -requires: 20 ---- - -# All tokens go to heaven -## Simple Summary -Make wallets and shops created from certified contracts make erc20 tokens easy to use for commerce. - -![wallet](../assets/eip-1175/wallet.png) - -## Abstract -The mutual trust between the wallet and the shop created by the authenticated contract allows you to pay for and purchase items at a simple process. - -## Motivation -New standards with improvements have been released, but the majority of tokens currently being developed are erc20 tokens. So I felt I needed a proposal to use old tokens in commerce. - To use various erc20 tokens for trading, you need a custom contract. However, a single wallet with a variety of tokens, and a mutually trusted store, can make transactions that are simple and efficient. The erc20 token is traded through two calls, `approve (address _spender, uint256 _value)` and `transferFrom (address _from, address _to, uint256 _value)`, but when using the wallet contract, `paySafe (address _shop, uint256 _item)`will be traded only in one call. -And if you only reuse the store interface, you can also trade using `payUnsafe (address _shop, uint256 _item)`. - -## Specification -![workflow](../assets/eip-1175/workflow.png) -## WalletCenter -### Methods -#### createWallet -Create wallet contract and add to list. Returns the address of new wallet. - -``` js -function createWallet() public returns (address _wallet) -``` - -#### isWallet -Returns true or false value for test this address is a created by createWallet. - -``` js -function isWallet(address _wallet) public constant returns (bool) -``` - -#### createShop -Create Shop contract and add to list. Returns the address of new Shop with erc20 token address. - -``` js -function createShop(address _erc20) public returns (address _shop) -``` - -#### isShop -Returns true or false value for test this address is a created by createWallet. - -``` js -function isShop(address _shop) public constant returns (bool) -``` - -### Events -#### Wallet -Search for my wallet. -``` js -event Wallet(address indexed _owner, address indexed _wallet) -``` - -#### Shop -Search for my shop. -``` js -event Shop(address indexed _owner, address indexed _shop, address indexed _erc20) -``` - -## Wallet -Wallet must be created by wallet center. -### Methods -#### balanceOf -Returns the account balance of Wallet. -``` js -function balanceOf(address _erc20) public constant returns (uint256 balance) -``` - -#### withdrawal -withdrawal `_value` amount of `_erc20` token to `_owner`. -``` js -function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success) -``` - -#### paySafe -Pay for safe shop (created by contract) item with item index `_item`. -``` js -function paySafe(address _shop, uint256 _item) onlyOwner onlyShop(_shop) public payable returns (bool success) -``` - -#### payUnsafe -Pay for unsafe shop (did not created by contract) item with item index `_item`. -``` js -function payUnsafe(address _shop, uint256 _item) onlyOwner public payable returns (bool success) -``` - -#### payCancel -Cancel pay and refund. (only weekly model) -``` js -function payCancel(address _shop, uint256 _item) onlyOwner public returns (bool success) -``` - -#### refund -Refund from shop with item index `_item`. -``` js -function refund(uint256 _item, uint256 _value) public payable returns (bool success) -``` - -### Events -#### Pay -``` js -event Pay(address indexed _shop, uint256 indexed _item, uint256 indexed _value) -``` - -#### Refund -``` js -event Refund(address indexed _shop, uint256 indexed _item, uint256 indexed _value) -``` - -## Shop -Shop is created by wallet center or not. but Shop that created by wallet center is called safe shop. -### Methods -#### balanceOf -Returns the account balance of Shop. -``` js -function balanceOf(address _erc20) public constant returns (uint256 balance) -``` - -#### withdrawal -withdrawal `_value` amount of `_erc20` token to `_owner`. -``` js -function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success) -``` - -#### pay -Pay from buyer with item index `_item`. -``` js -function pay(uint256 _item) onlyWallet(msg.sender) public payable returns (bool success) -``` - -#### refund -refund token to `_to`. -``` js -function refund(address _buyer, uint256 _item, uint256 _value) onlyWallet(_buyer) onlyOwner public payable returns (bool success) -``` - -#### resister -Listing item for sell. -``` js -function resister(uint8 _category, uint256 _price, uint256 _stock) onlyOwner public returns (uint256 _itemId) -``` - -#### update -Update item state for sell. (change item `_price` or add item `_stock`) -``` js -function update(uint256 _item, uint256 _price, uint256 _stock) onlyOwner public -``` - -#### price -Get token address and price from buyer with item index `_item`. -``` js -function price(uint256 _item) public constant returns (address _erc20, uint256 _value) -``` - -#### canBuy -`_who` can Buy `_item`. -``` js -function canBuy(address _who, uint256 _item) public constant returns (bool _canBuy) -``` - -#### isBuyer -`_who` is buyer of `_item`. -``` js -function isBuyer(address _who, uint256 _item) public constant returns (bool _buyer) -``` - -#### info -Set shop information bytes. -``` js -function info(bytes _msgPack) -``` - -#### upVote -Up vote for this shop. -``` js -function upVote() -``` - -#### dnVote -Down vote for this shop. -``` js -function dnVote() -``` - -#### about -Get shop token, up vote and down vote. -``` js -function about() view returns (address _erc20, uint256 _up, uint256 _down) -``` - -#### infoItem -Set item information bytes. -``` js -function infoItem(uint256 _item, bytes _msgPack) -``` - -#### upVoteItem -Up vote for this item. -``` js -function upVoteItem(uint256 _item) -``` - -#### dnVoteItem -Down vote for this item. -``` js -function dnVoteItem(uint256 _item) -``` - -#### aboutItem -Get Item price, up vote and down vote. -``` js -function aboutItem(uint256 _item) view returns (uint256 _price, uint256 _up, uint256 _down) -``` - -### Events -#### Pay -``` js -event Pay(address indexed _buyer, uint256 indexed _item, uint256 indexed _value) -``` - -#### Refund -``` js -event Refund(address indexed _to, uint256 indexed _item, uint256 indexed _value) -``` - -#### Item -``` js -event Item(uint256 indexed _item, uint256 _price) -``` - -#### Info -``` js -event Info(bytes _msgPack) -``` - -#### InfoItem -``` js -event InfoItem(uint256 indexed _item, bytes _msgPack) -``` - -## Implementation -Sample token contract address is [0x393dd70ce2ae7b30501aec94727968c517f90d52](https://ropsten.etherscan.io/address/0x393dd70ce2ae7b30501aec94727968c517f90d52) - -WalletCenter contract address is [0x1fe0862a4a8287d6c23904d61f02507b5044ea31](https://ropsten.etherscan.io/address/0x1fe0862a4a8287d6c23904d61f02507b5044ea31) - -WalletCenter create shop contract address is [0x59117730D02Ca3796121b7975796d479A5Fe54B0](https://ropsten.etherscan.io/address/0x59117730D02Ca3796121b7975796d479A5Fe54B0) - -WalletCenter create wallet contract address is [0x39da7111844df424e1d0a0226183533dd07bc5c6](https://ropsten.etherscan.io/address/0x39da7111844df424e1d0a0226183533dd07bc5c6) - - -## Appendix -``` js -pragma solidity ^0.4.24; - -contract ERC20Interface { - function totalSupply() public constant returns (uint); - function balanceOf(address tokenOwner) public constant returns (uint balance); - function allowance(address tokenOwner, address spender) public constant returns (uint remaining); - function transfer(address to, uint tokens) public returns (bool success); - function approve(address spender, uint tokens) public returns (bool success); - function transferFrom(address from, address to, uint tokens) public returns (bool success); - - event Transfer(address indexed from, address indexed to, uint tokens); - event Approval(address indexed tokenOwner, address indexed spender, uint tokens); -} - -contract SafeMath { - function safeAdd(uint a, uint b) public pure returns (uint c) { - c = a + b; - require(c >= a); - } - function safeSub(uint a, uint b) public pure returns (uint c) { - require(b <= a); - c = a - b; - } - function safeMul(uint a, uint b) public pure returns (uint c) { - c = a * b; - require(a == 0 || c / a == b); - } - function safeDiv(uint a, uint b) public pure returns (uint c) { - require(b > 0); - c = a / b; - } -} - -contract _Base { - address internal owner; - address internal walletCenter; - - modifier onlyOwner { - require(owner == msg.sender); - _; - } - modifier onlyWallet(address _addr) { - require(WalletCenter(walletCenter).isWallet(_addr)); - _; - } - modifier onlyShop(address _addr) { - require(WalletCenter(walletCenter).isShop(_addr)); - _; - } - - function balanceOf(address _erc20) public constant returns (uint256 balance) { - if(_erc20==address(0)) - return address(this).balance; - return ERC20Interface(_erc20).balanceOf(this); - } - - function transfer(address _to, address _erc20, uint256 _value) internal returns (bool success) { - require((_erc20==address(0)?address(this).balance:ERC20Interface(_erc20).balanceOf(this))>=_value); - if(_erc20==address(0)) - _to.transfer(_value); - else - ERC20Interface(_erc20).approve(_to,_value); - return true; - } - - function withdrawal(address _erc20, uint256 _value) public returns (bool success); - - event Pay(address indexed _who, uint256 indexed _item, uint256 indexed _value); - event Refund(address indexed _who, uint256 indexed _item, uint256 indexed _value); - event Prize(address indexed _who, uint256 indexed _item, uint256 indexed _value); -} - -contract _Wallet is _Base { - constructor(address _who) public { - owner = _who; - walletCenter = msg.sender; - } - - function pay(address _shop, uint256 _item) private { - require(_Shop(_shop).canBuy(this,_item)); - - address _erc20; - uint256 _value; - (_erc20,_value) = _Shop(_shop).price(_item); - - transfer(_shop,_erc20,_value); - _Shop(_shop).pay(_item); - emit Pay(_shop,_item,_value); - } - - function paySafe(address _shop, uint256 _item) onlyOwner onlyShop(_shop) public payable returns (bool success) { - pay(_shop,_item); - return true; - } - function payUnsafe(address _shop, uint256 _item) onlyOwner public payable returns (bool success) { - pay(_shop,_item); - return true; - } - function payCancel(address _shop, uint256 _item) onlyOwner public returns (bool success) { - _Shop(_shop).payCancel(_item); - return true; - } - - function refund(address _erc20, uint256 _item, uint256 _value) public payable returns (bool success) { - require((_erc20==address(0)?msg.value:ERC20Interface(_erc20).allowance(msg.sender,this))==_value); - if(_erc20!=address(0)) - ERC20Interface(_erc20).transferFrom(msg.sender,this,_value); - emit Refund(msg.sender,_item,_value); - return true; - } - function prize(address _erc20, uint256 _item, uint256 _value) public payable returns (bool success) { - require((_erc20==address(0)?msg.value:ERC20Interface(_erc20).allowance(msg.sender,this))==_value); - if(_erc20!=address(0)) - ERC20Interface(_erc20).transferFrom(msg.sender,this,_value); - emit Prize(msg.sender,_item,_value); - return true; - } - - function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success) { - require((_erc20==address(0)?address(this).balance:ERC20Interface(_erc20).balanceOf(this))>=_value); - if(_erc20==address(0)) - owner.transfer(_value); - else - ERC20Interface(_erc20).transfer(owner,_value); - return true; - } -} - -contract _Shop is _Base, SafeMath{ - address erc20; - constructor(address _who, address _erc20) public { - owner = _who; - walletCenter = msg.sender; - erc20 = _erc20; - } - - struct item { - uint8 category; // 0 = disable, 1 = non Stock, non Expire, 2 = can Expire (after 1 week), 3 = stackable - uint256 price; - uint256 stockCount; - - mapping(address=>uint256) customer; - } - - uint index; - mapping(uint256=>item) items; - - function pay(uint256 _item) onlyWallet(msg.sender) public payable returns (bool success) { - require(canBuy(msg.sender, _item)); - require((erc20==address(0)?msg.value:ERC20Interface(erc20).allowance(msg.sender,this))==items[_item].price); - - if(erc20!=address(0)) - ERC20Interface(erc20).transferFrom(msg.sender,this,items[_item].price); - - if(items[_item].category==1 || items[_item].category==2 && now > safeAdd(items[_item].customer[msg.sender], 1 weeks)) - items[_item].customer[msg.sender] = now; - else if(items[_item].category==2 && now < safeAdd(items[_item].customer[msg.sender], 1 weeks) ) - items[_item].customer[msg.sender] = safeAdd(items[_item].customer[msg.sender], 1 weeks); - else if(items[_item].category==3) { - items[_item].customer[msg.sender] = safeAdd(items[_item].customer[msg.sender],1); - items[_item].stockCount = safeSub(items[_item].stockCount,1); - } - - emit Pay(msg.sender,_item,items[_item].customer[msg.sender]); - return true; - } - - function payCancel(uint256 _item) onlyWallet(msg.sender) public returns (bool success) { - require (items[_item].category==2&&safeAdd(items[_item].customer[msg.sender],2 weeks)>now&&balanceOf(erc20)>=items[_item].price); - - items[_item].customer[msg.sender] = safeSub(items[_item].customer[msg.sender],1 weeks); - transfer(msg.sender, erc20, items[_item].price); - _Wallet(msg.sender).refund(erc20,_item,items[_item].price); - emit Refund(msg.sender,_item,items[_item].price); - - return true; - } - function refund(address _to, uint256 _item) onlyWallet(_to) onlyOwner public payable returns (bool success) { - require(isBuyer(_to,_item)&&items[_item].category>0&&(items[_item].customer[_to]>0||(items[_item].category==2&&safeAdd(items[_item].customer[_to],2 weeks)>now))); - require((erc20==address(0)?address(this).balance:ERC20Interface(erc20).balanceOf(this))>=items[_item].price); - - if(items[_item].category==1) - items[_item].customer[_to] = 0; - else if(items[_item].category==2) - items[_item].customer[_to] = safeSub(items[_item].customer[_to],1 weeks); - else - items[_item].customer[_to] = safeSub(items[_item].customer[_to],1); - - transfer(_to, erc20, items[_item].price); - _Wallet(_to).refund(erc20,_item,items[_item].price); - emit Refund(_to,_item,items[_item].price); - - return true; - } - - event Item(uint256 indexed _item, uint256 _price); - function resister(uint8 _category, uint256 _price, uint256 _stock) onlyOwner public returns (uint256 _itemId) { - require(_category>0&&_category<4); - require(_price>0); - items[index] = item(_category,_price,_stock); - index = safeAdd(index,1); - emit Item(index,_price); - return safeSub(index,1); - } - function update(uint256 _item, uint256 _price, uint256 _stock) onlyOwner public { - require(items[_item].category>0); - require(_price>0); - uint256 temp = items[_item].price; - items[_item].price = _price; - items[_item].stockCount = safeAdd(items[_item].stockCount,_stock); - - if(temp!=items[_item].price) - emit Item(index,items[_item].price); - } - - function price(uint256 _item) public constant returns (address _erc20, uint256 _value) { - return (erc20,items[_item].price); - } - - function canBuy(address _who, uint256 _item) public constant returns (bool _canBuy) { - return (items[_item].category>0) && - !(items[_item].category==1&&items[_item].customer[_who]>0) && - (items[_item].stockCount>0); - } - - function isBuyer(address _who, uint256 _item) public constant returns (bool _buyer) { - return (items[_item].category==1&&items[_item].customer[_who]>0)||(items[_item].category==2&&safeAdd(items[_item].customer[_who],1 weeks)>now)||(items[_item].category==3&&items[_item].customer[_who]>0); - } - - uint lastWithdrawal; - function withdrawal(address _erc20, uint256 _value) onlyOwner public returns (bool success) { - require(safeAdd(lastWithdrawal,1 weeks)<=now); - require((_erc20==address(0)?address(this).balance:ERC20Interface(_erc20).balanceOf(this))>=_value); - if(_erc20==address(0)) - owner.transfer(_value); - else - ERC20Interface(_erc20).transfer(owner,_value); - lastWithdrawal = now; - return true; - } -} - -contract WalletCenter { - mapping(address=>bool) public wallet; - event Wallet(address indexed _owner, address indexed _wallet); - function createWallet() public returns (address _wallet) { - _wallet = new _Wallet(msg.sender); - wallet[_wallet] = true; - emit Wallet(msg.sender,_wallet); - return _wallet; - } - function isWallet(address _wallet) public constant returns (bool) { - return wallet[_wallet]; - } - mapping(address=>bool) public shop; - event Shop(address indexed _owner, address indexed _shop, address indexed _erc20); - function createShop(address _erc20) public returns (address _shop) { - _shop = new _Shop(msg.sender,_erc20); - shop[_shop] = true; - emit Shop(msg.sender,_shop,_erc20); - return _shop; - } - function isShop(address _shop) public constant returns (bool) { - return shop[_shop]; - } -} -``` -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1175.md diff --git a/EIPS/eip-1178.md b/EIPS/eip-1178.md index 06a740ce42c3c7..24583a93f60104 100644 --- a/EIPS/eip-1178.md +++ b/EIPS/eip-1178.md @@ -1,156 +1 @@ ---- -eip: 1178 -title: Multi-class Token Standard -author: Albert Chon -discussions-to: https://github.com/ethereum/EIPs/issues/1179 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-06-22 ---- - -## Simple Summary -A standard interface for multi-class fungible tokens. -## Abstract -This standard allows for the implementation of a standard API for multi-class fungible tokens (henceforth referred to as "MCFTs") within smart contracts. This standard provides basic functionality to track and transfer ownership of MCFTs. -## Motivation -Currently, there is no standard to support tokens that have multiple classes. In the real world, there are many situations in which defining distinct classes of the same token would be fitting (e.g. distinguishing between preferred/common/restricted shares of a company). Yet, such nuance cannot be supported in today's token standards. An ERC-20 token contract defines tokens that are all of one class while an ERC-721 token contract creates a class (defined by token_id) for each individual token. The ERC-1178 token standard proposes a new standard for creating multiple classes of tokens within one token contract. - -> Aside: In theory, while it is possible to implement tokens with classes using the properties of token structs in ERC-721 tokens, gas costs of implementing this in practice are prohibitive for any non-trivial application. - -## Specification -### ERC-20 Compatibility (partial) -**name** - -```solidity -function name() constant returns (string name) -``` - -*OPTIONAL - It is recommended that this method is implemented for enhanced usability with wallets and exchanges, but interfaces and other contracts MUST NOT depend on the existence of this method.* - -Returns the name of the aggregate collection of MCFTs managed by this contract. - e.g. `"My Company Tokens"`. - -**class name** - -```solidity -function className(uint256 classId) constant returns (string name) -``` - -*OPTIONAL - It is recommended that this method is implemented for enhanced usability with wallets and exchanges, but interfaces and other contracts MUST NOT depend on the existence of this method.* - -Returns the name of the class of MCFT managed by this contract. - e.g. `"My Company Preferred Shares Token"`. - -**symbol** -```solidity -function symbol() constant returns (string symbol) -``` - -*OPTIONAL - It is recommend that this method is implemented for enhanced usability with wallets and exchanges, but interfaces and other contracts MUST NOT depend on the existence of this method.* - -Returns a short string symbol referencing the entire collection of MCFT managed in this contract. e.g. "MUL". This symbol SHOULD be short (3-8 characters is recommended), with no whitespace characters or new-lines and SHOULD be limited to the uppercase latin alphabet (i.e. the 26 letters used in English). - -**totalSupply** -```solidity -function totalSupply() constant returns (uint256 totalSupply) -``` -Returns the total number of all MCFTs currently tracked by this contract. - -**individualSupply** -```solidity -function individualSupply(uint256 _classId) constant returns (uint256 individualSupply) -``` -Returns the total number of MCFTs of class `_classId` currently tracked by this contract. - -**balanceOf** -```solidity -function balanceOf(address _owner, uint256 _classId) constant returns (uint256 balance) -``` - -Returns the number of MCFTs of token class `_classId` assigned to address `_owner`. - -**classesOwned** -```solidity -function classesOwned(address _owner) constant returns (uint256[] classes) -``` - -Returns an array of `_classId`'s of MCFTs that address `_owner` owns in the contract. -> NOTE: returning an array is supported by `pragma experimental ABIEncoderV2` - -## Basic Ownership - -**approve** -```solidity -function approve(address _to, uint256 _classId, uint256 quantity) -``` -Grants approval for address `_to` to take possession `quantity` amount of the MCFT with ID `_classId`. This method MUST `throw` if `balanceOf(msg.sender, _classId) < quantity`, or if `_classId` does not represent an MCFT class currently tracked by this contract, or if `msg.sender == _to`. - -Only one address can "have approval" at any given time for a given address and `_classId`. Calling `approve` with a new address and `_classId` revokes approval for the previous address and `_classId`. Calling this method with 0 as the `_to` argument clears approval for any address and the specified `_classId`. - -Successful completion of this method MUST emit an `Approval` event (defined below) unless the caller is attempting to clear approval when there is no pending approval. In particular, an Approval event MUST be fired if the `_to` address is zero and there is some outstanding approval. Additionally, an Approval event MUST be fired if `_to` is already the currently approved address and this call otherwise has no effect. (i.e. An `approve()` call that "reaffirms" an existing approval MUST fire an event.) - - - -**transfer** -```solidity -function transfer(address _to, uint256 _classId, uint256 quantity) -``` -Assigns the ownership of `quantity` MCFT's with ID `_classId` to `_to` if and only if `quantity == balanceOf(msg.sender, _classId)`. A successful transfer MUST fire the `Transfer` event (defined below). - -This method MUST transfer ownership to `_to` or `throw`, no other outcomes can be possible. Reasons for failure include (but are not limited to): - -* `msg.sender` is not the owner of `quantity` amount of tokens of `_classId`'s. -* `_classId` does not represent an MCFT class currently tracked by this contract - -A conforming contract MUST allow the current owner to "transfer" a token to themselves, as a way of affirming ownership in the event stream. (i.e. it is valid for `_to == msg.sender` if `balanceOf(msg.sender, _classId) >= balance`.) This "no-op transfer" MUST be considered a successful transfer, and therefore MUST fire a `Transfer` event (with the same address for `_from` and `_to`). - -## Advanced Ownership and Exchange -```solidity -function approveForToken(uint256 classIdHeld, uint256 quantityHeld, uint256 classIdWanted, uint256 quantityWanted) -``` -Allows holder of one token to allow another individual (or the smart contract itself) to approve the exchange of their tokens of one class for tokens of another class at their specified exchange rate (see sample implementation for more details). This is equivalent to posting a bid in a marketplace. - -```solidity -function exchange(address to, uint256 classIdPosted, uint256 quantityPosted, uint256 classIdWanted, uint256 quantityWanted) -``` -Allows an individual to fill an existing bid (see above function) and complete the exchange of their tokens of one class for another. In the sample implementation, this function call should fail unless the callee has already approved the contract to transfer their tokens. Of course, it is possible to create an implementation where calling this function implicitly assumes approval and the transfer is completed in one step. - -```solidity -transferFrom(address from, address to, uint256 classId) -``` -Allows a third party to initiate a transfer of tokens from `from` to `to` assuming the approvals have been granted. - -## Events -**Transfer** - -This event MUST trigger when MCFT ownership is transferred via any mechanism. - -Additionally, the creation of new MCFTs MUST trigger a Transfer event for each newly created MCFTs, with a `_from` address of 0 and a `_to` address matching the owner of the new MCFT (possibly the smart contract itself). The deletion (or burn) of any MCFT MUST trigger a Transfer event with a `_to` address of 0 and a `_from` address of the owner of the MCFT (now former owner!). - -NOTE: A Transfer event with `_from == _to` is valid. See the `transfer()` documentation for details. - -```solidity -event Transfer(address indexed _from, address indexed _to, uint256 _classId) -``` - -**Approval** -This event MUST trigger on any successful call to `approve(_to, _classId, quantity)` (unless the caller is attempting to clear approval when there is no pending approval). - -```solidity -event Approval(address indexed _owner, address indexed _approved, uint256 _classId) -``` -## Rationale -### Current Limitations -The design of this project was motivated when I tried to create different classes of fungible ERC-721 tokens (an oxymoron) but ran into gas limits from having to create each tokens individually and maintain them in an efficient data structure for access. Using the maximum gas amount one can send with a transaction on Metamask (a popular web wallet), I was only able to create around 46 ERC-721 tokens before exhausting all gas. This experience motivated the creation of the multi-class fungible token standard. - - -## Backwards Compatibility -Adoption of the MCFT standard proposal would not pose backwards compatibility issues as it defines a new standard for token creation. This standard follows the semantics of ERC-721 as closely as possible, but can't be entirely compatible with it due to the fundamental differences between multi-class fungible and non-fungible tokens. For example, the `ownerOf`, `takeOwnership`, and `tokenOfOwnerByIndex` methods in the ERC-721 token standard cannot be implemented in this standard. Furthermore, the function arguments to `balanceOf`, `approve`, and `transfer` differ as well. - -## Implementation -A sample implementation can be found [here](https://github.com/achon22/ERC-1178/blob/master/erc1178-sample.sol) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1178.md diff --git a/EIPS/eip-1185.md b/EIPS/eip-1185.md index 77964d90c4e817..91e8828956824c 100644 --- a/EIPS/eip-1185.md +++ b/EIPS/eip-1185.md @@ -1,272 +1 @@ ---- -eip: 1185 -title: Storage of DNS Records in ENS -description: A system to store and retrieve DNS records within the ENS contract. -author: Jim McDonald (@mcdee) -discussions-to: https://ethereum-magicians.org/t/eip1185-dns-resolver-profile-for-ens/1589 -status: Review -type: Standards Track -category: ERC -created: 2018-06-26 -requires: 137 ---- - -## Abstract - -This EIP defines a resolver profile for ENS that provides features for storage and lookup of DNS records. This allows ENS to be used as a store of authoritative DNS information. - -## Motivation - -ENS is a highly desirable store for DNS information. It provides the distributed authority of DNS without conflating ownership and authoritative serving of information. With ENS, the owner of a domain has full control over their own DNS records. Also, ENS has the ability (through smart contracts) for a domain's subdomains to be irrevocably assigned to another entity. - -## Specification - -The resolver profile to support DNS on ENS follows the resolver specification as defined in [ERC-137](./eip-137.md). - -Traditionally, DNS is a zone-based system in that all of the records for a zone are kept together in the same file. This has the benefit of simplicity and atomicity of zone updates, but when transposed to ENS can result in significant gas costs for simple changes. As a result, the resolver works on the basis of record sets. A record set is uniquely defined by the tuple `(domain, name, resource record type)`, for example the tuple `(example.com, www.example.com, A)` defines the record set of `A` records for the name `www.example.com` in the domain `example.com`. A record set can contain 0 or more values, for example if `www.example.com` has `A` records `1.2.3.4` and `5.6.7.8` then the aforementioned tuple will have two values. - -The choice to work at the level of record sets rather than zones means that this specification cannot completely support some features of DNS, such as zone transfers and DNSSEC. It would be possible to build a different resolver profile that works at the zone level, however it would be very expensive to carry out updates and so is not considered further for this EIP. - -The DNS resolver interface consists of two functions to set DNS information and two functions to query DNS information. - -### setDNSRecords(bytes32 node, bytes data) - -`setDNSRecords()` sets, updates or clears 1 or more DNS records for a given node. It has function signature `0x0af179d7`. - -The arguments for the function are as follows: - - - node: the namehash of the fully-qualified domain in ENS for which to set the records. Namehashes are defined in [ERC-137](./eip-137.md) - - data: 1 or more DNS records in DNS wire format. Any record that is supplied without a value will be cleared. Note that all records in the same RRset should be contiguous within the data; if not then the later RRsets will overwrite the earlier one(s) - - -### clearDNSZone(bytes32 node) - -`clearDNSZone()` removes all DNS records for the domain. It has function signature `0xad5780af`. - -Although it is possible to clear records individually with `setDNSRecords()` as described above this requires the owner to know all of the records that have been set (as the resolver has no methods to iterate over the records for a given domain), and might require multiple transactions. `clearDNSZone()` removes all zone information in a single operation. - -The arguments for the function is as follows: - - - node: the namehash of the fully-qualified domain in ENS for which to clear the records. Namehashes are defined in [ERC-137](./eip-137.md) - -### dnsRecords(bytes32 node, bytes32 name, uint16 resource) view returns (bytes) - -`dnsRecords()` obtains the DNS records for a given node, name and resource. It has function signature `0x2461e851`. - -The arguments for the function are as follows: - - - node: the namehash of the fully-qualified domain in ENS for which to set the records. Namehashes are defined in [ERC-137](./eip-137.md) - - name: the `keccak256()` hash of the name of the record in DNS wire format. - - resource: the resource record ID. Resource record IDs are defined in RFC1035 and subsequent RFCs. - -The function returns all matching records in DNS wire format. If there are no records present the function will return nothing. - -### hasDNSRecords(bytes32 node, bytes32 name) view returns (bool) - -`hasDNSRecords()` reports if there are any records for the provided name in the domain. It has function signature `0x4cbf6ba4`. - -This function is needed by DNS resolvers when working with wildcard resources as defined in RFC4592. - -The arguments for the function are as follows: - - - node: the namehash of the fully-qualified domain in ENS for which to set the records. Namehashes are defined in [ERC-137](./eip-137.md) - - name: the `keccak256()` hash of the name of the record in DNS wire format. - -The function returns `true` if there are any records for the provided node and name, otherwise `false`. - -## Rationale - -DNS is a federated system of naming, and the higher-level entities control availability of everything beneath them (_e.g._ `.org` controls the availability of `ethereum.org`). A decentralized version of DNS would not have this constraint, and allow lookups directly for any domain with relevant records within ENS. - -## Backwards Compatibility - -Not applicable. - -## Reference Implementation - -The reference implementation of the DNS resolver is as follows: - -```solidity -pragma solidity ^0.7.4; -import "../ResolverBase.sol"; -import "@ensdomains/dnssec-oracle/contracts/RRUtils.sol"; - -abstract contract DNSResolver is ResolverBase { - using RRUtils for *; - using BytesUtils for bytes; - - bytes4 constant private DNS_RECORD_INTERFACE_ID = 0xa8fa5682; - bytes4 constant private DNS_ZONE_INTERFACE_ID = 0x5c47637c; - - // DNSRecordChanged is emitted whenever a given node/name/resource's RRSET is updated. - event DNSRecordChanged(bytes32 indexed node, bytes name, uint16 resource, bytes record); - // DNSRecordDeleted is emitted whenever a given node/name/resource's RRSET is deleted. - event DNSRecordDeleted(bytes32 indexed node, bytes name, uint16 resource); - // DNSZoneCleared is emitted whenever a given node's zone information is cleared. - event DNSZoneCleared(bytes32 indexed node); - - // DNSZonehashChanged is emitted whenever a given node's zone hash is updated. - event DNSZonehashChanged(bytes32 indexed node, bytes lastzonehash, bytes zonehash); - - // Zone hashes for the domains. - // A zone hash is an ERC-1577 content hash in binary format that should point to a - // resource containing a single zonefile. - // node => contenthash - mapping(bytes32=>bytes) private zonehashes; - - // Version the mapping for each zone. This allows users who have lost - // track of their entries to effectively delete an entire zone by bumping - // the version number. - // node => version - mapping(bytes32=>uint256) private versions; - - // The records themselves. Stored as binary RRSETs - // node => version => name => resource => data - mapping(bytes32=>mapping(uint256=>mapping(bytes32=>mapping(uint16=>bytes)))) private records; - - // Count of number of entries for a given name. Required for DNS resolvers - // when resolving wildcards. - // node => version => name => number of records - mapping(bytes32=>mapping(uint256=>mapping(bytes32=>uint16))) private nameEntriesCount; - - /** - * Set one or more DNS records. Records are supplied in wire-format. - * Records with the same node/name/resource must be supplied one after the - * other to ensure the data is updated correctly. For example, if the data - * was supplied: - * a.example.com IN A 1.2.3.4 - * a.example.com IN A 5.6.7.8 - * www.example.com IN CNAME a.example.com. - * then this would store the two A records for a.example.com correctly as a - * single RRSET, however if the data was supplied: - * a.example.com IN A 1.2.3.4 - * www.example.com IN CNAME a.example.com. - * a.example.com IN A 5.6.7.8 - * then this would store the first A record, the CNAME, then the second A - * record which would overwrite the first. - * - * @param node the namehash of the node for which to set the records - * @param data the DNS wire format records to set - */ - function setDNSRecords(bytes32 node, bytes calldata data) external authorised(node) { - uint16 resource = 0; - uint256 offset = 0; - bytes memory name; - bytes memory value; - bytes32 nameHash; - // Iterate over the data to add the resource records - for (RRUtils.RRIterator memory iter = data.iterateRRs(0); !iter.done(); iter.next()) { - if (resource == 0) { - resource = iter.dnstype; - name = iter.name(); - nameHash = keccak256(abi.encodePacked(name)); - value = bytes(iter.rdata()); - } else { - bytes memory newName = iter.name(); - if (resource != iter.dnstype || !name.equals(newName)) { - setDNSRRSet(node, name, resource, data, offset, iter.offset - offset, value.length == 0); - resource = iter.dnstype; - offset = iter.offset; - name = newName; - nameHash = keccak256(name); - value = bytes(iter.rdata()); - } - } - } - if (name.length > 0) { - setDNSRRSet(node, name, resource, data, offset, data.length - offset, value.length == 0); - } - } - - /** - * Obtain a DNS record. - * @param node the namehash of the node for which to fetch the record - * @param name the keccak-256 hash of the fully-qualified name for which to fetch the record - * @param resource the ID of the resource as per https://en.wikipedia.org/wiki/List_of_DNS_record_types - * @return the DNS record in wire format if present, otherwise empty - */ - function dnsRecord(bytes32 node, bytes32 name, uint16 resource) public view returns (bytes memory) { - return records[node][versions[node]][name][resource]; - } - - /** - * Check if a given node has records. - * @param node the namehash of the node for which to check the records - * @param name the namehash of the node for which to check the records - */ - function hasDNSRecords(bytes32 node, bytes32 name) public view returns (bool) { - return (nameEntriesCount[node][versions[node]][name] != 0); - } - - /** - * Clear all information for a DNS zone. - * @param node the namehash of the node for which to clear the zone - */ - function clearDNSZone(bytes32 node) public authorised(node) { - versions[node]++; - emit DNSZoneCleared(node); - } - - /** - * setZonehash sets the hash for the zone. - * May only be called by the owner of that node in the ENS registry. - * @param node The node to update. - * @param hash The zonehash to set - */ - function setZonehash(bytes32 node, bytes calldata hash) external authorised(node) { - bytes memory oldhash = zonehashes[node]; - zonehashes[node] = hash; - emit DNSZonehashChanged(node, oldhash, hash); - } - - /** - * zonehash obtains the hash for the zone. - * @param node The ENS node to query. - * @return The associated contenthash. - */ - function zonehash(bytes32 node) external view returns (bytes memory) { - return zonehashes[node]; - } - - function supportsInterface(bytes4 interfaceID) virtual override public pure returns(bool) { - return interfaceID == DNS_RECORD_INTERFACE_ID || - interfaceID == DNS_ZONE_INTERFACE_ID || - super.supportsInterface(interfaceID); - } - - function setDNSRRSet( - bytes32 node, - bytes memory name, - uint16 resource, - bytes memory data, - uint256 offset, - uint256 size, - bool deleteRecord) private - { - uint256 version = versions[node]; - bytes32 nameHash = keccak256(name); - bytes memory rrData = data.substring(offset, size); - if (deleteRecord) { - if (records[node][version][nameHash][resource].length != 0) { - nameEntriesCount[node][version][nameHash]--; - } - delete(records[node][version][nameHash][resource]); - emit DNSRecordDeleted(node, name, resource); - } else { - if (records[node][version][nameHash][resource].length == 0) { - nameEntriesCount[node][version][nameHash]++; - } - records[node][version][nameHash][resource] = rrData; - emit DNSRecordChanged(node, name, resource, rrData); - } - } -} -``` - -## Security Considerations - -Security of this solution would be dependent on security of the records within the ENS domain. This degenenrates to the security of the key(s) which have authority over that domain. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1185.md diff --git a/EIPS/eip-1191.md b/EIPS/eip-1191.md index 55a744f9fb0ace..51ac9d55d10995 100644 --- a/EIPS/eip-1191.md +++ b/EIPS/eip-1191.md @@ -1,133 +1 @@ ---- -eip: 1191 -title: Add chain id to mixed-case checksum address encoding -author: Juliano Rizzo (@juli) -status: Last Call -last-call-deadline: 2019-11-18 -type: Standards Track -category: ERC -created: 2018-03-18 -requires: 55, 155 -discussions-to: https://github.com/ethereum/EIPs/issues/1121 ---- - -## Simple Summary - -This EIP extends [EIP-55](./eip-55.md) by optionally adding a chain id defined by [EIP-155](./eip-155.md) to the checksum calculation. - -## Abstract - -The [EIP-55](./eip-55.md) was created to prevent users from losing funds by sending them to invalid addresses. This EIP extends [EIP-55](./eip-55.md) to protect users from losing funds by sending them to addresses that are valid but that where obtained from a client of another network.For example, if this EIP is implemented, a wallet can alert the user that is trying to send funds to an Ethereum Testnet address from an Ethereum Mainnet wallet. - -## Motivation - -The motivation of this proposal is to provide a mechanism to allow software to distinguish addresses from different Ethereum based networks. This proposal is necessary because Ethereum addresses are hashes of public keys and do not include any metadata. By extending the [EIP-55](./eip-55.md) checksum algorithm it is possible to achieve this objective. - -## Specification - -Convert the address using the same algorithm defined by [EIP-55](./eip-55.md) but if a registered chain id is provided, add it to the input of the hash function. If the chain id passed to the function belongs to a network that opted for using this checksum variant, prefix the address with the chain id and the `0x` separator before calculating the hash. Then convert the address to hexadecimal, but if the ith digit is a letter (ie. it's one of `abcdef`) print it in uppercase if the 4*ith bit of the calculated hash is 1 otherwise print it in lowercase. - -## Rationale - - Benefits: - - - By means of a minimal code change on existing libraries, users are protected from losing funds by mixing addresses of different Ethereum based networks. - -## Implementation - -```python -#!/usr/bin/python3 -from sha3 import keccak_256 -import random -""" - addr (str): Hexadecimal address, 40 characters long with 2 characters prefix - chainid (int): chain id from EIP-155 """ -def eth_checksum_encode(addr, chainid=1): - adopted_eip1191 = [30, 31] - hash_input = str(chainid) + addr.lower() if chainid in adopted_eip1191 else addr[2:].lower() - hash_output = keccak_256(hash_input.encode('utf8')).hexdigest() - aggregate = zip(addr[2:].lower(),hash_output) - out = addr[:2] + ''.join([c.upper() if int(a,16) >= 8 else c for c,a in aggregate]) - return out -``` - -## Test Cases - -```python -eth_mainnet = [ -"0x27b1fdb04752bbc536007a920d24acb045561c26", -"0x3599689E6292b81B2d85451025146515070129Bb", -"0x42712D45473476b98452f434e72461577D686318", -"0x52908400098527886E0F7030069857D2E4169EE7", -"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", -"0x6549f4939460DE12611948b3f82b88C3C8975323", -"0x66f9664f97F2b50F62D13eA064982f936dE76657", -"0x8617E340B3D01FA5F11F306F4090FD50E238070D", -"0x88021160C5C792225E4E5452585947470010289D", -"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", -"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", -"0xde709f2102306220921060314715629080e2fb77", -"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", -] -rsk_mainnet = [ -"0x27b1FdB04752BBc536007A920D24ACB045561c26", -"0x3599689E6292B81B2D85451025146515070129Bb", -"0x42712D45473476B98452f434E72461577d686318", -"0x52908400098527886E0F7030069857D2E4169ee7", -"0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD", -"0x6549F4939460DE12611948B3F82B88C3C8975323", -"0x66F9664f97f2B50F62d13EA064982F936de76657", -"0x8617E340b3D01Fa5f11f306f4090fd50E238070D", -"0x88021160c5C792225E4E5452585947470010289d", -"0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB", -"0xDBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB", -"0xDe709F2102306220921060314715629080e2FB77", -"0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359", -] -rsk_testnet = [ -"0x27B1FdB04752BbC536007a920D24acB045561C26", -"0x3599689e6292b81b2D85451025146515070129Bb", -"0x42712D45473476B98452F434E72461577D686318", -"0x52908400098527886E0F7030069857D2e4169EE7", -"0x5aAeb6053F3e94c9b9A09F33669435E7EF1BEaEd", -"0x6549f4939460dE12611948b3f82b88C3c8975323", -"0x66f9664F97F2b50f62d13eA064982F936DE76657", -"0x8617e340b3D01fa5F11f306F4090Fd50e238070d", -"0x88021160c5C792225E4E5452585947470010289d", -"0xd1220a0CF47c7B9Be7A2E6Ba89f429762E7b9adB", -"0xdbF03B407C01E7cd3cbEa99509D93f8dDDc8C6fB", -"0xDE709F2102306220921060314715629080e2Fb77", -"0xFb6916095CA1dF60bb79CE92ce3Ea74C37c5D359", -] -test_cases = {30 : rsk_mainnet, 31 : rsk_testnet, 1 : eth_mainnet} - -for chainid, cases in test_cases.items(): - for addr in cases: - assert ( addr == eth_checksum_encode(addr,chainid) ) -``` - -## Usage - -### Usage Table - -| Network | Chain id | Supports this EIP | -|-|-|-| -| RSK Mainnet | 30 | Yes | -| RSK Testnet | 31 | Yes | - -### Implementation Table - -| Project | EIP Usage | Implementation | -|-|-|-| -| MyCrypto | Yes | [JavaScript](https://github.com/MyCryptoHQ/MyCrypto/blob/develop/common/utils/formatters.ts#L126) | -| MyEtherWallet | Yes | [JavaScript](https://github.com/MyEtherWallet/MyEtherWallet/blob/73c4a24f8f67c655749ac990c5b62efd92a2b11a/src/helpers/addressUtils.js#L22) | -| Ledger | Yes | [C](https://github.com/LedgerHQ/ledger-app-eth/blob/master/src_common/ethUtils.c#L203) | -| Trezor | Yes | [Python](https://github.com/trezor/trezor-core/blob/270bf732121d004a4cd1ab129adaccf7346ff1db/src/apps/ethereum/get_address.py#L32) and [C](https://github.com/trezor/trezor-crypto/blob/4153e662b60a0d83c1be15150f18483a37e9092c/address.c#L62) | -| Web3.js | Yes | [JavaScript](https://github.com/ethereum/web3.js/blob/aaf26c8806bc9fb60cf6dcb6658104963c6c7fc7/packages/web3-utils/src/Utils.js#L140) | -| EthereumJS-util | Yes | [JavaScript](https://github.com/ethereumjs/ethereumjs-util/pull/204/commits/cdf0b3c996b05ac5b1f758f17ea9f9ed1847c1eb) | -| ENS address-encoder | Yes | [TypeScript](https://github.com/ensdomains/address-encoder/commit/5bf53b13fa014646ea28c9e5f937361dc9b40590) | - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1191.md diff --git a/EIPS/eip-1202.md b/EIPS/eip-1202.md index c3bcbb1a936ab7..178359c244be86 100644 --- a/EIPS/eip-1202.md +++ b/EIPS/eip-1202.md @@ -1,163 +1 @@ ---- -eip: 1202 -title: Voting Interface -description: A general interface for voting on-chain -author: Zainan Victor Zhou (@xinbenlv), Evan (@evbots), Yin Xu (@yingogobot) -discussions-to: https://ethereum-magicians.org/t/eip-1202-voting-interface/11484 -status: Draft -type: Standards Track -category: ERC -created: 2018-07-08 -requires: 5269 ---- - -## Abstract - -This EIP is an API for implementing voting with smart contract. This standard provides functionalities to voting as well as to view the vote result and set voting status. - -## Motivation - -Voting is one of the earliest example of EVM programming, and also a key to DAO/organizational governance process. We foresee many DAOs will ultimately need to leverage voting as one of the important part of their governance. By creating a voting standard for smart contract / token, we can have the following benefits - -### Benefits of having a standard - -1. Allow general UI and applications to be built on top of a standardized voting to allow more general user to participate, and encourage more DApp and DAO to think about their governance -2. Allow delegate voting / smart contract voting, automatic voting -3. Allow voting results to be recorded on-chain, in a standard way, and allow DAOs and DApps to honor the voting result programmatically. -4. Allow the compatibility with token standard such as [ERC-20](./eip-20.md) or other new standards([ERC-777](./eip-777.md)) and item standard such as [ERC-721](./eip-721.md) -5. Create massive potential for interoperability within Ethereum echo systems and other system. -6. Allow setting voting deadline, allow determine on single or multiple options. Allow requiring voting orders. (trade-off is interface complexity, we might need [ERC-20](./eip-20.md) approach and later a [ERC-777](./eip-777.md) for advanced voting) -7. Recording the voting with weights with token amount. -8. Possibly allow trust-worthy privacy-safe voting and anonymous voting (with either voter address being un-associated with the vote they cast, given a list of randomized/obfuscated voting options). -9. Possibly allow result in reward by voting participation or voting result. - -### Non-Goal / Out of Scope - -1. **Delegation**: We intentionally leave delegation out of scope. A separate EIP could be proposed to address this particular use case. -2. **Eligibility or Weights**: Some of the implementing want to have weights or eligibility to vote to be configurable. Such as OpenZeppelin's implementation of GovernorBravo uses snapshot. Aslo weights calculation such as quadratic voting is not within the scope of this EIP. This EIP is intend to be flexible for -any current and new voting weights calculation. -3. **Proposal**: We intentionally leave Proposal out of scope. Proposals are going to be identified by `proposalId` but what information of the proposal includes, -whether they are on-chain or off-chain and whether they are exeutable, is leaved out from this proposal. A separate EIP could be proposed to address this particular use case. See one of such proposals [ERC-5247](./eip-5247.md) -4. **Signature Aggregations / Endorsement**: When implementing contracts want to allow user to submit their vote or approval of vote offline and have some other -account to generate the transaction, the signature aggregations or endorsements are not in scope of this EIP. A separate EIP could be proposed to address this particular use case. See one of such proposals here [ERC-5453](./eip-5453.md). - -### Use-cases - -1. Determine on issuing new token, issuing more token or issuing sub-token -2. Determine on creating new item under [ERC-721](./eip-721.md) -3. Determine on election on certain person or smart contract to be delegated leader for project or subproject -4. Determine on auditing result ownership allowing migration of smart contract proxy address - -## Specification - -1. Compliant contracts MUST implement the `IERC1202Core` below - -```solidity -interface IERC1202Core { - event VoteCast( - address indexed voter, - uint256 indexed proposalId, - uint8 support, - uint256 weight, - string reason, - bytes extraParams - ); - - function castVote( - uint256 proposalId, - uint8 support, - uint256 weight, - string calldata reasonUri, - bytes calldata extraParams - ) external payable returns; - - function castVoteFrom( - address from, - uint256 proposalId, - uint8 support, - uint256 weight, - string calldata reasonUri, - bytes calldata extraParams - ) external payable returns; - - function execute(uint256 proposalId, bytes memory extraParams) payable external; -} -``` - -2. Compliant contracts MAY implement the `IERC1202MultiVote` Interface. If the intention is for multi-options to be supported, e.g. for ranked-choices -or variant weights voting, Compliant contracts MUST implement `IERC1202MultiVote` Interface. - -```solidity -interface IERC1202MultiVote { - event MultiVoteCast( - address indexed voter, - uint256 indexed proposalId, - uint8[] support, - uint256[] weight, - string reason, - bytes extraParams - ); - - function castMultiVote( - uint256 proposalId, - uint8[] support, - uint256[] weight, - string calldata reasonUri, - bytes calldata extraParams - ) external payable; -} -``` - -3. the compliant contract SHOULD implement [ERC-5269](./eip-5269.md) interface. - - -### Getting Info: Voting Period, Eligibility, Weight - -```solidity -interface IERC1202Info { - function votingPeriodFor(uint256 proposalId) external view returns (uint256 startPointOfTime, uint256 endPointOfTime); - function eligibleVotingWeight(uint256 proposalId, address voter) external view returns (uint256); -} -``` - -## Rationale - -We made the following design decisions and here are the rationales. - -### Granularity and Anonymity - -We created a `view` function `ballotOf` primarily making it easier for people to check the vote from certain address. This has the following assumptions: - -- It's possible to check someone's vote directly given an address. If implementor don't want to make it so easily, they can simply reject all calls to this function. We want to make sure that we support both anonymous voting an non-anonymous voting. However since all calls to a smart contract is logged in block history, there is really no secrecy unless done with cryptography tricks. I am not cryptography-savvy enough to comment on the possibility. Please see "Second Feedback Questions 2018" for related topic. - -- It's assumes for each individual address, they can only vote for one decision. They can distribute their available voting power into more granular level. If implementor wants allow this, they ask the user to create another wallet address and grant the new address certain power. For example, a token based voting where voting weight is determined by the amount of token held by a voter, a voter who wants to distribute its voting power in two different option(option set) can transfer some of the tokens to the new account and cast the votes from both accounts. - -### Weights - -We assume there are `weight` of votes and can be checked by calling `eligibleVotingWeight(proposalId, address voter)`, and the weight distribution is either internally determined or determined by constructor. - -## Backwards Compatibility - -1. The `support` options are chosen to be `uint8` for the purpose to be backward compatible for GovernorBravo. It can be increased in the future - -## Security Considerations - -We expect the voting standard to be used in connection with other contracts such as token distributions, conducting actions in consensus or on behalf of an entity, multi-signature wallets, etc. - -The major security consideration is to ensure only using the standard interface for performing downstream actions or receiving upstream input (vote casting). We expect future audit tool to be based on standard interfaces. - -It's also important to note as discussed in this standard that for the sake of simplicity, this EIP is kept in the very basic form. It can be extended to support many different implementation variations. Such variations might contain different assumptions of the behavior and interpretation of actions. One example would be: What does it mean if someone votes multiple times through `vote`? - -- Would that mean the voter is increasing their weight, or -- vote multiple options in the meanwhile, or -- Does the latter vote override the previous vote? - -Because of the flexible nature of voting, we expect many subsequent standards need to be created as an extension of this EIP. We suggest any extension or implementations of this standard be thoroughly audited before included in large scale or high asset volume applications. - -The third consideration is non-triviality. Some voting applications assume **_anonymity_**, **_randomness_**, **_time-based deadline_**, **_ordering_**, etc, these requirements in Ethereum are known to be non-trivial to achieve. We suggest any applications or organizations rely on audited and time-proven shared libraries when these requirements need to be enforced in their applications. - -The fourth consideration is potential abuse. When voting is standardized and put on contract, it is possible to write another contract that rewards a voter to vote in a certain way. It creates potential issues of bribery and conflict of interest abuse that is previously hard to implement. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1202.md diff --git a/EIPS/eip-1203.md b/EIPS/eip-1203.md index dc971789dd83a7..cff474ba65882c 100644 --- a/EIPS/eip-1203.md +++ b/EIPS/eip-1203.md @@ -1,230 +1 @@ ---- -eip: 1203 -title: ERC-1203 Multi-Class Token Standard (ERC-20 Extension) -author: Jeff Huang , Min Zu -discussions-to: https://github.com/ethereum/EIPs/issues/1203 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-07-01 ---- - -## Simple Summary - -A standard interface for multi-class tokens (MCTs). - -## Abstract - -The following standard allows for the implementation of a standard API for MCTs within smart contracts. This standard provides basic functionality to track, transfer, and convert MCTs. - -## Motivation - -This standard is heavily inspired by ERC-20 Token Standard and ERC-721 Non-Fungible Token Standard. However, whereas these standards are chiefly concerned with representation of items/value in a single class, fungible or note, this proposed standard focus on that of a more complexed, multi-class system. It is fair to think of MCTs as a hybrid of fungible tokens (FT) and non-fungible tokens (NFTs), that is tokens are fungible within the same class but non-fungible with that from a different class. And conversions between classes may be optionally supported. - -MCTs are useful in representing various structures with heterogeneous components, such as: - -- **Abstract Concepts:** A company may have different classes of stocks (e.g. senior preferred, junior preferred, class A common, class B common) that together make up its outstanding equities. A shareholder's position of such company composites of zero or more shares in each class. - -- **Virtual Items:** A sandbox computer game may have many types of resources (e.g. rock, wood, berries, cows, meat, knife, etc.) that together make up that virtual world. A player's inventory has any combination and quantity of these resources - -- **Physical Items:** A supermarket may have many SKUs it has available for purchase (e.g. eggs, milk, beef jerky, beer, etc.). Things get added or removed from a shopper's cart as it moves down the aisle. - -It's sometimes possible, especially with regard to abstract concepts or virtual items, to convert from one class to another, at a specified conversion ratio. When it comes to physical items, such conversion essentially is the implementation of bartering. Though it might generally be easier to introduce a common intermediary class, i.e. money. - -## Specification - -```solidity -contract ERC20 { - function totalSupply() public view returns (uint256); - function balanceOf(address _owner) public view returns (uint256); - function transfer(address _to, uint256 _value) public returns (bool); - function approve(address _spender, uint256 _value) public returns (bool); - function allowance(address _owner, address _spender) public view returns (uint256); - function transferFrom(address _from, address _to, uint256 _value) public returns (bool); - - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); -} - -contract ERC1203 is ERC20 { - function totalSupply(uint256 _class) public view returns (uint256); - function balanceOf(address _owner, uint256 _class) public view returns (uint256); - function transfer(address _to, uint256 _class, uint256 _value) public returns (bool); - function approve(address _spender, uint256 _class, uint256 _value) public returns (bool); - function allowance(address _owner, address _spender, uint256 _class) public view returns (uint256); - function transferFrom(address _from, address _to, uint256 _class, uint256 _value) public returns (bool); - - function fullyDilutedTotalSupply() public view returns (uint256); - function fullyDilutedBalanceOf(address _owner) public view returns (uint256); - function fullyDilutedAllowance(address _owner, address _spender) public view returns (uint256); - function convert(uint256 _fromClass, uint256 _toClass, uint256 _value) public returns (bool); - - event Transfer(address indexed _from, address indexed _to, uint256 _class, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _class, uint256 _value); - event Convert(uint256 indexed _fromClass, uint256 indexed _toClass, uint256 _value); -} -``` - -### ERC-20 Methods and Events (fully compatible) - -Please see [ERC-20 Token Standard](./eip-20.md) for detailed specifications. Do note that these methods and events only work on the "default" class of an MCT. - -```solidity - function totalSupply() public view returns (uint256); - function balanceOf(address _owner) public view returns (uint256); - function transfer(address _to, uint256 _value) public returns (bool); - function approve(address _spender, uint256 _value) public returns (bool); - function allowance(address _owner, address _spender) public view returns (uint256); - function transferFrom(address _from, address _to, uint256 _value) public returns (bool); - - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); -``` - -### Tracking and Transferring - -**totalSupply** - -Returns the total number of tokens in the specified `_class` - -```solidity - function totalSupply(uint256 _class) public view returns (uint256); -``` - -**balanceOf** - -Returns the number of tokens of a specified `_class` that the `_owner` has - -```solidity - function balanceOf(address _owner, uint256 _class) public view returns (uint256); -``` - -**transfer** - -Transfer `_value` tokens of `_class` to address specified by `_to`, return `true` if successful - -```solidity - function transfer(address _to, uint256 _class, uint256 _value) public returns (bool); -``` - -**approve** - -Grant `_spender` the right to transfer `_value` tokens of `_class`, return `true` if successful - -```solidity - function approve(address _spender, uint256 _class, uint256 _value) public returns (bool); -``` - -**allowance** - -Return the number of tokens of `_class` that `_spender` is authorized to transfer on the behalf of `_owner` - -```solidity - function allowance(address _owner, address _spender, uint256 _class) public view returns (uint256); -``` - -**transferFrom** - -Transfer `_value` tokens of `_class` from address specified by `_from` to address specified by `_to` as previously approved, return `true` if successful - -```solidity - function transferFrom(address _from, address _to, uint256 _class, uint256 _value) public returns (bool); -``` - -**Transfer** - -Triggered when tokens are transferred or created, including zero value transfers - -```solidity - event Transfer(address indexed _from, address indexed _to, uint256 _class, uint256 _value); -``` - -**Approval** - -Triggered on successful `approve` - -```solidity - event Approval(address indexed _owner, address indexed _spender, uint256 _class, uint256 _value); -``` - -### Conversion and Dilution - -**fullyDilutedTotalSupply** - -Return the total token supply as if all converted to the lowest common denominator class - -```solidity - function fullyDilutedTotalSupply() public view returns (uint256); -``` - -**fullyDilutedBalanceOf** - -Return the total token owned by `_owner` as if all converted to the lowest common denominator class - -```solidity - function fullyDilutedBalanceOf(address _owner) public view returns (uint256); -``` - -**fullyDilutedAllowance** - -Return the total token `_spender` is authorized to transfer on behalf of `_owner` as if all converted to the lowest common denominator class - -```solidity - function fullyDilutedAllowance(address _owner, address _spender) public view returns (uint256); -``` - -**convert** - -Convert `_value` of `_fromClass` to `_toClass`, return `true` if successful - -```solidity - function convert(uint256 _fromClass, uint256 _toClass, uint256 _value) public returns (bool); -``` - -**Conversion** - -Triggered on successful `convert` - -```solidity - event Conversion(uint256 indexed _fromClass, uint256 indexed _toClass, uint256 _value); -``` - -## Rationale -This standard purposely extends ERC-20 Token Standard so that new MCTs following or existing ERC-20 tokens extending this standard are fully compatible with current wallets and exchanges. In addition, new methods and events are kept as closely to ERC-20 conventions as possible for ease of adoption. - -We have considered alternative implementations to support the multi-class structure, as discussed below, and we found current token standards incapable or inefficient in deal with such structures. - -**Using multiple ERC-20 tokens** - -It is certainly possible to create an ERC-20 token for each class, and a separate contract to coordinate potential conversions, but the short coming in this approach is clearly evident. The rationale behind this standard is to have a single contract to manage multiple classes of tokens. - -**Shoehorning ERC-721 token** - -Treating each token as unique, the non-fungible token standard offers maximum representational flexibility arguably at the expense of convenience. The main challenge of using ERC-721 to represent multi-class token is that separate logic is required to keep track of which tokens belongs to which class, a hacky and unnecessary endeavor. - -**Using ERC-1178 token** - -We came across ERC-1178 as we were putting final touches on our own proposal. The two ERCs look very similar on the surface but we believe there're a few key advantages this one has over ERC-1178. - -- ERC-1178 offers no backward compatibility whereas this proposal is an extension of ERC-20 and therefore fully compatible with all existing wallets and exchanges -- By the same token, existing ERC-20 contracts can extend themselves to adopt this standard and support additional classes without affecting their current behaviors -- This proposal introduces the concept of cross class conversion and dilution, making each token class integral part of a whole system rather than many silos - -## Backwards Compatibility -This EIP is fully compatible with the mandatory methods of ERC20 Token Standard so long as the implementation includes a "lowest common denominator" class, which may be class B common/gold coin/money in the abstract/virtual/physical examples above respectively. Where it is not possible to implement such class, then the implementation should specify a default class for tracking or transferring unless otherwise specified, e.g. US dollar is transferred unless other currency is explicitly specified. - -We find it contrived to require the optional methods of ERC20 Token Standard, `name()`, `symbol()`, and `decimals()`, but developers are certainly free to implement these as they wish. - -## Test Cases -The repository at [jeffishjeff/ERC-1203](https://github.com/jeffishjeff/ERC-1203) contains the [sample test cases](https://github.com/jeffishjeff/ERC-1203/blob/master/token.test.js). - -## Implementation -The repository at [jeffishjeff/ERC-1203](https://github.com/jeffishjeff/ERC-1203) contains the [sample implementation](https://github.com/jeffishjeff/ERC-1203/blob/master/token.sol). - -## References -- ERC-20 Token Standard. ./eip-20.md -- ERC-721 Non-Fungible Token Standard. ./eip-721.md -- ERC-1178 Multi-class Token Standard. ./eip-1178.md - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1203.md diff --git a/EIPS/eip-1207.md b/EIPS/eip-1207.md index 9b9fb0ca34b92c..2afd766736dc7f 100644 --- a/EIPS/eip-1207.md +++ b/EIPS/eip-1207.md @@ -1,169 +1 @@ ---- -eip: 1207 -title: DAuth Access Delegation Standard -author: Xiaoyu Wang (@wxygeek), Bicong Wang (@Wangbicong) -discussions-to: https://github.com/ethereum/EIPs/issues/1207 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-07-10 ---- - -DAuth Access Delegation Standard -===== - -## Simple Summary -DAuth is a standard interface for accessing authorization delegation between smart contracts and users. - -## Abstract -The DAuth protocol defines a set of standard API allowing identity delegations between smart contracts without the user's private key. Identity delegations include accessing and operating a user's data and assets contained in the delegated contracts. - -## Motivation -The inspiration for designing DAuth comes from OAuth protocol that is extensively used in web applications. But unlike the centralized authorization of OAuth, DAuth works in a distributed manner, thus providing much more reliability and generality. - -## Specification -![Rationale](../assets/eip-1207/rationale.png) - -**Resource owner**: the authorizer - -**Resource contract**: the contract providing data and operators - -**API**: the resource contract APIs that the grantee contract can invoke - -**Client contract**: the grantee contract using authorization to access and operate the data - -**Grantee request**: the client contract calls the resource contract with the authorizer authorization - - -**AuthInfo** -``` js -struct AuthInfo { - string[] funcNames; - uint expireAt; -} -``` -Required - The struct contains user authorization information -* `funcNames`: a list of function names callable by the granted contract -* `expireAt`: the authorization expire timestamp in seconds - -**userAuth** -``` js -mapping(address => mapping(address => AuthInfo)) userAuth; -``` -Required - userAuth maps (authorizer address, grantee contract address) pair to the user’s authorization AuthInfo object - -**callableFuncNames** -``` js -string[] callableFuncNames; -``` -Required - All methods that are allowed other contracts to call -* The callable function MUST verify the grantee’s authorization - -**updateCallableFuncNames** -``` js -function updateCallableFuncNames(string _invokes) public returns (bool success); -``` -Optional - Update the callable function list for the client contract by the resource contract's administrator -* `_invokes`: the invoke methods that the client contract can call -* return: Whether the callableFuncNames is updated or not -* This method MUST return success or throw, no other outcomes can be possible - -**verify** -``` js -function verify(address _authorizer, string _invoke) internal returns (bool success); -``` -Required - check the invoke method authority for the client contract -* `_authorizer`: the user address that the client contract agents -* `_invoke`: the invoke method that the client contract wants to call -* return: Whether the grantee request is authorized or not -* This method MUST return success or throw, no other outcomes can be possible - -**grant** -``` js -function grant(address _grantee, string _invokes, uint _expireAt) public returns (bool success); -``` -Required - delegate a client contract to access the user's resource -* `_grantee`: the client contract address -* `_invokes`: the callable methods that the client contract can access. It is a string which contains all function names split by spaces -* `_expireAt`: the authorization expire timestamp in seconds -* return: Whether the grant is successful or not -* This method MUST return success or throw, no other outcomes can be possible -* A successful grant MUST fire the Grant event(defined below) - -**regrant** -``` js -function regrant(address _grantee, string _invokes, uint _expireAt) public returns (bool success); -``` -Optional - alter a client contract's delegation - -**revoke** -``` js -function revoke(address _grantee) public returns (bool success); -``` -Required - delete a client contract's delegation -* `_grantee`: the client contract address -* return: Whether the revoke is successful or not -* A successful revoke MUST fire the Revoke event(defined below). - -**Grant** -``` js -event Grant(address _authorizer, address _grantee, string _invokes, uint _expireAt); -``` -* This event MUST trigger when the authorizer grant a new authorization when `grant` or `regrant` processes successfully - -**Revoke** -``` js -event Revoke(address _authorizer, address _grantee); -``` -* This event MUST trigger when the authorizer revoke a specific authorization successfully - -**Callable Resource Contract Functions** - -All public or external functions that are allowed the grantee to call MUST use overload to implement two functions: The First one is the standard method that the user invokes directly, the second one is the grantee methods of the same function name with one more authorizer address parameter. - -Example: -``` js -function approve(address _spender, uint256 _value) public returns (bool success) { - return _approve(msg.sender, _spender, _value); -} - -function approve(address _spender, uint256 _value, address _authorizer) public returns (bool success) { - verify(_authorizer, "approve"); - - return _approve(_authorizer, _spender, _value); -} - -function _approve(address sender, address _spender, uint256 _value) internal returns (bool success) { - allowed[sender][_spender] = _value; - emit Approval(sender, _spender, _value); - return true; -} -``` - -## Rationale - -**Current Limitations** - -The current design of many smart contracts only considers the user invokes the smart contract functions by themselves using the private key. However, in some case, the user wants to delegate other client smart contracts to access and operate their data or assets in the resource smart contract. There isn’t a common protocol to provide a standard delegation approach. - -**Rationale** - -On the Ethereum platform, all storage is transparent and the `msg.sender` is reliable. Therefore, the DAuth don't need an `access_token` like OAuth. DAuth just recodes the users' authorization for the specific client smart contract's address. It is simple and reliable on the Ethereum platform. - -## Backwards Compatibility -This EIP introduces no backward compatibility issues. In the future, the new version protocol has to keep these interfaces. - -## Implementation -Following is the DAuth Interface implementation. Furthermore, the example implementations of EIP20 Interface and ERC-DAuth Interface are also provided. Developers can easily implement their own contracts with ERC-DAuth Interface and other EIP. - -* ERC-DAuth Interface implementation is available at: - - https://github.com/DIA-Network/ERC-DAuth/blob/master/ERC-DAuth-Interface.sol - -* Example implementation with EIP20 Interface and ERC-DAuth Interface is available at: - - https://github.com/DIA-Network/ERC-DAuth/blob/master/eip20-dauth-example/EIP20DAuth.sol - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1207.md diff --git a/EIPS/eip-1261.md b/EIPS/eip-1261.md index e2bf12a7cd9cbe..b7234edca6e649 100644 --- a/EIPS/eip-1261.md +++ b/EIPS/eip-1261.md @@ -1,390 +1 @@ ---- -eip: 1261 -title: Membership Verification Token (MVT) -author: Chaitanya Potti (@chaitanyapotti), Partha Bhattacharya (@pb25193) -type: Standards Track -category: ERC -status: Stagnant -created: 2018-07-14 -requires: 165, 173 -discussions-to: https://github.com/ethereum/EIPs/issues/1261 ---- - -## Simple Summary - -A standard interface for Membership Verification Token(MVT). - -## Abstract - -The following standard allows for the implementation of a standard API for Membership Verification Token within smart contracts(called entities). This standard provides basic functionality to track membership of individuals in certain on-chain ‘organizations’. This allows for several use cases like automated compliance, and several forms of governance and membership structures. - -We considered use cases of MVTs being assigned to individuals which are non-transferable and revocable by the owner. MVTs can represent proof of recognition, proof of membership, proof of right-to-vote and several such otherwise abstract concepts on the blockchain. The following are some examples of those use cases, and it is possible to come up with several others: - -- Voting: Voting is inherently supposed to be a permissioned activity. So far, onchain voting systems are only able to carry out voting with coin balance based polls. This can now change and take various shapes and forms. -- Passport issuance, social benefit distribution, Travel permit issuance, Drivers licence issuance are all applications which can be abstracted into membership, that is belonging of an individual to a small set, recognized by some authority as having certain entitlements, without needing any individual specific information(right to welfare, freedom of movement, authorization to operate vehicles, immigration) -- Investor permissioning: Making regulatory compliance a simple on chain process. Tokenization of securities, that are streamlined to flow only to accredited addresses, tracing and certifying on chain addresses for AML purposes. -- Software licencing: Software companies like game developers can use the protocol to authorize certain hardware units(consoles) to download and use specific software(games) - -In general, an individual can have different memberships in their day to day life. The protocol allows for the creation of software that puts everything all at one place. Their identity can be verified instantly. Imagine a world where you don't need to carry a wallet full of identity cards (Passport, gym membership, SSN, Company ID etc) and organizations can easily keep track of all its members. Organizations can easily identify and disallow fake identities. - -Attributes are a huge part of ERC-1261 which help to store identifiable information regarding its members. Polls can make use of attributes to calculate the voterbase. -E.g: Users should belong to USA entity and not belong to Washington state attribute to be a part of a poll. - -There will exist a mapping table that maps attribute headers to an array of all possible attributes. This is done in order to subdivide entities into subgroups which are exclusive and exhaustive. For example, -header: blood group alphabet -Array: [ o, a, b, ab ] -header: blood group sign -Array: [ +, - ] - -NOT an example of exclusive exhaustive: -Header: video subscription -Array: [ Netflix, HBO, Amazon ] -Because a person is not necessitated to have EXACTLY one of the elements. He or she may have none or more than one. - -## Motivation - -A standard interface allows any user, applications to work with any MVT on Ethereum. We provide for simple ERC-1261 smart contracts. Additional applications are discussed below. - -This standard is inspired from the fact that voting on the blockchain is done with token balance weights. This has been greatly detrimental to the formation of flexible governance systems on the blockchain, despite the tremendous governance potential that blockchains offer. The idea was to create a permissioning system that allows organizations to vet people once into the organization on the blockchain, and then gain immense flexibility in the kind of governance that can be carried out. - -We have also reviewed other Membership EIPs including EIP-725/735 Claim Registry. A significant difference between #735 claims and #1261 MVTs is information ownership. In #735 the Claim Holder owns any claims made about themselves. The problem with this is that there is no way for a Claim Issuer to revoke or alter a claim once it has been issued. While #735 does specify a removeClaim method, a malicious implementation could simply ignore that method call, because they own the claim. - -Imagine that SafeEmploy™, a background checking company, issues a claim about Timmy. The claim states that Timmy has never been convicted of any felonies. Timmy makes some bad decisions, and now that claim is no longer true. SafeEmploy™ executes removeClaim, but Timmy's #735 contract just ignores it, because Timmy wants to stay employed (and is crypto-clever). #1261 MVTs do not have this problem. Ownership of a badge/claim is entirely determined by the contract issuing the badges, not the one receiving them. The issuer is free to remove or change those badges as they see fit. - -**Trade-off between trustlessness and usability:** -To truly understand the value of the protocol, it is important to understand the trade-off we are treading on. The MVT contract allows the creator to revoke the token, and essentially confiscate the membership of the member in question. To some, this might seem like an unacceptable flaw, however this is a design choice, and not a flaw. -The choice may seem to place a great amount of trust in the individuals who are managing the entity contract(entity owners). If the interests of the entity owner conflict with the interests of the members, the owner may resort to addition of fake addresses(to dominate consensus) or evicting members(to censor unfavourable decisions). At first glance this appears to be a major shortcomings, because the blockchain space is used to absolute removal of authority in most cases. Even the official definition of a dapp requires the absence of any party that manages the services provided by the application. However, the trust in entity owners is not misplaced, if it can be ensured that the interests of entity owners are aligned with the interests of members. -Another criticism of such a system would be that the standard edge of blockchain intermediation - “you cannot bribe the system if you don’t know who to bribe” - no longer holds. It is possible to bribe an entity owner into submission, and get them to censor or fake votes. There are several ways to respond to this argument. First of all, all activities, such as addition of members, and removal of members can be tracked on the blockchain and traces of such activity cannot be removed. It is not difficult to build analytics tools to detect malicious activity(adding 100 fake members suddenly who vote in the direction/sudden removal of a number of members voting in a certain direction). Secondly, the entity owners’ power is limited to the addition and removal of members. This means that they cannot tamper any votes. They can only alter the counting system to include fake voters or remove real voters. Any sensible auditor can identify the malicious/victim addresses and create an open source audit tool to find out the correct results. The biggest loser in this attack will be the entity owner, who has a reputation to lose. -Finally, one must understand why we are taking a step away from trustlessness in this trade-off. The answer is usability. Introducing a permissioning system expands the scope of products and services that can be delivered through the blockchain, while leveraging other aspects of the blockchain(cheap, immutable, no red-tape, secure). Consider the example of the driver licence issuing agency using the ERC-1300 standard. This is a service that simply cannot be deployed in a completely trustless environment. The introduction of permissioned systems expanded the scope of services on the blockchain to cover this particular service. Sure, they have the power to revoke a person’s licence for no reason. But will they? Who stands to lose the most, if the agency acts erratically? The agency itself. Now consider the alternative, the way licences(not necessarily only drivers licence, but say shareholder certificates and so on) are issued, the amount of time consumed, the complete lack of transparency. One could argue that if the legacy systems providing these services really wanted to carry out corruption and nepotism in the execution of these services, the present systems make it much easier to do so. Also, they are not transparent, meaning that there is no way to even detect if they act maliciously. -All that being said, we are very excited to share our proposal with the community and open up to suggestions in this space. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -**Every ERC-1261 compliant contract must implement the `ERC1261`, `ERC173` and `ERC165` interfaces** (subject to "caveats" below): - -```solidity -/// @title ERC-1261 MVT Standard -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1261.md -/// The constructor should define the attribute set for this MVT. -/// Note: the ERC-165 identifier for this interface is 0x1d8362cf. -interface IERC1261 {/* is ERC173, ERC165 */ - /// @dev This emits when a token is assigned to a member. - event Assigned(address indexed _to, uint[] attributeIndexes); - - /// @dev This emits when a membership is revoked. - event Revoked(address indexed _to); - - /// @dev This emits when a user forfeits his membership - event Forfeited(address indexed _to); - - /// @dev This emits when a membership request is accepted - event ApprovedMembership(address indexed _to, uint[] attributeIndexes); - - /// @dev This emits when a membership is requested by an user - event RequestedMembership(address indexed _to); - - /// @dev This emits when data of a member is modified. - /// Doesn't emit when a new membership is created and data is assigned. - event ModifiedAttributes(address indexed _to, uint attributeIndex, uint attributeValueIndex); - - /// @notice Adds a new attribute (key, value) pair to the set of pre-existing attributes. - /// @dev Adds a new attribute at the end of the array of attributes and maps it to `values`. - /// Contract can set a max number of attributes and throw if limit is reached. - /// @param _name Name of the attribute which is to be added. - /// @param values List of values of the specified attribute. - function addAttributeSet(bytes32 _name, bytes32[] calldata values) external; - - /// @notice Modifies the attribute value of a specific attribute for a given `_to` address. - /// @dev Use appropriate checks for whether a user/admin can modify the data. - /// Best practice is to use onlyOwner modifier from ERC173. - /// @param _to The address whose attribute is being modified. - /// @param _attributeIndex The index of attribute which is being modified. - /// @param _modifiedValueIndex The index of the new value which is being assigned to the user attribute. - function modifyAttributeByIndex(address _to, uint _attributeIndex, uint _modifiedValueIndex) external; - - /// @notice Requests membership from any address. - /// @dev Throws if the `msg.sender` already has the token. - /// The individual `msg.sender` can request for a membership if some existing criteria are satisfied. - /// When a membership is requested, this function emits the RequestedMembership event. - /// dev can store the membership request and use `approveRequest` to assign membership later - /// dev can also oraclize the request to assign membership later - /// @param _attributeIndexes the attribute data associated with the member. - /// This is an array which contains indexes of attributes. - function requestMembership(uint[] calldata _attributeIndexes) external payable; - - /// @notice User can forfeit his membership. - /// @dev Throws if the `msg.sender` already doesn't have the token. - /// The individual `msg.sender` can revoke his/her membership. - /// When the token is revoked, this function emits the Revoked event. - function forfeitMembership() external payable; - - /// @notice Owner approves membership from any address. - /// @dev Throws if the `_user` doesn't have a pending request. - /// Throws if the `msg.sender` is not an owner. - /// Approves the pending request - /// Make oraclize callback call this function - /// When the token is assigned, this function emits the `ApprovedMembership` and `Assigned` events. - /// @param _user the user whose membership request will be approved. - function approveRequest(address _user) external; - - /// @notice Owner discards membership from any address. - /// @dev Throws if the `_user` doesn't have a pending request. - /// Throws if the `msg.sender` is not an owner. - /// Discards the pending request - /// Make oraclize callback call this function if criteria are not satisfied - /// @param _user the user whose membership request will be discarded. - function discardRequest(address _user) external; - - /// @notice Assigns membership of an MVT from owner address to another address. - /// @dev Throws if the member already has the token. - /// Throws if `_to` is the zero address. - /// Throws if the `msg.sender` is not an owner. - /// The entity assigns the membership to each individual. - /// When the token is assigned, this function emits the Assigned event. - /// @param _to The address to which the token is assigned. - /// @param _attributeIndexes The attribute data associated with the member. - /// This is an array which contains indexes of attributes. - function assignTo(address _to, uint[] calldata _attributeIndexes) external; - - /// @notice Only Owner can revoke the membership. - /// @dev This removes the membership of the user. - /// Throws if the `_from` is not an owner of the token. - /// Throws if the `msg.sender` is not an owner. - /// Throws if `_from` is the zero address. - /// When transaction is complete, this function emits the Revoked event. - /// @param _from The current owner of the MVT. - function revokeFrom(address _from) external; - - /// @notice Queries whether a member is a current member of the organization. - /// @dev MVT's assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param _to An address for whom to query the membership. - /// @return Whether the member owns the token. - function isCurrentMember(address _to) external view returns (bool); - - /// @notice Gets the value collection of an attribute. - /// @dev Returns the values of attributes as a bytes32 array. - /// @param _name Name of the attribute whose values are to be fetched - /// @return The values of attributes. - function getAttributeExhaustiveCollection(bytes32 _name) external view returns (bytes32[] memory); - - /// @notice Returns the list of all past and present members. - /// @dev Use this function along with isCurrentMember to find wasMemberOf() in Js. - /// It can be calculated as present in getAllMembers() and !isCurrentMember(). - /// @return List of addresses who have owned the token and currently own the token. - function getAllMembers() external view returns (address[]); - - /// @notice Returns the count of all current members. - /// @dev Use this function in polls as denominator to get percentage of members voted. - /// @return Count of current Members. - function getCurrentMemberCount() external view returns (uint); - - /// @notice Returns the list of all attribute names. - /// @dev Returns the names of attributes as a bytes32 array. - /// AttributeNames are stored in a bytes32 Array. - /// Possible values for each attributeName are stored in a mapping(attributeName => attributeValues). - /// AttributeName is bytes32 and attributeValues is bytes32[]. - /// Attributes of a particular user are stored in bytes32[]. - /// Which has a single attributeValue for each attributeName in an array. - /// Use web3.toAscii(data[0]).replace(/\u0000/g, "") to convert to string in JS. - /// @return The names of attributes. - function getAttributeNames() external view returns (bytes32[] memory); - - /// @notice Returns the attributes of `_to` address. - /// @dev Throws if `_to` is the zero address. - /// Use web3.toAscii(data[0]).replace(/\u0000/g, "") to convert to string in JS. - /// @param _to The address whose current attributes are to be returned. - /// @return The attributes associated with `_to` address. - function getAttributes(address _to) external view returns (bytes32[]); - - /// @notice Returns the `attribute` stored against `_to` address. - /// @dev Finds the index of the `attribute`. - /// Throws if the attribute is not present in the predefined attributes. - /// Returns the attributeValue for the specified `attribute`. - /// @param _to The address whose attribute is requested. - /// @param _attributeIndex The attribute Index which is required. - /// @return The attribute value at the specified name. - function getAttributeByIndex(address _to, uint _attributeIndex) external view returns (bytes32); -} - -interface ERC173 /* is ERC165 */ { - /// @dev This emits when ownership of a contract changes. - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /// @notice Get the address of the owner - /// @return The address of the owner. - function owner() external view; - - /// @notice Set the address of the new owner of the contract - /// @param _newOwner The address of the new owner of the contract - function transferOwnership(address _newOwner) external; -} - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -The **metadata extension** is OPTIONAL for ERC-1261 smart contracts (see "caveats", below). This allows your smart contract to be interrogated for its name and for details about the organization which your MV tokens represent. - -```solidity -/// @title ERC-1261 MVT Standard, optional metadata extension -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1261.md -interface ERC1261Metadata /* is ERC1261 */ { - /// @notice A descriptive name for a collection of MVTs in this contract - function name() external view returns (string _name); - - /// @notice An abbreviated name for MVTs in this contract - function symbol() external view returns (string _symbol); -} -``` - -This is the "ERC1261 Metadata JSON Schema" referenced above. - -```json -{ - "title": "Organization Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the organization to which this MVT represents" - }, - "description": { - "type": "string", - "description": "Describes the organization to which this MVT represents" - } - } -} -``` - -### Caveats - -The 0.4.24 Solidity interface grammar is not expressive enough to document the ERC-1261 standard. A contract which complies with ERC-1261 MUST also abide by the following: - -- Solidity issue #3412: The above interfaces include explicit mutability guarantees for each function. Mutability guarantees are, in order weak to strong: `payable`, implicit nonpayable, `view`, and `pure`. Your implementation MUST meet the mutability guarantee in this interface and you MAY meet a stronger guarantee. For example, a `payable` function in this interface may be implemented as nonpayble (no state mutability specified) in your contract. We expect a later Solidity release will allow your stricter contract to inherit from this interface, but a workaround for version 0.4.24 is that you can edit this interface to add stricter mutability before inheriting from your contract. -- Solidity issue #3419: A contract that implements `ERC1261Metadata` SHALL also implement `ERC1261`. -- Solidity issue #2330: If a function is shown in this specification as `external` then a contract will be compliant if it uses `public` visibility. As a workaround for version 0.4.24, you can edit this interface to switch to `public` before inheriting from your contract. -- Solidity issues #3494, #3544: Use of `this.*.selector` is marked as a warning by Solidity, a future version of Solidity will not mark this as an error. - -_If a newer version of Solidity allows the caveats to be expressed in code, then this EIP MAY be updated and the caveats removed, such will be equivalent to the original specification._ - -## Rationale - -There are many potential uses of Ethereum smart contracts that depend on tracking membership. Examples of existing or planned MVT systems are Vault, a DAICO platform, and Stream, a security token framework. Future uses include the implementation of direct democracy, in-game memberships and badges, licence and travel document issuance, electronic voting machine trails, software licencing and many more. - -**MVT Word Choice:** - -Since the tokens are non transferable and revocable, they function like membership cards. Hence the word membership verification token. - -**Transfer Mechanism** - -MVTs can't be transferred. This is a design choice, and one of the features that distinguishes this protocol. -Any member can always ask the issuer to revoke the token from an existing address and assign to a new address. -One can think of the set of MVTs as identifying a user, and you cannot split the user into parts and have it be the same user, but you can transfer a user to a new private key. - -**Assign and Revoke mechanism** - -The assign and revoke functions' documentation only specify conditions when the transaction MUST throw. Your implementation MAY also throw in other situations. This allows implementations to achieve interesting results: - -- **Disallow additional memberships after a condition is met** — Sample contract available on GitHub -- **Blacklist certain address from receiving MV tokens** — Sample contract available on GitHub -- **Disallow additional memberships after a certain time is reached** — Sample contract available on GitHub -- **Charge a fee to user of a transaction** — require payment when calling `assign` and `revoke` so that condition checks from external sources can be made - -**ERC-173 Interface** - -We chose Standard Interface for Ownership (ERC-173) to manage the ownership of a ERC-1261 contract. - -A future EIP/ Zeppelin may create a multi-ownable implementation for ownership. We strongly support such an EIP and it would allow your ERC-1261 implementation to implement `ERC1261Metadata`, or other interfaces by delegating to a separate contract. - -**ERC-165 Interface** - -We chose Standard Interface Detection (ERC-165) to expose the interfaces that a ERC-1261 smart contract supports. - -A future EIP may create a global registry of interfaces for contracts. We strongly support such an EIP and it would allow your ERC-1261 implementation to implement `ERC1261Metadata`, or other interfaces by delegating to a separate contract. - -**Gas and Complexity** (regarding the enumeration extension) - -This specification contemplates implementations that manage a few and _arbitrarily large_ numbers of MVTs. If your application is able to grow then avoid using for/while loops in your code. These indicate your contract may be unable to scale and gas costs will rise over time without bound - -**Privacy** - -Personal information: The protocol does not put any personal information on to the blockchain, so there is no compromise of privacy in that respect. -Membership privacy: The protocol by design, makes it public which addresses are/aren’t members. Without making that information public, it would not be possible to independently audit governance activity or track admin(entity owner) activity. - -**Metadata Choices** (metadata extension) - -We have required `name` and `symbol` functions in the metadata extension. Every token EIP and draft we reviewed (ERC-20, ERC-223, ERC-677, ERC-777, ERC-827) included these functions. - -We remind implementation authors that the empty string is a valid response to `name` and `symbol` if you protest to the usage of this mechanism. We also remind everyone that any smart contract can use the same name and symbol as _your_ contract. How a client may determine which ERC-1261 smart contracts are well-known (canonical) is outside the scope of this standard. - -A mechanism is provided to associate MVTs with URIs. We expect that many implementations will take advantage of this to provide metadata for each MVT system. The URI MAY be mutable (i.e. it changes from time to time). We considered an MVT representing membership of a place, in this case metadata about the organization can naturally change. - -Metadata is returned as a string value. Currently this is only usable as calling from `web3`, not from other contracts. This is acceptable because we have not considered a use case where an on-blockchain application would query such information. - -_Alternatives considered: put all metadata for each asset on the blockchain (too expensive), use URL templates to query metadata parts (URL templates do not work with all URL schemes, especially P2P URLs), multiaddr network address (not mature enough)_ - -**Community Consensus** - -We have been very inclusive in this process and invite anyone with questions or contributions into our discussion. However, this standard is written only to support the identified use cases which are listed herein. - -## Backwards Compatibility - -We have adopted `name` and `symbol` semantics from the ERC-20 specification. - -Example MVT implementations as of July 2018: - -- Membership Verification Token(https://github.com/chaitanyapotti/MembershipVerificationToken) - -## Test Cases - -Membership Verification Token ERC-1261 Token includes test cases written using Truffle. - -## Implementations - -Membership Verification Token ERC1261 -- a reference implementation - -- MIT licensed, so you can freely use it for your projects -- Includes test cases -- Also available as a npm package - npm i membershipverificationtoken - -## References - -**Standards** - -1. ERC-20 Token Standard. ./eip-20.md -1. ERC-165 Standard Interface Detection. ./eip-165.md -1. ERC-725/735 Claim Registry ./eip-725.md -1. ERC-173 Owned Standard. ./eip-173.md -1. JSON Schema. https://json-schema.org/ -1. Multiaddr. https://github.com/multiformats/multiaddr -1. RFC 2119 Key words for use in RFCs to Indicate Requirement Levels. https://www.ietf.org/rfc/rfc2119.txt - -**Issues** - -1. The Original ERC-1261 Issue. https://github.com/ethereum/eips/issues/1261 -1. Solidity Issue \#2330 -- Interface Functions are Axternal. https://github.com/ethereum/solidity/issues/2330 -1. Solidity Issue \#3412 -- Implement Interface: Allow Stricter Mutability. https://github.com/ethereum/solidity/issues/3412 -1. Solidity Issue \#3419 -- Interfaces Can't Inherit. https://github.com/ethereum/solidity/issues/3419 - -**Discussions** - -1. Gitter #EIPs (announcement of first live discussion). https://gitter.im/ethereum/EIPs?at=5b5a1733d2f0934551d37642 -1. ERC-1261 (announcement of first live discussion). https://github.com/ethereum/eips/issues/1261 - -**MVT Implementations and Other Projects** - -1. Membership Verification Token ERC-1261 Token. https://github.com/chaitanyapotti/MembershipVerificationToken - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1261.md diff --git a/EIPS/eip-1271.md b/EIPS/eip-1271.md index 39f9e4e057b925..0b857473690c2c 100644 --- a/EIPS/eip-1271.md +++ b/EIPS/eip-1271.md @@ -1,162 +1 @@ ---- -eip: 1271 -title: Standard Signature Validation Method for Contracts -description: Standard way to verify a signature when the account is a smart contract -author: Francisco Giordano (@frangio), Matt Condon (@shrugs), Philippe Castonguay (@PhABC), Amir Bandeali (@abandeali1), Jorge Izquierdo (@izqui), Bertrand Masius (@catageek) -discussions-to: https://github.com/ethereum/EIPs/issues/1271 -status: Final -type: Standards Track -category: ERC -created: 2018-07-25 ---- - -## Abstract -Externally Owned Accounts (EOA) can sign messages with their associated private keys, but currently contracts cannot. We propose a standard way for any contracts to verify whether a signature on a behalf of a given contract is valid. This is possible via the implementation of a `isValidSignature(hash, signature)` function on the signing contract, which can be called to validate a signature. - -## Motivation - -There are and will be many contracts that want to utilize signed messages for validation of rights-to-move assets or other purposes. In order for these contracts to be able to support non Externally Owned Accounts (i.e., contract owners), we need a standard mechanism by which a contract can indicate whether a given signature is valid or not on its behalf. - -One example of an application that requires signatures to be provided would be decentralized exchanges with off-chain orderbook, where buy/sell orders are signed messages. In these applications, EOAs sign orders, signaling their desire to buy/sell a given asset and giving explicit permissions to the exchange smart contracts to conclude a trade via a signature. When it comes to contracts however, regular signatures are not possible since contracts do not possess a private key, hence this proposal. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). - -```javascript -pragma solidity ^0.5.0; - -contract ERC1271 { - - // bytes4(keccak256("isValidSignature(bytes32,bytes)") - bytes4 constant internal MAGICVALUE = 0x1626ba7e; - - /** - * @dev Should return whether the signature provided is valid for the provided hash - * @param _hash Hash of the data to be signed - * @param _signature Signature byte array associated with _hash - * - * MUST return the bytes4 magic value 0x1626ba7e when function passes. - * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) - * MUST allow external calls - */ - function isValidSignature( - bytes32 _hash, - bytes memory _signature) - public - view - returns (bytes4 magicValue); -} -``` - -`isValidSignature` can call arbitrary methods to validate a given signature, which could be context dependent (e.g. time based or state based), EOA dependent (e.g. signers authorization level within smart wallet), signature scheme Dependent (e.g. ECDSA, multisig, BLS), etc. - -This function should be implemented by contracts which desire to sign messages (e.g. smart contract wallets, DAOs, multisignature wallets, etc.) Applications wanting to support contract signatures should call this method if the signer is a contract. - - -## Rationale -We believe the name of the proposed function to be appropriate considering that an *authorized* signers providing proper signatures for a given data would see their signature as "valid" by the signing contract. Hence, a signed action message is only valid when the signer is authorized to perform a given action on the behalf of a smart wallet. - -Two arguments are provided for simplicity of separating the hash signed from the signature. A bytes32 hash is used instead of the unhashed message for simplicity, since contracts could expect a certain hashing function that is not standard, such as with [EIP-712](./eip-712.md). - -`isValidSignature()` should not be able to modify states in order to prevent `GasToken` minting or similar attack vectors. Again, this is to simplify the implementation surface of the function for better standardization and to allow off-chain contract queries. - -The specific return value is expected to be returned instead of a boolean in order to have stricter and simpler verification of a signature. - -## Backwards Compatibility - -This EIP is backward compatible with previous work on signature validation since this method is specific to contract based signatures and not EOA signatures. - -## Reference Implementation - -Example implementation of a signing contract: - -```solidity - - /** - * @notice Verifies that the signer is the owner of the signing contract. - */ - function isValidSignature( - bytes32 _hash, - bytes calldata _signature - ) external override view returns (bytes4) { - // Validate signatures - if (recoverSigner(_hash, _signature) == owner) { - return 0x1626ba7e; - } else { - return 0xffffffff; - } - } - - /** - * @notice Recover the signer of hash, assuming it's an EOA account - * @dev Only for EthSign signatures - * @param _hash Hash of message that was signed - * @param _signature Signature encoded as (bytes32 r, bytes32 s, uint8 v) - */ - function recoverSigner( - bytes32 _hash, - bytes memory _signature - ) internal pure returns (address signer) { - require(_signature.length == 65, "SignatureValidator#recoverSigner: invalid signature length"); - - // Variables are not scoped in Solidity. - uint8 v = uint8(_signature[64]); - bytes32 r = _signature.readBytes32(0); - bytes32 s = _signature.readBytes32(32); - - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - // - // Source OpenZeppelin - // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol - - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - revert("SignatureValidator#recoverSigner: invalid signature 's' value"); - } - - if (v != 27 && v != 28) { - revert("SignatureValidator#recoverSigner: invalid signature 'v' value"); - } - - // Recover ECDSA signer - signer = ecrecover(_hash, v, r, s); - - // Prevent signer from being 0x0 - require( - signer != address(0x0), - "SignatureValidator#recoverSigner: INVALID_SIGNER" - ); - - return signer; - } -``` - -Example implementation of a contract calling the isValidSignature() function on an external signing contract ; - -```solidity - function callERC1271isValidSignature( - address _addr, - bytes32 _hash, - bytes calldata _signature - ) external view { - bytes4 result = IERC1271Wallet(_addr).isValidSignature(_hash, _signature); - require(result == 0x1626ba7e, "INVALID_SIGNATURE"); - } -``` - -## Security Considerations -Since there are no gas-limit expected for calling the isValidSignature() function, it is possible that some implementation will consume a large amount of gas. It is therefore important to not hardcode an amount of gas sent when calling this method on an external contract as it could prevent the validation of certain signatures. - -Note also that each contract implementing this method is responsible to ensure that the signature passed is indeed valid, otherwise catastrophic outcomes are to be expected. - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1271.md diff --git a/EIPS/eip-1319.md b/EIPS/eip-1319.md index 92ab5560e002b7..0b90595fa5da81 100644 --- a/EIPS/eip-1319.md +++ b/EIPS/eip-1319.md @@ -1,176 +1 @@ ---- -eip: 1319 -title: Smart Contract Package Registry Interface -author: Piper Merriam , Christopher Gewecke , g. nicholas d'andrea , Nick Gheorghita (@njgheorghita) -type: Standards Track -category: ERC -status: Stagnant -created: 2018-08-13 -discussions-to: https://github.com/ethereum/EIPs/issues/1319 ---- - -## Simple Summary -A standard interface for smart contract package registries. - -## Abstract -This EIP specifies an interface for publishing to and retrieving assets from smart contract package registries. It is a companion EIP to [1123](./eip-1123.md) which defines a standard for smart contract package manifests. - -## Motivation -The goal is to establish a framework that allows smart contract publishers to design and deploy code registries with arbitrary business logic while exposing a set of common endpoints that tooling can use to retrieve assets for contract consumers. - -A clear standard would help the existing EthPM Package Registry evolve from a centralized, single-project community resource into a decentralized multi-registry system whose constituents are bound together by the proposed interface. In turn, these registries could be ENS name-spaced, enabling installation conventions familiar to users of `npm` and other package managers. - -**Examples** -```shell -$ ethpm install packages.zeppelin.eth/Ownership -``` - -```javascript -const SimpleToken = await web3.packaging - .registry('packages.ethpm.eth') - .getPackage('simple-token') - .getVersion('^1.1.5'); -``` - -## Specification -The specification describes a small read/write API whose components are mandatory. It allows registries to manage versioned releases using the conventions of [semver](https://semver.org/) without imposing this as a requirement. It assumes registries will share the following structure and conventions: - -+ a **registry** is a deployed contract which manages a collection of **packages**. -+ a **package** is a collection of **releases** -+ a **package** is identified by a unique string name and unique bytes32 **packageId** within a given **registry** -+ a **release** is identified by a `bytes32` **releaseId** which must be unique for a given package name and release version string pair. -+ a **releaseId** maps to a set of data that includes a **manifestURI** string which describes the location of an [EIP 1123 package manifest](./eip-1123.md). This manifest contains data about the release including the location of its component code assets. -+ a **manifestURI** is a URI as defined by [RFC3986](https://tools.ietf.org/html/rfc3986) which can be used to retrieve the contents of the package manifest. In addition to validation against RFC3986, each **manifestURI** must also contain a hash of the content as specified in the [EIP-1123](./eip-1123.md). - -### Examples - -**Package Names / Release Versions** - -```shell -"simple-token" # package name -"1.0.1" # version string -``` - -**Release IDs** - -Implementations are free to choose any scheme for generating a **releaseId**. A common approach would be to hash the strings together as below: - -```solidity -// Hashes package name and a release version string -function generateReleaseId(string packageName, string version) - public - view - returns (bytes32 releaseId) - { - return keccak256(abi.encodePacked(packageName, version)); - } -``` -Implementations **must** expose this id generation logic as part of their public `read` API so -tooling can easily map a string based release query to the registry's unique identifier for that release. - -**Manifest URIs** - -Any IPFS or Swarm URI meets the definition of **manifestURI**. - -Another example is content on GitHub addressed by its SHA-1 hash. The Base64 encoded content at this hash can be obtained by running: -```shell -$ curl https://api.github.com/repos/:owner/:repo/git/blobs/:file_sha - -# Example -$ curl https://api.github.com/repos/rstallman/hello/git/blobs/ce013625030ba8dba906f756967f9e9ca394464a -``` - -The string "hello" can have its GitHub SHA-1 hash independently verified by comparing it to the output of: -```shell -$ printf "blob 6\000hello\n" | sha1sum -> ce013625030ba8dba906f756967f9e9ca394464a -``` - -### Write API Specification -The write API consists of a single method, `release`. It passes the registry the package name, a -version identifier for the release, and a URI specifying the location of a manifest which -details the contents of the release. -```solidity -function release(string packageName, string version, string manifestURI) public - returns (bytes32 releaseId); -``` - -### Events - -#### VersionRelease -MUST be triggered when `release` is successfully called. - -```solidity -event VersionRelease(string packageName, string version, string manifestURI) -``` - -### Read API Specification - -The read API consists of a set of methods that allows tooling to extract all consumable data from a registry. - -```solidity -// Retrieves a slice of the list of all unique package identifiers in a registry. -// `offset` and `limit` enable paginated responses / retrieval of the complete set. (See note below) -function getAllPackageIds(uint offset, uint limit) public view - returns ( - bytes32[] packageIds, - uint pointer - ); - -// Retrieves the unique string `name` associated with a package's id. -function getPackageName(bytes32 packageId) public view returns (string packageName); - -// Retrieves the registry's unique identifier for an existing release of a package. -function getReleaseId(string packageName, string version) public view returns (bytes32 releaseId); - -// Retrieves a slice of the list of all release ids for a package. -// `offset` and `limit` enable paginated responses / retrieval of the complete set. (See note below) -function getAllReleaseIds(string packageName, uint offset, uint limit) public view - returns ( - bytes32[] releaseIds, - uint pointer - ); - -// Retrieves package name, release version and URI location data for a release id. -function getReleaseData(bytes32 releaseId) public view - returns ( - string packageName, - string version, - string manifestURI - ); - -// Retrieves the release id a registry *would* generate for a package name and version pair -// when executing a release. -function generateReleaseId(string packageName, string version) - public - view - returns (bytes32 releaseId); - -// Returns the total number of unique packages in a registry. -function numPackageIds() public view returns (uint totalCount); - -// Returns the total number of unique releases belonging to the given packageName in a registry. -function numReleaseIds(string packageName) public view returns (uint totalCount); -``` -**Pagination** - -`getAllPackageIds` and `getAllReleaseIds` support paginated requests because it's possible that the return values for these methods could become quite large. The methods should return a `pointer` that points to the next available item in a list of all items such that a caller can use it to pick up from where the previous request left off. (See [here](https://mixmax.com/blog/api-paging-built-the-right-way) for a discussion of the merits and demerits of various pagination strategies.) The `limit` parameter defines the maximum number of items a registry should return per request. - -## Rationale -The proposal hopes to accomplish the following: - -+ Define the smallest set of inputs necessary to allow registries to map package names to a set of -release versions while allowing them to use any versioning schema they choose. -+ Provide the minimum set of getter methods needed to retrieve package data from a registry so that registry aggregators can read all of their data. -+ Define a standard query that synthesizes a release identifier from a package name and version pair so that tooling can resolve specific package version requests without needing to query a registry about all of a package's releases. - -Registries may offer more complex `read` APIs that manage requests for packages within a semver range or at `latest` etc. This EIP is agnostic about how tooling or registries might implement these. It recommends that registries implement [EIP-165](./eip-165.md) and avail themselves of resources to publish more complex interfaces such as [EIP-926](./eip-926.md). - -## Backwards Compatibility -No existing standard exists for package registries. The package registry currently deployed by EthPM would not comply with the standard since it implements only one of the method signatures described in the specification. - -## Implementation -A reference implementation of this proposal is in active development at the EthPM organization on GitHub [here](https://github.com/ethpm/escape-truffle). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1319.md diff --git a/EIPS/eip-1328.md b/EIPS/eip-1328.md index ddc15279daa58c..32be4f354edc5d 100644 --- a/EIPS/eip-1328.md +++ b/EIPS/eip-1328.md @@ -1,72 +1 @@ ---- -eip: 1328 -title: WalletConnect URI Format -description: Define URI format for initiating connections between applications and wallets -author: ligi (@ligi), Pedro Gomes (@pedrouid) -discussions-to: https://ethereum-magicians.org/t/wallet-connect-eip/850 -status: Review -type: Standards Track -category: ERC -created: 2018-08-15 ---- - -## Abstract - -This standard defines how the data to connect some application and a wallet can be encoded with a URI. This URI can then be shown either as a QR code or as a link. - -## Specification - -### Syntax - -WalletConnect request URI with the following parameters: - - request = "wc" ":" topic [ "@" version ][ "?" parameters ] - topic = STRING - version = 1*DIGIT - parameters = parameter *( "&" parameter ) - parameter = key "=" value - key = STRING - value = STRING - -### Semantics - -Required parameters are dependent on the WalletConnect protocol version: - -For WalletConnect v1.0 protocol (`version`=`1`) the parameters are: - -- `key` - symmetric key used for encryption -- `bridge` - url of the bridge server for relaying messages - -For WalletConnect v2.0 protocol (`version`=`2`) the parameters are: - -- `symKey` - symmetric key used for encrypting messages over relay -- `methods` - jsonrpc methods supported for pairing topic -- `relay-protocol` - transport protocol for relaying messages -- `relay-data` - (optional) transport data for relaying messages - - -### Example - -``` -# 1.0 -wc:8a5e5bdc-a0e4-4702-ba63-8f1a5655744f@1?bridge=https%3A%2F%2Fbridge.walletconnect.org&key=41791102999c339c844880b23950704cc43aa840f3739e365323cda4dfa89e7a - -# 2.0 -wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?relay-protocol=irn&symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&methods=[wc_sessionPropose],[wc_authRequest,wc_authBatchRequest]" -``` - -## Rationale - -This proposal moves away from the JSON format used in the alpha version of the WalletConnect protocol because it suffered from very inefficient parsing of the intent of the QR code, thereby making it easier to create better QR code parsers APIs for wallets to implement. Also by using a URI instead of JSON inside the QR-Code the Android Intent system can be leveraged. - -## Backwards Compatibility - -Versioning is required as part of the syntax for this URI specification to allow the WalletConnect protocol to evolve and allow backwards-compatibility whenever a new version is introduced. - -## Security Considerations - -URIs should be shared between user devices or applications and no sensitive data is shared within the URI that could compromise the communication or would allow control of the user's private keys. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1328.md diff --git a/EIPS/eip-1337.md b/EIPS/eip-1337.md index 665756b47070da..381479f83b3b79 100644 --- a/EIPS/eip-1337.md +++ b/EIPS/eip-1337.md @@ -1,247 +1 @@ ---- -eip: 1337 -title: Subscriptions on the blockchain -author: Kevin Owocki , Andrew Redden , Scott Burke , Kevin Seagraves , Luka Kacil , Štefan Šimec , Piotr Kosiński (@kosecki123), ankit raj , John Griffin , Nathan Creswell -discussions-to: https://ethereum-magicians.org/t/eip-1337-subscriptions-on-the-blockchain/4422 -type: Standards Track -status: Stagnant -category: ERC -created: 2018-08-01 -requires: 20, 165 ---- - -## Simple Summary -Monthly subscriptions are a key monetization channel for legacy web, and arguably they are the most healthy monetization channel for businesses on the legacy web (especially when compared to ad/surveillance) based models. They are arguably more healthy than a token based economic system (depending upon the vesting model of the ICO) because - -##### For a user: - * you don't have to read a complex whitepaper to use a dapps utility (as opposed to utility tokens) -* you don't have to understand the founder's vesting schedules -* you can cancel anytime - -##### For a Service Provider: -* since you know your subscriber numbers, churn numbers, conversion rate, you get consistent cash flow, and accurate projections -* you get to focus on making your customers happy -* enables you to remove speculators from your ecosystem - -For these reasons, we think it's imperative to create a standard way to do 'subscriptions' on Ethereum. - -## Abstract -To enable replay-able transactions users sign a concatenated bytes hash that is composed of the input data needed to execute the transaction. This data is stored off chain by the recipient of the payment and is transmitted to the customers smart contract for execution alongside a provided signature. - -## Motivation -Recurring payments are the bedrock of SaSS and countless other businesses, a robust specification for defining this interaction will enable a broad spectrum of revenue generation and business models. - -## Specification -#### Enum Contract - -EIP-1337 Contracts should be compiled with a contract that references all the enumerations that are required for operation - -```SOLIDITY -/// @title Enum - Collection of enums -/// Original concept from Richard Meissner - Gnosis safe contracts -contract Enum { - enum Operation { - Call, - DelegateCall, - Create, - ERC20, - ERC20Approve - } - enum SubscriptionStatus { - ACTIVE, - PAUSED, - CANCELLED, - EXPIRED - } - - enum Period { - INIT, - DAY, - WEEK, - MONTH - } -} -``` - -#### EIP-165 - -EIP-1337 compliant contracts support EIP-165 announcing what interfaces they support - -```SOLIDITY -interface ERC165 { - /** - * @notice Query if a contract implements an interface - * @param interfaceID The interface identifier, as specified in ERC-165 - * @dev Interface identification is specified in ERC-165. This function - * uses less than 30,000 gas. - * @return `true` if the contract implements `interfaceID` and - * `interfaceID` is not 0xffffffff, `false` otherwise - **/ - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -#### Public View Functions - -###### isValidSubscription -```SOLIDITY - -/** @dev Checks if the subscription is valid. - * @param bytes subscriptionHash is the identifier of the customer's subscription with its relevant details. - * @return success is the result of whether the subscription is valid or not. - **/ - -function isValidSubscription( - uint256 subscriptionHash - ) - public - view - returns ( - bool success - ) -``` -###### getSubscriptionStatus -```SOLIDITY - -/** @dev returns the value of the subscription - * @param bytes subscriptionHash is the identifier of the customer's subscription with its relevant details. - * @return status is the enumerated status of the current subscription, 0 expired, 1 active, 2 paused, 3 cancelled - **/ -function getSubscriptionStatus( - uint256 subscriptionHash - ) - public - view - returns ( - uint256 status, - uint256 nextWithdraw - ) -``` - -###### getSubscriptionHash - -```SOLIDITY -/** @dev returns the hash of cocatenated inputs to the address of the contract holding the logic., - * the owner would sign this hash and then provide it to the party for execution at a later date, - * this could be viewed like a cheque, with the exception that unless you specifically - * capture the hash on chain a valid signature will be executable at a later date, capturing the hash lets you modify the status to cancel or expire it. - * @param address recipient the address of the person who is getting the funds. - * @param uint256 value the value of the transaction - * @param bytes data the data the user is agreeing to - * @param uint256 txGas the cost of executing one of these transactions in gas(probably safe to pad this) - * @param uint256 dataGas the cost of executing the data portion of the transaction(delegate calls etc) - * @param uint 256 gasPrice the agreed upon gas cost of Execution of this subscription(cost incurment is up to implementation, ie, sender or receiver) - * @param address gasToken address of the token in which gas will be compensated by, address(0) is ETH, only works in the case of an enscrow implementation) - * @param bytes meta dynamic bytes array with 4 slots, 2 required, 2 optional // address refundAddress / uint256 period / uint256 offChainID / uint256 expiration (uinx timestamp) - * @return bytes32, return the hash input arguments concatenated to the address of the contract that holds the logic. - **/ -function getSubscriptionHash( - address recipient, - uint256 value, - bytes data, - Enum.Operation operation, - uint256 txGas, - uint256 dataGas, - uint256 gasPrice, - address gasToken, - bytes meta - ) - public - view - returns ( - bytes32 subscriptionHash - ) -``` - - -###### getModifyStatusHash - -```SOLIDITY -/** @dev returns the hash of concatenated inputs that the owners user would sign with their public keys - * @param address recipient the address of the person who is getting the funds. - * @param uint256 value the value of the transaction - * @return bytes32 returns the hash of concatenated inputs with the address of the contract holding the subscription hash - **/ -function getModifyStatusHash( - bytes32 subscriptionHash - Enum.SubscriptionStatus status - ) - public - view - returns ( - bytes32 modifyStatusHash - ) -``` -#### Public Functions - -###### modifyStatus -```SOLIDITY - -/** @dev modifys the current subscription status - * @param uint256 subscriptionHash is the identifier of the customer's subscription with its relevant details. - * @param Enum.SubscriptionStatus status the new status of the subscription - * @param bytes signatures of the requested method being called - * @return success is the result of the subscription being paused - **/ -function modifyStatus( - uint256 subscriptionHash, - Enum.SubscriptionStatus status, - bytes signatures - ) - public - returns ( - bool success - ) -``` - -###### executeSubscription -```SOLIDITY - -/** @dev returns the hash of cocatenated inputs to the address of the contract holding the logic., - * the owner would sign this hash and then provide it to the party for execution at a later date, - * this could be viewed like a cheque, with the exception that unless you specifically - * capture the hash on chain a valid signature will be executable at a later date, capturing the hash lets you modify the status to cancel or expire it. - * @param address recipient the address of the person who is getting the funds. - * @param uint256 value the value of the transaction - * @param bytes data the data the user is agreeing to - * @param uint256 txGas the cost of executing one of these transactions in gas(probably safe to pad this) - * @param uint256 dataGas the cost of executing the data portion of the transaction(delegate calls etc) - * @param uint 256 gasPrice the agreed upon gas cost of Execution of this subscription(cost incurment is up to implementation, ie, sender or receiver) - * @param address gasToken address of the token in which gas will be compensated by, address(0) is ETH, only works in the case of an enscrow implementation) - * @param bytes meta dynamic bytes array with 4 slots, 2 required, 2 optional // address refundAddress / uint256 period / uint256 offChainID / uint256 expiration (uinx timestamp) - * @param bytes signatures signatures concatenated that have signed the inputs as proof of valid execution - * @return bool success something to note that a failed execution will still pay the issuer of the transaction for their gas costs. - **/ -function executeSubscription( - address to, - uint256 value, - bytes data, - Enum.Operation operation, - uint256 txGas, - uint256 dataGas, - uint256 gasPrice, - address gasToken, - bytes meta, - bytes signatures - ) - public - returns ( - bool success - ) -``` - -## Rationale -Merchants who accept credit-cards do so by storing a token that is retrieved from a third party processor(stripe, paypal, etc), this token is used to grant access to pull payment from the cx's credit card provider and move funds to the merchant account. -Having users sign input data acts in a similliar fashion and enables that merchant to store the signature of the concatenated bytes hash and input data used to generate the hash and pass them off to the contract holding the subscription logic, thus enabling a workflow that is similliar to what exists in the present day legacy web. - -## Backwards Compatibility -N/A - -## Test Cases -TBD - -## Implementation -TBD - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1337.md diff --git a/EIPS/eip-1363.md b/EIPS/eip-1363.md index d4984c37998c9f..07637ba527fa76 100644 --- a/EIPS/eip-1363.md +++ b/EIPS/eip-1363.md @@ -1,208 +1 @@ ---- -eip: 1363 -title: Payable Token -author: Vittorio Minacori (@vittominacori) -discussions-to: https://github.com/ethereum/eips/issues/1363 -status: Final -type: Standards Track -category: ERC -created: 2020-01-31 -requires: 20, 165 ---- - -## Simple Summary -Defines a token interface for [ERC-20](./eip-20.md) tokens that supports executing recipient code after `transfer` or `transferFrom`, or spender code after `approve`. - -## Abstract -Standard functions a token contract and contracts working with tokens can implement to make a token Payable. - -`transferAndCall` and `transferFromAndCall` will call an `onTransferReceived` on a `ERC1363Receiver` contract. - -`approveAndCall` will call an `onApprovalReceived` on a `ERC1363Spender` contract. - -## Motivation -There is no way to execute code after a [ERC-20](./eip-20.md) transfer or approval (i.e. making a payment), so to make an action it is required to send another transaction and pay GAS twice. - -This proposal wants to make token payments easier and working without the use of any other listener. It allows to make a callback after a transfer or approval in a single transaction. - -There are many proposed uses of Ethereum smart contracts that can accept [ERC-20](./eip-20.md) payments. - -Examples could be -* to create a token payable crowdsale -* selling services for tokens -* paying invoices -* making subscriptions - -For these reasons it was named as **"Payable Token"**. - -Anyway you can use it for specific utilities or for any other purposes who require the execution of a callback after a transfer or approval received. - -This proposal has been inspired by the [ERC-721](./eip-721.md) `onERC721Received` and `ERC721TokenReceiver` behaviours. - -## Specification -Implementing contracts **MUST** implement the [ERC-1363](./eip-1363.md) interface as well as the [ERC-20](./eip-20.md) and [ERC-165](./eip-165.md) interfaces. - -```solidity -pragma solidity ^0.8.0; - -interface ERC1363 /* is ERC20, ERC165 */ { - /* - * Note: the ERC-165 identifier for this interface is 0xb0202a11. - * 0xb0202a11 === - * bytes4(keccak256('transferAndCall(address,uint256)')) ^ - * bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^ - * bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^ - * bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^ - * bytes4(keccak256('approveAndCall(address,uint256)')) ^ - * bytes4(keccak256('approveAndCall(address,uint256,bytes)')) - */ - - /** - * @notice Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver - * @param to address The address which you want to transfer to - * @param value uint256 The amount of tokens to be transferred - * @return true unless throwing - */ - function transferAndCall(address to, uint256 value) external returns (bool); - - /** - * @notice Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver - * @param to address The address which you want to transfer to - * @param value uint256 The amount of tokens to be transferred - * @param data bytes Additional data with no specified format, sent in call to `to` - * @return true unless throwing - */ - function transferAndCall(address to, uint256 value, bytes memory data) external returns (bool); - - /** - * @notice Transfer tokens from one address to another and then call `onTransferReceived` on receiver - * @param from address The address which you want to send tokens from - * @param to address The address which you want to transfer to - * @param value uint256 The amount of tokens to be transferred - * @return true unless throwing - */ - function transferFromAndCall(address from, address to, uint256 value) external returns (bool); - - - /** - * @notice Transfer tokens from one address to another and then call `onTransferReceived` on receiver - * @param from address The address which you want to send tokens from - * @param to address The address which you want to transfer to - * @param value uint256 The amount of tokens to be transferred - * @param data bytes Additional data with no specified format, sent in call to `to` - * @return true unless throwing - */ - function transferFromAndCall(address from, address to, uint256 value, bytes memory data) external returns (bool); - - /** - * @notice Approve the passed address to spend the specified amount of tokens on behalf of msg.sender - * and then call `onApprovalReceived` on spender. - * @param spender address The address which will spend the funds - * @param value uint256 The amount of tokens to be spent - */ - function approveAndCall(address spender, uint256 value) external returns (bool); - - /** - * @notice Approve the passed address to spend the specified amount of tokens on behalf of msg.sender - * and then call `onApprovalReceived` on spender. - * @param spender address The address which will spend the funds - * @param value uint256 The amount of tokens to be spent - * @param data bytes Additional data with no specified format, sent in call to `spender` - */ - function approveAndCall(address spender, uint256 value, bytes memory data) external returns (bool); -} - -interface ERC20 { - function totalSupply() external view returns (uint256); - function balanceOf(address account) external view returns (uint256); - function transfer(address recipient, uint256 amount) external returns (bool); - function transferFrom(address sender, 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); - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval(address indexed owner, address indexed spender, uint256 value); -} - -interface ERC165 { - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} -``` - -A contract that wants to accept token payments via `transferAndCall` or `transferFromAndCall` **MUST** implement the following interface: - -```solidity -/** - * @title ERC1363Receiver interface - * @dev Interface for any contract that wants to support `transferAndCall` or `transferFromAndCall` - * from ERC1363 token contracts. - */ -interface ERC1363Receiver { - /* - * Note: the ERC-165 identifier for this interface is 0x88a7ca5c. - * 0x88a7ca5c === bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)")) - */ - - /** - * @notice Handle the receipt of ERC1363 tokens - * @dev Any ERC1363 smart contract calls this function on the recipient - * after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the - * transfer. Return of other than the magic value MUST result in the - * transaction being reverted. - * Note: the token contract address is always the message sender. - * @param operator address The address which called `transferAndCall` or `transferFromAndCall` function - * @param from address The address which are token transferred from - * @param value uint256 The amount of tokens transferred - * @param data bytes Additional data with no specified format - * @return `bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` - * unless throwing - */ - function onTransferReceived(address operator, address from, uint256 value, bytes memory data) external returns (bytes4); -} -``` - -A contract that wants to accept token payments via `approveAndCall` **MUST** implement the following interface: - -```solidity -/** - * @title ERC1363Spender interface - * @dev Interface for any contract that wants to support `approveAndCall` - * from ERC1363 token contracts. - */ -interface ERC1363Spender { - /* - * Note: the ERC-165 identifier for this interface is 0x7b04a2d0. - * 0x7b04a2d0 === bytes4(keccak256("onApprovalReceived(address,uint256,bytes)")) - */ - - /** - * @notice Handle the approval of ERC1363 tokens - * @dev Any ERC1363 smart contract calls this function on the recipient - * after an `approve`. This function MAY throw to revert and reject the - * approval. Return of other than the magic value MUST result in the - * transaction being reverted. - * Note: the token contract address is always the message sender. - * @param owner address The address which called `approveAndCall` function - * @param value uint256 The amount of tokens to be spent - * @param data bytes Additional data with no specified format - * @return `bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))` - * unless throwing - */ - function onApprovalReceived(address owner, uint256 value, bytes memory data) external returns (bytes4); -} -``` - -## Rationale -The choice to use `transferAndCall`, `transferFromAndCall` and `approveAndCall` derives from the [ERC-20](./eip-20.md) naming. They want to highlight that they have the same behaviours of `transfer`, `transferFrom` and `approve` with the addition of a callback on receiver or spender. - -## Backwards Compatibility -This proposal has been inspired also by [ERC-223](https://github.com/ethereum/EIPs/issues/223) and [ERC-677](https://github.com/ethereum/EIPs/issues/677) but it uses the [ERC-721](./eip-721.md) approach, so it doesn't override the [ERC-20](./eip-20.md) `transfer` and `transferFrom` methods and defines the interfaces IDs to be implemented maintaining the [ERC-20](./eip-20.md) backwards compatibility. - -## Security Considerations -The `approveAndCall` and `transferFromAndCall` methods can be affected by the same issue of the standard [ERC-20](./eip-20.md) `approve` and `transferFrom` method. - -Changing an allowance with the `approveAndCall` methods brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. - -One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards ([EIP-20#issuecomment-263524729](https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1363.md diff --git a/EIPS/eip-137.md b/EIPS/eip-137.md index b35e9ef62c2649..aaeb495431908d 100644 --- a/EIPS/eip-137.md +++ b/EIPS/eip-137.md @@ -1,386 +1 @@ ---- -eip: 137 -title: Ethereum Domain Name Service - Specification -author: Nick Johnson -status: Final -type: Standards Track -category: ERC -created: 2016-04-04 ---- - -# Abstract - -This draft EIP describes the details of the Ethereum Name Service, a proposed protocol and ABI definition that provides flexible resolution of short, human-readable names to service and resource identifiers. This permits users and developers to refer to human-readable and easy to remember names, and permits those names to be updated as necessary when the underlying resource (contract, content-addressed data, etc) changes. - -The goal of domain names is to provide stable, human-readable identifiers that can be used to specify network resources. In this way, users can enter a memorable string, such as 'vitalik.wallet' or 'www.mysite.swarm', and be directed to the appropriate resource. The mapping between names and resources may change over time, so a user may change wallets, a website may change hosts, or a swarm document may be updated to a new version, without the domain name changing. Further, a domain need not specify a single resource; different record types allow the same domain to reference different resources. For instance, a browser may resolve 'mysite.swarm' to the IP address of its server by fetching its A (address) record, while a mail client may resolve the same address to a mail server by fetching its MX (mail exchanger) record. -# Motivation - -Existing [specifications](https://github.com/ethereum/wiki/wiki/Registrar-ABI) and [implementations](https://ethereum.gitbooks.io/frontier-guide/content/registrar_services.html) for name resolution in Ethereum provide basic functionality, but suffer several shortcomings that will significantly limit their long-term usefulness: -- A single global namespace for all names with a single 'centralised' resolver. -- Limited or no support for delegation and sub-names/sub-domains. -- Only one record type, and no support for associating multiple copies of a record with a domain. -- Due to a single global implementation, no support for multiple different name allocation systems. -- Conflation of responsibilities: Name resolution, registration, and whois information. - -Use-cases that these features would permit include: -- Support for subnames/sub-domains - eg, live.mysite.tld and forum.mysite.tld. -- Multiple services under a single name, such as a DApp hosted in Swarm, a Whisper address, and a mail server. -- Support for DNS record types, allowing blockchain hosting of 'legacy' names. This would permit an Ethereum client such as Mist to resolve the address of a traditional website, or the mail server for an email address, from a blockchain name. -- DNS gateways, exposing ENS domains via the Domain Name Service, providing easier means for legacy clients to resolve and connect to blockchain services. - -The first two use-cases, in particular, can be observed everywhere on the present-day internet under DNS, and we believe them to be fundamental features of a name service that will continue to be useful as the Ethereum platform develops and matures. - -The normative parts of this document does not specify an implementation of the proposed system; its purpose is to document a protocol that different resolver implementations can adhere to in order to facilitate consistent name resolution. An appendix provides sample implementations of resolver contracts and libraries, which should be treated as illustrative examples only. - -Likewise, this document does not attempt to specify how domains should be registered or updated, or how systems can find the owner responsible for a given domain. Registration is the responsibility of registrars, and is a governance matter that will necessarily vary between top-level domains. - -Updating of domain records can also be handled separately from resolution. Some systems, such as swarm, may require a well defined interface for updating domains, in which event we anticipate the development of a standard for this. -# Specification -## Overview - -The ENS system comprises three main parts: -- The ENS registry -- Resolvers -- Registrars - -The registry is a single contract that provides a mapping from any registered name to the resolver responsible for it, and permits the owner of a name to set the resolver address, and to create subdomains, potentially with different owners to the parent domain. - -Resolvers are responsible for performing resource lookups for a name - for instance, returning a contract address, a content hash, or IP address(es) as appropriate. The resolver specification, defined here and extended in other EIPs, defines what methods a resolver may implement to support resolving different types of records. - -Registrars are responsible for allocating domain names to users of the system, and are the only entities capable of updating the ENS; the owner of a node in the ENS registry is its registrar. Registrars may be contracts or externally owned accounts, though it is expected that the root and top-level registrars, at a minimum, will be implemented as contracts. - -Resolving a name in ENS is a two-step process. First, the ENS registry is called with the name to resolve, after hashing it using the procedure described below. If the record exists, the registry returns the address of its resolver. Then, the resolver is called, using the method appropriate to the resource being requested. The resolver then returns the desired result. - -For example, suppose you wish to find the address of the token contract associated with 'beercoin.eth'. First, get the resolver: - -```javascript -var node = namehash("beercoin.eth"); -var resolver = ens.resolver(node); -``` - -Then, ask the resolver for the address for the contract: - -```javascript -var address = resolver.addr(node); -``` - -Because the `namehash` procedure depends only on the name itself, this can be precomputed and inserted into a contract, removing the need for string manipulation, and permitting O(1) lookup of ENS records regardless of the number of components in the raw name. -## Name Syntax - -ENS names must conform to the following syntax: - -
<domain> ::= <label> | <domain> "." <label>
-<label> ::= any valid string label per [UTS46](https://unicode.org/reports/tr46/)
-
- -In short, names consist of a series of dot-separated labels. Each label must be a valid normalised label as described in [UTS46](https://unicode.org/reports/tr46/) with the options `transitional=false` and `useSTD3AsciiRules=true`. For Javascript implementations, a [library](https://www.npmjs.com/package/idna-uts46) is available that normalises and checks names. - -Note that while upper and lower case letters are allowed in names, the UTS46 normalisation process case-folds labels before hashing them, so two names with different case but identical spelling will produce the same namehash. - -Labels and domains may be of any length, but for compatibility with legacy DNS, it is recommended that labels be restricted to no more than 64 characters each, and complete ENS names to no more than 255 characters. For the same reason, it is recommended that labels do not start or end with hyphens, or start with digits. - -## namehash algorithm - -Before being used in ENS, names are hashed using the 'namehash' algorithm. This algorithm recursively hashes components of the name, producing a unique, fixed-length string for any valid input domain. The output of namehash is referred to as a 'node'. - -Pseudocode for the namehash algorithm is as follows: - -``` -def namehash(name): - if name == '': - return '\0' * 32 - else: - label, _, remainder = name.partition('.') - return sha3(namehash(remainder) + sha3(label)) -``` - -Informally, the name is split into labels, each label is hashed. Then, starting with the last component, the previous output is concatenated with the label hash and hashed again. The first component is concatenated with 32 '0' bytes. Thus, 'mysite.swarm' is processed as follows: - -``` -node = '\0' * 32 -node = sha3(node + sha3('swarm')) -node = sha3(node + sha3('mysite')) -``` - -Implementations should conform to the following test vectors for namehash: - - namehash('') = 0x0000000000000000000000000000000000000000000000000000000000000000 - namehash('eth') = 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae - namehash('foo.eth') = 0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f - -## Registry specification - -The ENS registry contract exposes the following functions: - -```solidity -function owner(bytes32 node) constant returns (address); -``` - -Returns the owner (registrar) of the specified node. - -```solidity -function resolver(bytes32 node) constant returns (address); -``` - -Returns the resolver for the specified node. - -```solidity -function ttl(bytes32 node) constant returns (uint64); -``` - -Returns the time-to-live (TTL) of the node; that is, the maximum duration for which a node's information may be cached. - -```solidity -function setOwner(bytes32 node, address owner); -``` - -Transfers ownership of a node to another registrar. This function may only be called by the current owner of `node`. A successful call to this function logs the event `Transfer(bytes32 indexed, address)`. - -```solidity -function setSubnodeOwner(bytes32 node, bytes32 label, address owner); -``` - -Creates a new node, `sha3(node, label)` and sets its owner to `owner`, or updates the node with a new owner if it already exists. This function may only be called by the current owner of `node`. A successful call to this function logs the event `NewOwner(bytes32 indexed, bytes32 indexed, address)`. - -```solidity -function setResolver(bytes32 node, address resolver); -``` - -Sets the resolver address for `node`. This function may only be called by the owner of `node`. A successful call to this function logs the event `NewResolver(bytes32 indexed, address)`. - -```solidity -function setTTL(bytes32 node, uint64 ttl); -``` - -Sets the TTL for a node. A node's TTL applies to the 'owner' and 'resolver' records in the registry, as well as to any information returned by the associated resolver. -## Resolver specification - -Resolvers may implement any subset of the record types specified here. Where a record types specification requires a resolver to provide multiple functions, the resolver MUST implement either all or none of them. Resolvers MUST specify a fallback function that throws. - -Resolvers have one mandatory function: - -```solidity -function supportsInterface(bytes4 interfaceID) constant returns (bool) -``` - -The `supportsInterface` function is documented in [EIP-165](./eip-165.md), and returns true if the resolver implements the interface specified by the provided 4 byte identifier. An interface identifier consists of the XOR of the function signature hashes of the functions provided by that interface; in the degenerate case of single-function interfaces, it is simply equal to the signature hash of that function. If a resolver returns `true` for `supportsInterface()`, it must implement the functions specified in that interface. - -`supportsInterface` must always return true for `0x01ffc9a7`, which is the interface ID of `supportsInterface` itself. - - Currently standardised resolver interfaces are specified in the table below. - -The following interfaces are defined: - -| Interface name | Interface hash | Specification | -| --- | --- | --- | -| `addr` | 0x3b3b57de | [Contract address](#addr) | -| `name` | 0x691f3431 | #181 | -| `ABI` | 0x2203ab56 | #205 | -| `pubkey` | 0xc8690233 | #619 | - -EIPs may define new interfaces to be added to this registry. - -### Contract Address Interface - -Resolvers wishing to support contract address resources must provide the following function: - -```solidity -function addr(bytes32 node) constant returns (address); -``` - -If the resolver supports `addr` lookups but the requested node does not have an addr record, the resolver MUST return the zero address. - -Clients resolving the `addr` record MUST check for a zero return value, and treat this in the same manner as a name that does not have a resolver specified - that is, refuse to send funds to or interact with the address. Failure to do this can result in users accidentally sending funds to the 0 address. - -Changes to an address MUST trigger the following event: - -```solidity -event AddrChanged(bytes32 indexed node, address a); -``` -# Appendix A: Registry Implementation - -```solidity -contract ENS { - struct Record { - address owner; - address resolver; - uint64 ttl; - } - - mapping(bytes32=>Record) records; - - event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); - event Transfer(bytes32 indexed node, address owner); - event NewResolver(bytes32 indexed node, address resolver); - - modifier only_owner(bytes32 node) { - if(records[node].owner != msg.sender) throw; - _ - } - - function ENS(address owner) { - records[0].owner = owner; - } - - function owner(bytes32 node) constant returns (address) { - return records[node].owner; - } - - function resolver(bytes32 node) constant returns (address) { - return records[node].resolver; - } - - function ttl(bytes32 node) constant returns (uint64) { - return records[node].ttl; - } - - function setOwner(bytes32 node, address owner) only_owner(node) { - Transfer(node, owner); - records[node].owner = owner; - } - - function setSubnodeOwner(bytes32 node, bytes32 label, address owner) only_owner(node) { - var subnode = sha3(node, label); - NewOwner(node, label, owner); - records[subnode].owner = owner; - } - - function setResolver(bytes32 node, address resolver) only_owner(node) { - NewResolver(node, resolver); - records[node].resolver = resolver; - } - - function setTTL(bytes32 node, uint64 ttl) only_owner(node) { - NewTTL(node, ttl); - records[node].ttl = ttl; - } -} -``` -# Appendix B: Sample Resolver Implementations -### Built-in resolver - -The simplest possible resolver is a contract that acts as its own name resolver by implementing the contract address resource profile: - -```solidity -contract DoSomethingUseful { - // Other code - - function addr(bytes32 node) constant returns (address) { - return this; - } - - function supportsInterface(bytes4 interfaceID) constant returns (bool) { - return interfaceID == 0x3b3b57de || interfaceID == 0x01ffc9a7; - } - - function() { - throw; - } -} -``` - -Such a contract can be inserted directly into the ENS registry, eliminating the need for a separate resolver contract in simple use-cases. However, the requirement to 'throw' on unknown function calls may interfere with normal operation of some types of contract. - -### Standalone resolver - -A basic resolver that implements the contract address profile, and allows only its owner to update records: - -```solidity -contract Resolver { - event AddrChanged(bytes32 indexed node, address a); - - address owner; - mapping(bytes32=>address) addresses; - - modifier only_owner() { - if(msg.sender != owner) throw; - _ - } - - function Resolver() { - owner = msg.sender; - } - - function addr(bytes32 node) constant returns(address) { - return addresses[node]; - } - - function setAddr(bytes32 node, address addr) only_owner { - addresses[node] = addr; - AddrChanged(node, addr); - } - - function supportsInterface(bytes4 interfaceID) constant returns (bool) { - return interfaceID == 0x3b3b57de || interfaceID == 0x01ffc9a7; - } - - function() { - throw; - } -} -``` - -After deploying this contract, use it by updating the ENS registry to reference this contract for a name, then calling `setAddr()` with the same node to set the contract address it will resolve to. -### Public resolver - -Similar to the resolver above, this contract only supports the contract address profile, but uses the ENS registry to determine who should be allowed to update entries: - -```solidity -contract PublicResolver { - event AddrChanged(bytes32 indexed node, address a); - event ContentChanged(bytes32 indexed node, bytes32 hash); - - ENS ens; - mapping(bytes32=>address) addresses; - - modifier only_owner(bytes32 node) { - if(ens.owner(node) != msg.sender) throw; - _ - } - - function PublicResolver(address ensAddr) { - ens = ENS(ensAddr); - } - - function addr(bytes32 node) constant returns (address ret) { - ret = addresses[node]; - } - - function setAddr(bytes32 node, address addr) only_owner(node) { - addresses[node] = addr; - AddrChanged(node, addr); - } - - function supportsInterface(bytes4 interfaceID) constant returns (bool) { - return interfaceID == 0x3b3b57de || interfaceID == 0x01ffc9a7; - } - - function() { - throw; - } -} -``` -# Appendix C: Sample Registrar Implementation - -This registrar allows users to register names at no cost if they are the first to request them. - -```solidity -contract FIFSRegistrar { - ENS ens; - bytes32 rootNode; - - function FIFSRegistrar(address ensAddr, bytes32 node) { - ens = ENS(ensAddr); - rootNode = node; - } - - function register(bytes32 subnode, address owner) { - var node = sha3(rootNode, subnode); - var currentOwner = ens.owner(node); - if(currentOwner != 0 && currentOwner != msg.sender) - throw; - - ens.setSubnodeOwner(rootNode, subnode, owner); - } -} -``` +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-137.md diff --git a/EIPS/eip-1386.md b/EIPS/eip-1386.md index a83217050eabf2..54cd0547750484 100644 --- a/EIPS/eip-1386.md +++ b/EIPS/eip-1386.md @@ -1,88 +1 @@ ---- -eip: 1386 -title: Attestation management contract -author: Weiwu Zhang , James Sangalli -discussions-to: https://github.com/ethereum/EIPs/issues/1386 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-09-08 ---- - -### Introduction - -Very often, we will need to use Attestations like "Alice lives in Australia" on the blockchain; that is issued by a valid issuer off chain for privacy reasons and is revokable inside a smart contract. - -An issuer can create a smart contract where he revokes multiple attestations in one go by building a bloom filter of all the hashes of the revoked attestations. - -An issuer can also put the validation method in their smart contract that can be called by other smart contracts who need to validate attestations issued by them. This allows each attestor to update their attestation format separately. - -### Purpose - -This ERC provides an interface for attestation issuers to manage their attestation signing keys and the attestations that are issued off chain for actions such as revocation and validation. - -In our draft implementation we include functions to hold cryptographic attestations, change the issuing contracts of attestations, revoke attestations and verify the authenticity of a cryptographic attestation. - -### Example use cases - -Let's say that our friend, Alice, wants to buy a bottle of wine to consume with her friends. She wants to do the order online and have it delivered to her home address whilst paying for it with Ether. - -Alice has a cryptographic attestation from her local road and maritime services who attests to her age, date of birth, country of residence and ability to drive. - -Alice is able to split up this attestation (see merkle tree attestations ERC [here](https://github.com/alpha-wallet/blockchain-attestation/blob/master/ethereum/lib/MerkleTreeAttestation.sol)) and provides only the leaf that states she is over the age of 21. - -Alice goes to buy the wine through the wine vendors smart contract and feeds in the merkle tree attestation proving that she is above 21 and can thus buy the wine, whilst attaching the appropriate amount of ether to complete the purchase. - -The issuer smart contract is able to validate her attestation, check that the issuer contract is valid and capable of performing such an attestation to her age. In this case it would have to be from someone like a driver's licence authority, as attestations to age from a school ID are not of a high enough capacity. - -The wine vendors smart contract validates the attestation, checks the payment amount is correct and credits Alice with the wine tokens she needs to complete the sale and deliver the wine. - -When the wine vendor shows up to her apartment with the wine, there is no need to prove her age again. - -### Draft interface -```solidity -/* each attestation issuer should provide their own verify() for the - * attestations they issued. There are two reasons for this. First, we - * need to leave room for new attestation methods other than the - * Merkle Tree format we are recommending. Second, the validity of the - * attestation may depend on the context that only the attestor - * knows. For example, a ticket as an attestation issued on a - * successful redemption of an American Express credit */ - -contract Issuer { - struct Attestation - { - bytes32[] merklePath; - bool valid; - uint8 v; - bytes32 r; - bytes32 s; - address attestor; - address recipient; - bytes32 salt; - bytes32 key; - bytes32 val; - }` - /* Verify the authenticity of an attestation */ - function verify(Attestation attestation); - function addattestorKey(address newAttestor, string capacity, uint expiry); - - /* this should call the revoke first */ - function replaceKey(address attestorToReplace, string capacity, uint expiry, address newAttestor); - - /* this revokes a single key */ - function removeKey(address attestor); - - /* if the key exists with such capacity and isn't revoked or expired */ - function validateKey(address attestor, string capacity) returns (bool); - - /* revoke an attestation by replace the bloom filter, this helps preserve privacy */ - function revokeAttestations(Bloomfilter b); - -} -``` - -Please click [here](https://github.com/alpha-wallet/blockchain-attestation/blob/master/ethereum/example-james-squire/james-squire.sol) to see a draft implementation of this interface - -### Related ERC's -#1388 #1387 +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1386.md diff --git a/EIPS/eip-1387.md b/EIPS/eip-1387.md index 1061ffec26dc8e..b66d1291bd500e 100644 --- a/EIPS/eip-1387.md +++ b/EIPS/eip-1387.md @@ -1,49 +1 @@ ---- -eip: 1387 -title: Merkle Tree Attestations with Privacy enabled -author: Weiwu Zhang , James Sangalli -discussions-to: https://github.com/ethereum/EIPs/issues/1387 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-09-08 ---- - -### Introduction - -It's often needed that an Ethereum smart contract must verify a claim (I live in Australia) attested by a valid attester. - -For example, an ICO contract might require that the participant, Alice, lives in Australia before she participates. Alice's claim of residency could come from a local Justice of the Peace who could attest that "Alice is a resident of Australia in NSW". - -Unlike previous attempts, we assume that the attestation is signed and issued off the blockchain in a Merkle Tree format. Only a part of the Merkle tree is revealed by Alice at each use. Therefore we avoid the privacy problem often associated with issuing attestations on chain. We also assume that Alice has multiple signed Merkle Trees for the same factual claim to avoid her transactions being linkable. - -## Purpose -This ERC provides an interface and reference implementation for smart contracts that need users to provide an attestation and validate it. - -### Draft implementation -```solidity -contract MerkleTreeAttestationInterface { - struct Attestation - { - bytes32[] merklePath; - bool valid; - uint8 v; - bytes32 r; - bytes32 s; - address attester; - address recipient; - bytes32 salt; - bytes32 key; - bytes32 val; - } - - function validate(Attestation attestation) public returns(bool); -} - -``` -### Relevant implementation examples -[Here](https://github.com/alpha-wallet/blockchain-attestation/blob/master/ethereum/lib/MerkleTreeAttestation.sol) is an example implementation of the MerkleTreeAttestationInterface -[Here](https://github.com/alpha-wallet/blockchain-attestation/blob/master/ethereum/example-james-squire/james-squire.sol) is an example service which would use such a merkle tree attestation - -### Related ERC's -#1388 #1386 +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1387.md diff --git a/EIPS/eip-1388.md b/EIPS/eip-1388.md index ed05a6407d55bc..6fec0f29ac9e30 100644 --- a/EIPS/eip-1388.md +++ b/EIPS/eip-1388.md @@ -1,87 +1 @@ ---- -eip: 1388 -title: Attestation Issuers Management List -author: Weiwu Zhang , James Sangalli -discussions-to: https://github.com/ethereum/EIPs/issues/1388 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-09-08 ---- - -### Introduction - -In smart contracts, we will need methods to handle cryptographic attestations to a users identifier or abilities. Let's say we have a real estate agent, KiwiRealtors, that provides an "expression of interest" function though a smart contract and requires the users to provide an attestation that they are a resident of New Zealand or Australia, as a legal requirement. This has actually happened in the New Zealand property market and it is the perfect example of a need to handle such attestations. - -However, it is not practical for a smart contract to explicitly trust an attestation issuer. There are multiple issuers who can provide an attestation to a person's residency - a local Justice of the Peace, the land title office, local police, passport authority etc. We envision a model where the effort to manage the list of qualified issuers is practically outsourced to a list. - -Anyone can publish a list of issuers. Only the most trusted and carefully maintained lists gets popular use. - -### Purpose -This ERC provides a smart contract interface for anyone to manage a list of attestation issuers. A smart contract would explicitly trust a list, and therefore all attestations issued by the issuers on the list. - -### Draft implementation -```solidity - /* The purpose of this contract is to manage the list of attestation - * issuer contracts and their capacity to fulfill requirements - */ - contract ManagedListERC - { - /* a manager is the steward of a list. Only he/she/it can change the - * list by removing/adding attestation issuers to the list. - - * An issuer in the list is represented by their contract - * addresses, not by the attestation signing keys managed by such a - * contract. - */ - struct List - { - string name; - string description; // short description of what the list entails - string capacity; // serves as a filter for the attestation signing keys - /* if a smart contract specifies a list, only attestation issued - * by issuers on that list is accepted. Furthermore, if that - * list has a non-empty capacity, only attestations signed by a - * signing key with that capacity is accepted. */ - - address[] issuerContracts; // all these addresses are contracts, no signing capacity - uint expiry; - } - - // find which list the sender is managing, then add an issuer to it - function addIssuer(address issuerContractAddress) public; - - //return false if the list identified by the sender doesn't have this issuer in the list - function removeIssuer(address issuerContractAddress, List listToRemoveIssuerFrom) public returns(bool); - - /* called by services, e.g. Kiwi Properties or James Squire */ - /* loop through all issuer's contract and execute validateKey() on - * every one of them in the hope of getting a hit, return the - * contract address of the first hit. Note that there is an attack - * method for one issuer to claim to own the key of another which - * is mitigated by later design. */ - //loop through the issuers array, calling validate on the signingKeyOfAttestation - function getIssuerCorrespondingToAttestationKey(bytes32 list_id, address signingKeyOfAttestation) public returns (address); - - /* for simplicity we use sender's address as the list ID, - * accepting these consequences: a) if one user wish to maintain - * several lists with different capacity, he or she must use a - * different sender address for each. b) if the user replaced the - * sender's key, either because he or she suspects the key is - * compromised or that it is lost and reset through special means, - * then the list is still identified by the first sender's - * address. - */ - - function createList(List list) public; - - /* replace list manager's key with the new key */ - function replaceListIndex(List list, address manager) public returns(bool); - - } -``` - -Click [here](https://github.com/alpha-wallet/blockchain-attestation/blob/master/ethereum/trustlist/ManagedList.sol) to see an example implementation of this ERC - -### Related ERC's -#1387 #1386 +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1388.md diff --git a/EIPS/eip-1417.md b/EIPS/eip-1417.md index 1bf50edd79b978..2b6df68805a38e 100644 --- a/EIPS/eip-1417.md +++ b/EIPS/eip-1417.md @@ -1,283 +1 @@ ---- -eip: 1417 -title: Poll Standard -author: Chaitanya Potti (@chaitanyapotti), Partha Bhattacharya (@pb25193) -type: Standards Track -category: ERC -status: Stagnant -created: 2018-09-16 -requires: 165, 1261 -discussions-to: https://github.com/ethereum/EIPs/issues/1417 ---- - -## Note to Readers - -1. We have created a couple of implementations of polls for varied use cases. - Please refer to them [here](https://github.com/chaitanyapotti/Voting) - -## Simple Summary - -A standard interface for Polls to be used with EIP-1261 (MVT). - -## Abstract - -The following standard allows for the implementation of a standard API for polls to be used with MVTs (refer [EIP-1261](./eip-1261.md)). The standard provides basic functionality to vote, unvote, tally votes, get voter turnout, and a lot more. The poll standard attempts to modularize blockchain voting by breaking down a poll into 4 crucial building blocks: voterbase qualification, vote weight calculation, vote consequences, and vote tallying. By creating a common interface for polls that have different kinds of building blocks, the poll standard makes it possible to make interactive front end applications which can seamlessly get data from a poll contract in order to bring transparency into consensus and decision making on the blockchain. - -We considered the usage of polls with MVTs because MVTs serve as a permissioning mechanism. The manual permissioning of polls allows for vote weightage functions to take up several shapes and forms. Hence the voterbase function applies several logical checks on the vote sender to confirm that they are member(see EIP 1261) of a certain entity or combination of entities. For the specification of the nature of voting, we define the vote weight function. The vote weight function decides how much of vote share each voter will receive and this can be based on several criteria, some of which are listed below in this article. There are certain kinds of polls that enforce certain consequences on the voter, for example a poll may require a voter to lock in a certain amount of tokens, or require the voter to pay a small fee. These on-chain consequences can be coded into the consequence module of the poll standard. Finally, the last module is where the votes are added. A ballot for each candidate is updated whenever relevant, depending on the vote value, and the corresponding NoV count(number of voters). This module is common for most polls, and is the most straightforward. Polls may be time bound, ie. having a finish time, after which no votes are recorded, or be unbound, such that there is no finish time. The following are some examples of specific polls which leverage the flexibility of the poll standard, and it is possible to come up with several others: - -- Plurality Voting: The simplest form of voting is when you want all eligible voters to have one vote per person. This is the simplest to code, as the vote weight is 1, and there is no vote consequence. The only relevant module here is the voterbase, which can be categorized by one or more MVT contracts. -- Token proportional voting: This kind of a poll is actually possible without the use of a voterbase function, because the vote weight function having token proportionality automatically rules out addresses which don't hold the appropriate ERC - 20/ ERC - 777 token. However the voterbase function may be leveraged to further permission the system and give voting rights only to a fixed subset of token holders. -- Capped Token Proportional Voting: This is a modified version of the previous example, where each voter is given proportional vote share only until a certain limit of token ownership. After exceeding that limit, holding more coins does not add more vote share. This format leverages the voterbase module effectively, disallowing people from spreading their coins across multiple addresses by allowing the admin to control which addresses can vote. -- Delegated Voting: Certain polls may allow voters to delegate their votes to other voters. This is known as delegated voting or liquid democracy. For such a poll, a complicated vote weight function is needed, and a data structure concerning the voterbase is also required. A consequence of voting here would be that a user cannot delegate, and a consequence of delegating is that a user cannot vote. Sample implementation of polls contains an example of this vote scheme. -- Karma Based Voting: A certain form of poll may be based on weightage from digital respect. This digital respect would be like a simple upvote from one member of voterbase to another. A mapping of mappings along with an appropriate vote weight function can serve this purpose. Sample implementation has an example. -- Quadratic voting: A system where each vote is associated with a fee, and the fee is proportional to the square of the vote weight that the voter wants. This can be designed by applying a vote weight based on the transaction message, and then charging a fee in the vote consequence module. - -The poll standard is intended to be a smart contract standard that makes poll deployment flexible, transparent and accessible. - -## Motivation - -A standard interface allows any user or applications to work with any Poll contract on Ethereum. We provide for simple ERC-1417 smart contracts. Additional applications are discussed below. - -This standard is inspired by the lack of governance tools in the blockchain space. Whenever there is a consensus collection exercise, someone goes ahead and deploys some kind of poll, and there is no standard software for accessing the data on the poll. For an end user who is not a developer, this is a real problem. The poll, which might be fully transparent, appears to be completely opaque to a common user who does not understand blockchain. In order for developers to build applications for interacting with and accessing poll data, and for poll deployers to have ready application level support, there must be a standardization of poll interfaces. - -This realization happened while conducting market research on DAICOs. The first ever DAICO, Abyss, had far from optimal user experience, and abysmal transparency. Since then, we have been working on a poll standard. During the process, we came across EIP 1202, the voting standard, and found that the discussion there had already diverged from our thoughts to an extent that it made sense to publish a separate proposal altogether. Some of the benefits brought by the poll standard - EIP 1417 aims to offer some additional benefits. - -1. Modularization: EIP 1417 modularizes the code present in the poll standard into 4 major building blocks based on functionality. These are: voterbase logic, vote weight calculation, vote consequence processing, and tallying module. This makes it easy for developers to change parts of a poll without disrupting other parts, and also helps people understand better, code written in the same format by other people. - -2. Permissioning: Permissioning is an important aspect of polls, and is missing in most poll proposals so far, on the blockchain. For some reason, most blockchain based polls seem to consider token holding as the only way to permission a poll. However this hampers flexibility, and hence our poll standard is leveraging EIP 1261 in order to clear the permissioning hurdle. Not only does it allow for more creative poll structures in terms of vote weightage, but even improves the flexibility in permissioning by allowing developers to combine several entities and read attributes from entities. - -3. Flexibility: The vote weight module of the poll standard can be used effectively to design various kinds of poll contracts which function differently and are suited to different environments. Some examples are quadratic voting, karma voting, delegated voting, token based voting, and one person one vote systems. These schemes are possible due to the separation of voterbase creation and vote weight calculation. - -4. NoV Counts: Several weighted polls have struggled to provide proper transparency because they only show the final result without enough granularity. This is because they do not store the number of voters that have voted for each proposal, and only store the total accrued vote for each option. EIP 1417 solves this by additionally recording number of voters(NoV) in each proposal. This NoV count is redundant in the case of one person one vote, but elsewhere, it is helpful in figuring out concentration of power. This ensures that malicious parties can be traced to a larger extent. - -5. Event Logging: The poll standard logs an event during a successful vote, unsuccessful vote, and a successful unvote. This is being done so that in the event of a malicious admin removing real members or adding fake members, communities can build tools in order to perform advanced audits and simulate results in the absence of the malicious attack. Such advanced features are completely absent in most polls, and hence, it is hard to investigate such polls. - -6. Pollscan.io: The Electus foundation is working on a web based application for accessing and interacting with poll data on the blockchain, it will be deployed on the domain name www.pollscan.io in the coming months. - -All that being said, we are very excited to share our proposal with the community and open up to suggestions in this space. - -### Benefits - -1. Building applications (pollscan.io) on top of a standardized voting interface enables transparency and encourage more DAO/DAICO's to act responsibly in terms of governance -2. Create Action contracts which take actions programmatically based on the result of a poll -3. Allow the compatibility with token standard such as [ERC-20](./eip-20.md) or (./eip-777.md)) and membership standard such as [EIP-1261](./eip-1261.md) -4. Flexibility allows for various voting schemes including but not limited to modern schemes such as PLCR Voting - -### Use-cases: - -Polls are useful in any context of collective decision making, which include but aren't limited to: - -1. Governing public resources, like ponds, playgrounds, streets etc -2. Maintaining fiscal policy in a transparent consensus driven manner -3. Governing crowdfunded projects - refer DAICO, Vitalik Buterin -4. Implementation of Futarchy -5. Decision making in political parties, and municipal corporations -6. Governing expenditure of a cryptocurrency community - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -**Every ERC-1417 compliant contract must implement the `ERC1417` and `ERC165` interfaces** (subject to "caveats" below): - -```solidity -/// @title ERC-1417 Poll Standard -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1417.md -/// Note: the ERC-165 identifier for this interface is 0x4fad898b. -interface IPoll { - /// @dev This emits when a person tries to vote without permissions. Useful for auditing purposes. - /// E.g.: To prevent an admin to revoke permissions; calculate the result had they not been removed. - /// @param _from User who tried to vote - /// @param _to the index of the proposal he voted to - /// @param voteWeight the weight of his vote - event TriedToVote(address indexed _from, uint8 indexed _to, uint voteWeight); - - /// @dev This emits when a person votes successfully - /// @param _from User who successfully voted - /// @param _to the index of the proposal he voted to - /// @param voteWeight the weight of his vote - event CastVote(address indexed _from, uint8 indexed _to, uint voteWeight); - - /// @dev This emits when a person revokes his vote - /// @param _from User who successfully unvoted - /// @param _to the index of the proposal he unvoted - /// @param voteWeight the weight of his vote - event RevokedVote(address indexed _from, uint8 indexed _to, uint voteWeight); - - /// @notice Handles the vote logic - /// @dev updates the appropriate data structures regarding the vote. - /// stores the proposalId against the user to allow for unvote - /// @param _proposalId the index of the proposal in the proposals array - function vote(uint8 _proposalId) external; - - /// @notice Handles the unvote logic - /// @dev updates the appropriate data structures regarding the unvote - function revokeVote() external; - - /// @notice gets the proposal names - /// @dev limit the proposal count to 32 (for practical reasons), loop and generate the proposal list - /// @return the list of names of proposals - function getProposals() external view returns (bytes32[]); - - /// @notice returns a boolean specifying whether the user can vote - /// @dev implement logic to enable checks to determine whether the user can vote - /// if using eip-1261, use protocol addresses and interface (IERC1261) to enable checking with attributes - /// @param _to the person who can vote/not - /// @return a boolean as to whether the user can vote - function canVote(address _to) external view returns (bool); - - /// @notice gets the vote weight of the proposalId - /// @dev returns the current cumulative vote weight of a proposal - /// @param _proposalId the index of the proposal in the proposals array - /// @return the cumulative vote weight of the specified proposal - function getVoteTally(uint _proposalId) external view returns (uint); - - /// @notice gets the no. of voters who voted for the proposal - /// @dev use a struct to keep a track of voteWeights and voterCount - /// @param _proposalId the index of the proposal in the proposals array - /// @return the voter count of the people who voted for the specified proposal - function getVoterCount(uint _proposalId) external view returns (uint); - - /// @notice calculates the vote weight associated with the person `_to` - /// @dev use appropriate logic to determine the vote weight of the individual - /// For sample implementations, refer to end of the eip - /// @param _to the person whose vote weight is being calculated - /// @return the vote weight of the individual - function calculateVoteWeight(address _to) external view returns (uint); - - /// @notice gets the leading proposal at the current time - /// @dev calculate the leading proposal at the current time - /// For practical reasons, limit proposal count to 32. - /// @return the index of the proposal which is leading - function winningProposal() external view returns (uint8); - - /// @notice gets the name of the poll e.g.: "Admin Election for Autumn 2018" - /// @dev Set the name in the constructor of the poll - /// @return the name of the poll - function getName() external view returns (bytes32); - - /// @notice gets the type of the Poll e.g.: Token (XYZ) weighted poll - /// @dev Set the poll type in the constructor of the poll - /// @return the type of the poll - function getPollType() external view returns (bytes32); - - /// @notice gets the logic to be used in a poll's `canVote` function - /// e.g.: "XYZ Token | US & China(attributes in erc-1261) | Developers(attributes in erc-1261)" - /// @dev Set the Voterbase logic in the constructor of the poll - /// @return the voterbase logic - function getVoterBaseLogic() external view returns (bytes32); - - /// @notice gets the start time for the poll - /// @dev Set the start time in the constructor of the poll as Unix Standard Time - /// @return start time as Unix Standard Time - function getStartTime() external view returns (uint); - - /// @notice gets the end time for the poll - /// @dev Set the end time in the constructor of the poll as Unix Time or specify duration in constructor - /// @return end time as Unix Standard Time - function getEndTime() external view returns (uint); - - /// @notice returns the list of entity addresses (eip-1261) used for perimissioning purposes. - /// @dev addresses list can be used along with IERC1261 interface to define the logic inside `canVote()` function - /// @return the list of addresses of entities - function getProtocolAddresses() external view returns (address[]); - - /// @notice gets the vote weight against all proposals - /// @dev limit the proposal count to 32 (for practical reasons), loop and generate the vote tally list - /// @return the list of vote weights against all proposals - function getVoteTallies() external view returns (uint[]); - - /// @notice gets the no. of people who voted against all proposals - /// @dev limit the proposal count to 32 (for practical reasons), loop and generate the vote count list - /// @return the list of voter count against all proposals - function getVoterCounts() external view returns (uint[]); - - /// @notice For single proposal polls, returns the total voterbase count. - /// For multi proposal polls, returns the total vote weight against all proposals - /// this is used to calculate the percentages for each proposal - /// @dev limit the proposal count to 32 (for practical reasons), loop and generate the voter base denominator - /// @return an integer which specifies the above mentioned amount - function getVoterBaseDenominator() external view returns (uint); -} -``` - -### Caveats - -The 0.4.24 Solidity interface grammar is not expressive enough to document the ERC-1417 standard. A contract which complies with ERC-1417 MUST also abide by the following: - -- Solidity issue #3412: The above interfaces include explicit mutability guarantees for each function. Mutability guarantees are, in order weak to strong: `payable`, implicit nonpayable, `view`, and `pure`. Your implementation MUST meet the mutability guarantee in this interface and you MAY meet a stronger guarantee. For example, a `payable` function in this interface may be implemented as nonpayble (no state mutability specified) in your contract. We expect a later Solidity release will allow your stricter contract to inherit from this interface, but a workaround for version 0.4.24 is that you can edit this interface to add stricter mutability before inheriting from your contract. -- Solidity issue #2330: If a function is shown in this specification as `external` then a contract will be compliant if it uses `public` visibility. As a workaround for version 0.4.24, you can edit this interface to switch to `public` before inheriting from your contract. - -_If a newer version of Solidity allows the caveats to be expressed in code, then this EIP MAY be updated and the caveats removed, such will be equivalent to the original specification._ - -## Rationale - -As the poll standard is built with the intention of creating a system that allows for more transparency and accessibility of governance data, the design choices in the poll standard are driven by this motivator. In this section we go over some of the major design choices, and why these choices were made: - -1. Event logging: The logic behind maintaining event logs in the cases of: - - - Cast Vote - - Unvote - - Failed Vote - is to ensure that in the event of a manipulated voterbase, simple off chain checks can be performed to audit the integrity of the poll result. - -2. No poll finish trigger: There was a consideration of adding functions in the poll which execute after completion of the poll to carry out some pre-decided logic. However this was deemed to be unnecessary - because such an action can be deployed in a separate contract which simply reads the result of a given poll, and against the spirit of modularity, because no actions can be created after the poll has been deployed. Also, such functions would not be able to combine the results of polls, and definitely would not fit into polls that do not have an end time. - -3. Allow for unbound polls: The poll standard, unlike other voting standard proposals, does not force polls to have an end time. This becomes relevant in some cases where the purpose of a poll is to have a live register of ongoing consensus. Some other use cases come into picture when you want to deploy a set of action contracts which read from the poll, and want to be able to execute the action contract whenever a poll reaches a certain threshold, rather than waiting for the end of the poll. - -4. Modularization: There have been opinions in the Ethereum community that there cannot exist a voting standard, because voting contracts can be of various types, and have several shapes and forms. However we disagree, and make the case that modularization is the solution. While different polls may need different logic, they all need consistent end points. All polls need to give out results along with headcounts, all polls should have event logs, all polls should be examinable with frontend tools, and so on. The poll standard is not a statement saying “all polls should be token based” or any such specific system. However the poll standard is a statement saying that all polls should have a common access and modification protocol - this will enable more apps to include governance without having to go through the trouble of making customers start using command line. - -Having explained our rationale, we are looking forward to hearing from the community some thoughts on how this can be made more useful or powerful. - -**Gas and Complexity** (regarding the enumeration for proposal count) - -This specification contemplates implementations that contain a sample of 32 proposals (max up to blockgaslimit). If your application is able to grow and needs more than 32 proposals, then avoid using for/while loops in your code. These indicate your contract may be unable to scale and gas costs will rise over time without bound - -**Privacy** - -Personal information: The standard does not put any personal information on to the blockchain, so there is no compromise of privacy in that respect. - -**Community Consensus** - -We have been very inclusive in this process and invite anyone with questions or contributions into our discussion. However, this standard is written only to support the identified use cases which are listed herein. - -## Test Cases - -Voting Standard includes test cases written using Truffle. - -## Implementations - -Voting Standard -- a reference implementation - -- MIT licensed, so you can freely use it for your projects -- Includes test cases -- Also available as a npm package - npm i electusvoting - -## References - -**Standards** - -- [EIP-20: ERC-20 Token Standard (a.k.a. ERC-20)](./eip-20.md) -- [EIP-165: Standard Interface Detection](./eip-165.md) -- [EIP-721: Non-Fungible Token Standard(a.k.a. ERC-721)](./eip-721.md) -- [ERC-1261 MV Token Standard](./eip-1261.md) -- [RFC 2119 Key words for use in RFCs to Indicate Requirement Levels](https://www.ietf.org/rfc/rfc2119.txt) - -**Issues** - -1. The Original ERC-1417 Issue. https://github.com/ethereum/eips/issues/1417 -1. Solidity Issue \#2330 -- Interface Functions are Axternal. https://github.com/ethereum/solidity/issues/2330 -1. Solidity Issue \#3412 -- Implement Interface: Allow Stricter Mutability. https://github.com/ethereum/solidity/issues/3412 -1. Solidity Issue \#3419 -- Interfaces Can't Inherit. https://github.com/ethereum/solidity/issues/3419 - -**Discussions** - -1. ERC-1417 (announcement of first live discussion). https://github.com/ethereum/eips/issues/1417 - -**Voting Implementations and Other Projects** - -- [Voting Implementations](https://github.com/chaitanyapotti/Voting) - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1417.md diff --git a/EIPS/eip-1438.md b/EIPS/eip-1438.md index 9572193f173eee..6d082d802e2486 100644 --- a/EIPS/eip-1438.md +++ b/EIPS/eip-1438.md @@ -1,142 +1 @@ ---- -eip: 1438 -title: dApp Components (avatar) & Universal Wallet -author: Jet Lim (@Nitro888) -discussions-to: https://ethresear.ch/t/avatar-system-and-universal-wallet-for-ethereum-address/3473 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-09-21 ---- - -## Simple Summary -Contracts are open source based. And most developers use the public contracts at the start of the project to modify or simply include them. This is project-oriented centralized development and I think it is a waste of resources. Therefore, we propose to make dApp or contracts component-ready for use in other services. - -## Abstract -There have been suggestions for modified tokens based on erc20, but since many tokens have already been built on erc20, it is necessary to increase the utilization of already developed erc20 tokens. Therefore, we propose a universal wallet that can use erc20 tokens universally. We also propose a component dApp that allows you to create and save your avatar (& social badge system), and use it immediately in other services. All of the dApps suggested in this document are based on decentralized development and use that anyone can create and participate in. - -## Motivation -While many projects are under development in an open source way, they are simply adding and deploy with open sources to their projects. This means that you are developing a centralized service that uses your own dApp-generated information on your own. In order to improve the block chain ecosystem, all resources created by dApp and placed in the public block chain must be reusable in another dApp. This means that you can enhance your service by exchanging the generated information with other dApp. Likewise, ERC20 Tokens require Universal Wallet standards to be easy to use for direct transactions. - -### Seeds for improvement of the blockchain ecosystem. -- Synergy - With other dApps and resources. -- Enhanced interface - For ERC20 tokens. -- Easy & Decentralized - Everyone should be able to add to their services easily, without censorship. - - -#### The following avatar store, badge system, and universal wallet are kind of examples about component dApp. -![intro](../assets/eip-1438/intro.png) - -## Specification -### 1. Avatar -#### 1.1. Avatar Shop -- The avatar store is created after ERC20 currency is set. -- You can customize asset category & viewer script. - -#### 1.2. Upload asset & user data -The avatar's information & assets are stored in the event log part of the block chain. -- Assets are SVG format. (compressed with gzip) -- avatar information data is json (compressed with msgpack) - -![avatar](../assets/eip-1438/avatar.png) -** The avatar assets from [Avataaars](https://github.com/fangpenlin/avataaars) developed by [Fang-Pen Lin](https://twitter.com/fangpenlin), the original avatar is designed by [Pablo Stanley](https://twitter.com/pablostanley). - -### 2. Universal Wallet -![wallet](../assets/eip-1438/wallet.png) -#### 2.1. ERC20 interface -``` js -contract ERC20Interface { - function totalSupply() public constant returns (uint); - function balanceOf(address tokenOwner) public constant returns (uint balance); - function allowance(address tokenOwner, address spender) public constant returns (uint remaining); - function transfer(address to, uint tokens) public returns (bool success); - function approve(address spender, uint tokens) public returns (bool success); - function transferFrom(address from, address to, uint tokens) public returns (bool success); - - event Transfer(address indexed from, address indexed to, uint tokens); - event Approval(address indexed tokenOwner, address indexed spender, uint tokens); -} -``` - -#### 2.2. Fixed ERC20 contract for receive approval and execute function in one call -``` js -function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) { - allowed[msg.sender][spender] = tokens; - emit Approval(msg.sender, spender, tokens); - ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data); - return true; -} -``` - -#### 2.3. And ApproveAndCallFallBack contract for Fixed ERC20. -However, many ERC20 tokens are not prepared. -``` js -contract ApproveAndCallFallBack { - function receiveApproval(address from, uint256 tokens, address token, bytes data) public; -} -``` -#### 2.4. Universal Wallet -We propose a Universal Wallet to solve this problem. - -``` js -contract UniversalWallet is _Base { - - constructor(bytes _msgPack) _Base(_msgPack) public {} - function () public payable {} - - //------------------------------------------------------- - // erc20 interface - //------------------------------------------------------- - function balanceOf(address _erc20) public constant returns (uint balance) { - if(_erc20==address(0)) - return address(this).balance; - return _ERC20Interface(_erc20).balanceOf(this); - } - function transfer(address _erc20, address _to, uint _tokens) onlyOwner public returns (bool success) { - require(balanceOf(_erc20)>=_tokens); - if(_erc20==address(0)) - _to.transfer(_tokens); - else - return _ERC20Interface(_erc20).transfer(_to,_tokens); - return true; - } - function approve(address _erc20, address _spender, uint _tokens) onlyOwner public returns (bool success) { - require(_erc20 != address(0)); - return _ERC20Interface(_erc20).approve(_spender,_tokens); - } - - //------------------------------------------------------- - // pay interface - //------------------------------------------------------- - function pay(address _store, uint _tokens, uint256[] _options) onlyOwner public { - address erc20 = _ApproveAndCallFallBack(_store).erc20(); - address spender = _ApproveAndCallFallBack(_store).spender(); - if(erc20 == address(0)) { - transfer(erc20,spender,_tokens); - _ApproveAndCallFallBack(_store).receiveApproval(_options); - } else { - _ERC20Interface(erc20).approve(spender,_tokens); - _ApproveAndCallFallBack(_store).receiveApproval(_options); - } - } - function pay(address _store, uint _tokens, bytes _msgPack) onlyOwner public { - address erc20 = _ApproveAndCallFallBack(_store).erc20(); - address spender = _ApproveAndCallFallBack(_store).spender(); - if(erc20 == address(0)) { - transfer(erc20,spender,_tokens); - _ApproveAndCallFallBack(_store).receiveApproval(_msgPack); - } else { - _ERC20Interface(erc20).approve(spender,_tokens); - _ApproveAndCallFallBack(_store).receiveApproval(_msgPack); - } - } -} -``` - -## Test Cases -- https://www.nitro888.com -- https://github.com/Nitro888/nitro888.github.io -- https://github.com/Nitro888/dApp-Alliance - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1438.md diff --git a/EIPS/eip-1444.md b/EIPS/eip-1444.md index 9c8e0d93e39b5c..c045e4b6d7b46c 100644 --- a/EIPS/eip-1444.md +++ b/EIPS/eip-1444.md @@ -1,322 +1 @@ ---- -eip: 1444 -title: Localized Messaging with Signal-to-Text -author: Brooklyn Zelenka (@expede), Jennifer Cooper (@jenncoop) -discussions-to: https://ethereum-magicians.org/t/eip-1444-localized-messaging-with-signal-to-text/ -status: Stagnant -type: Standards Track -category: ERC -created: 2018-09-23 ---- - -## Simple Summary - -A method of converting machine codes to human-readable text in any language and phrasing. - -## Abstract - -An on-chain system for providing user feedback by converting machine-efficient codes into human-readable strings in any language or phrasing. The system does not impose a list of languages, but rather lets users create, share, and use the localizated text of their choice. - -## Motivation - -There are many cases where an end user needs feedback or instruction from a smart contract. Directly exposing numeric codes does not make for good UX or DX. If Ethereum is to be a truly global system usable by experts and lay persons alike, systems to provide feedback on what happened during a transaction are needed in as many languages as possible. - -Returning a hard-coded string (typically in English) only serves a small segment of the global population. This standard proposes a method to allow users to create, register, share, and use a decentralized collection of translations, enabling richer messaging that is more culturally and linguistically diverse. - -There are several machine efficient ways of representing intent, status, state transition, and other semantic signals including booleans, enums and [ERC-1066 codes](./eip-1066.md). By providing human-readable messages for these signals, the developer experience is enhanced by returning easier to consume information with more context (ex. `revert`). End user experience is enhanced by providing text that can be propagated up to the UI. - -## Specification - -### Contract Architecture - -Two types of contract: `LocalizationPreferences`, and `Localization`s. - -The `LocalizationPreferences` contract functions as a proxy for `tx.origin`. - -```diagram - +--------------+ - | | - +------> | Localization | - | | | - | +--------------+ - | - | -+-----------+ +-------------------------+ | +--------------+ -| | | | <------+ | | -| Requestor | <------> | LocalizationPreferences | <-------------> | Localization | -| | | | <------+ | | -+-----------+ +-------------------------+ | +--------------+ - | - | - | +--------------+ - | | | - +------> | Localization | - | | - +--------------+ -``` - -### `Localization` - -A contract that holds a simple mapping of codes to their text representations. - -```solidity -interface Localization { - function textFor(bytes32 _code) external view returns (string _text); -} -``` - -#### `textFor` - -Fetches the localized text representation. - -```solidity -function textFor(bytes32 _code) external view returns (string _text); -``` - -### `LocalizationPreferences` - -A proxy contract that allows users to set their preferred `Localization`. Text lookup is delegated to the user's preferred contract. - -A fallback `Localization` with all keys filled MUST be available. If the user-specified `Localization` has not explicitly set a loalization (ie. `textFor` returns `""`), the `LocalizationPreferences` MUST redelegate to the fallback `Localization`. - -```solidity -interface LocalizationPreferences { - function set(Localization _localization) external returns (bool); - function textFor(bytes32 _code) external view returns (bool _wasFound, string _text); -} -``` - -#### `set` - -Registers a user's preferred `Localization`. The registering user SHOULD be considered `tx.origin`. - -```solidity -function set(Localization _localization) external; -``` - -#### `textFor` - -Retrieve text for a code found at the user's preferred `Localization` contract. - -The first return value (`bool _wasFound`) represents if the text is available from that `Localization`, or if a fallback was used. If the fallback was used in this context, the `textFor`'s first return value MUST be set to `false`, and is `true` otherwise. - -```solidity -function textFor(bytes32 _code) external view returns (bool _wasFound, string _text); -``` - -### String Format - -All strings MUST be encoded as [UTF-8](https://www.ietf.org/rfc/rfc3629.txt). - -```solidity -"Špeĉiäl chârãçtérs are permitted" -"As are non-Latin characters: アルミ缶の上にあるみかん。" -"Emoji are legal: 🙈🙉🙊🎉" -"Feel free to be creative: (ノ◕ヮ◕)ノ*:・゚✧" -``` - -### Templates - -Template strings are allowed, and MUST follow the [ANSI C `printf`](https://pubs.opengroup.org/onlinepubs/009696799/utilities/printf.html) conventions. - -```solidity -"Satoshi's true identity is %s" -``` - -Text with 2 or more arguments SHOULD use the POSIX parameter field extension. - -```solidity -"Knock knock. Who's there? %1$s. %1$s who? %2$s!" -``` - -## Rationale - -### `bytes32` Keys - -`bytes32` is very efficient since it is the EVM's base word size. Given the enormous number of elements (card(A) > 1.1579 × 1077), it can embed nearly any practical signal, enum, or state. In cases where an application's key is longer than `bytes32`, hashing that long key can map that value into the correct width. - -Designs that use datatypes with small widths than `bytes32` (such as `bytes1` in [ERC-1066](./eip-1066.md)) can be directly embedded into the larger width. This is a trivial one-to-one mapping of the smaller set into the the larger one. - -### Local vs Globals and Singletons - -This spec has opted to not _force_ a single global registry, and rather allow any contract and use case deploy their own system. This allows for more flexibility, and does not restrict the community for opting to use singleton `LocalizationPreference` contracts for common use cases, share `Localization`s between different proxys, delegate translations between `Localization`s, and so on. - -There are many practical uses of agreed upon singletons. For instance, translating codes that aim to be fairly universal and integrated directly into the broader ecosystem (wallets, frameworks, debuggers, and the like) will want to have a single `LocalizationPreference`. - -Rather the dispersing several `LocalizationPreference`s for different use cases and codes, one could imagine a global "registry of registries". While this approach allows for a unified lookups of all translations in all use cases, it is antithetical to the spirit of decentralization and freedom. Such a system also increases the lookup complexity, places an onus on getting the code right the first time (or adding the overhead of an upgradable contract), and need to account for use case conflicts with a "unified" or centralized numbering system. Further, lookups should be lightweight (especially in cases like looking up revert text). - -For these reasons, this spec chooses the more decentralized, lightweight, free approach, at the cost of on-chain discoverability. A registry could still be compiled, but would be difficult to enforce, and is out of scope of this spec. - -### Off Chain Storage - -A very viable alternative is to store text off chain, with a pointer to the translations on-chain, and emit or return a `bytes32` code for another party to do the lookup. It is difficult to guarantee that off-chain resources will be available, and requires coordination from some other system like a web server to do the code-to-text matching. This is also not compatible with `revert` messages. - -### ASCII vs UTF-8 vs UTF-16 - -UTF-8 is the most widely used encoding at time of writing. It contains a direct embedding of ASCII, while providing characters for most natural languages, emoji, and special characters. - -Please see the [UTF-8 Everywhere Manifesto](https://utf8everywhere.org/) for more information. - -### When No Text is Found - -Returning a blank string to the requestor fully defeats the purpose of a localization system. The two options for handling missing text are: - -1. A generic "text not found" message in the preferred language -2. The actual message, in a different language - -#### Generic Option - -This designed opted to not use generic fallback text. It does not provide any useful information to the user other than to potentially contact the `Localization` maintainer (if one even exists and updating is even possible). - -#### Fallback Option - -The design outlined in this proposal is to providing text in a commonly used language (ex. English or Mandarin). First, this is the language that will be routed to if the user has yet to set a preference. Second, there is a good chance that a user may have _some_ proficiency with the language, or at least be able to use an automated translation service. - -Knowing that the text fell back via `textFor`s first return field boolean is _much_ simpler than attempting language detection after the fact. This information is useful for certain UI cases. for example where there may be a desire to explain why localization fell back. - -### Decentralized Text Crowdsourcing - -In order for Ethereum to gain mass adoption, users must be able to interact with it in the language, phrasing, and level of detail that they are most comfortable with. Rather than imposing a fixed set of translations as in a traditional, centralized application, this EIP provides a way for anyone to create, curate, and use translations. This empowers the crowd to supply culturally and linguistically diverse messaging, leading to broader and more distributed access to information. - -### `printf`-style Format Strings - -C-style `printf` templates have been the de facto standard for some time. They have wide compatibility across most languages (either in standard or third-party libraries). This makes it much easier for the consuming program to interpolate strings with low developer overhead. - -#### Parameter Fields - -The POSIX parameter field extension is important since languages do not share a common word order. Parameter fields enable the reuse and rearrangement of arguments in different localizations. - -```solidity -("%1$s is an element with the atomic number %2$d!", "Mercury", 80); -// => "Mercury is an element with the atomic number 80!" -``` - -#### Simplified Localizations - -Localization text does not require use of all parameters, and may simply ignore values. This can be useful for not exposing more technical information to users that would otherwise find it confusing. - -```ruby -#!/usr/bin/env ruby - -sprintf("%1$s é um elemento", "Mercurio", 80) -# => "Mercurio é um elemento" -``` - -```clojure -#!/usr/bin/env clojure - -(format "Element #%2$s" "Mercury" 80) -;; => Element #80 -``` - -### Interpolation Strategy - -Please note that it is highly advisable to return the template string _as is_, with arguments as multiple return values or fields in an `event`, leaving the actual interpolation to be done off chain. - - -```solidity -event AtomMessage { - bytes32 templateCode; - bytes32 atomCode; - uint256 atomicNumber; -} -``` - -```javascript -#!/usr/bin/env node - -var printf = require('printf'); - -const { returnValues: { templateCode, atomCode, atomicNumber } } = eventResponse; - -const template = await AppText.textFor(templateCode); -// => "%1$s ist ein Element mit der Ordnungszahl %2$d!" - -const atomName = await PeriodicTableText.textFor(atomCode); -// => "Merkur" - -printf(template, atomName, 80); -// => "Merkur ist ein Element mit der Ordnungszahl 80!" -``` - -### Unspecified Behaviour - -This spec does not specify: - -* Public or private access to the default `Localization` -* Who may set text - * Deployer - * `onlyOwner` - * Anyone - * Whitelisted users - * and so on -* When text is set - * `constructor` - * Any time - * Write to empty slots, but not overwrite existing text - * and so on - -These are intentionally left open. There are many cases for each of these, and restricting any is fully beyond the scope of this proposal. - -## Implementation - -```solidity -pragma solidity ^0.4.25; - -contract Localization { - mapping(bytes32 => string) private dictionary_; - - constructor() public {} - - // Currently overwrites anything - function set(bytes32 _code, string _message) external { - dictionary_[_code] = _message; - } - - function textFor(bytes32 _code) external view returns (string _message) { - return dictionary_[_code]; - } -} - -contract LocalizationPreference { - mapping(address => Localization) private registry_; - Localization public defaultLocalization; - - bytes32 private empty_ = keccak256(abi.encodePacked("")); - - constructor(Localization _defaultLocalization) public { - defaultLocalization = _defaultLocalization; - } - - function set(Localization _localization) external returns (bool) { - registry_[tx.origin] = _localization; - return true; - } - - function get(bytes32 _code) external view returns (bool, string) { - return get(_code, tx.origin); - } - - // Primarily for testing - function get(bytes32 _code, address _who) public view returns (bool, string) { - string memory text = getLocalizationFor(_who).textFor(_code); - - if (keccak256(abi.encodePacked(text)) != empty_) { - return (true, text); - } else { - return (false, defaultLocalization.textFor(_code)); - } - } - - function getLocalizationFor(address _who) internal view returns (Localization) { - if (Localization(registry_[_who]) == Localization(0)) { - return Localization(defaultLocalization); - } else { - return Localization(registry_[tx.origin]); - } - } -} -``` - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1444.md diff --git a/EIPS/eip-1450.md b/EIPS/eip-1450.md index 4e6e0daae175d7..52d07a65016f83 100644 --- a/EIPS/eip-1450.md +++ b/EIPS/eip-1450.md @@ -1,326 +1 @@ ---- -eip: 1450 -title: ERC-1450 A compatible security token for issuing and trading SEC-compliant securities -author: John Shiple (@johnshiple), Howard Marks , David Zhang -discussions-to: https://ethereum-magicians.org/t/erc-proposal-ldgrtoken-a-compatible-security-token-for-issuing-and-trading-sec-compliant-securities/1468 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-09-25 ---- - -# ERC-1450 - A compatible security token for issuing and trading SEC-compliant securities - -## Simple Summary -`ERC-1450` is an `ERC-20` compatible token that enables issuing tokens representing securities that are required to comply with one or more of the following [Securities Act Regulations: Regulation Crowdfunding, Regulation D, and Regulation A](https://www.sec.gov/smallbusiness/exemptofferings). - -## Abstract -`ERC-1450` facilitates the recording of ownership and transfer of securities sold in compliance with the [Securities Act Regulations CF, D and A](https://www.sec.gov/smallbusiness/exemptofferings). The issuance and trading of securities is subject to the Securities Exchange Commission (SEC) and specific U.S. state blue sky laws and regulations. - -`ERC-1450` manages securities ownership during issuance and trading. The Issuer is the only role that should create a `ERC-1450` and assign the RTA. The RTA is the only role that is allowed to execute `ERC-1450`’s `mint`, `burnFrom`, and `transferFrom` functions. No role is allowed to execute `ERC-1450`’s `transfer` function. - -## Motivation -With the advent of the [JOBS Act](https://www.sec.gov/spotlight/jobs-act.shtml) in 2012 and the launch of Regulation Crowdfunding and the amendments to Regulation A and Regulation D in 2016, there has been an expansion in the exemptions available to Issuers and Investors to sell and purchase securities that have not been "registered" with the SEC under the Securities Act of 1933. - -There are currently no token standards that expressly facilitate conformity to securities law and related regulations. ERC-20 tokens do not support the regulated roles of Funding Portal, Broker Dealer, RTA, and Investor and do not support the [Bank Secrecy Act/USA Patriot Act KYC and AML requirements](https://www.occ.treas.gov/topics/compliance-bsa/bsa/index-bsa.html). Other improvements (notably [EIP-1404 (Simple Restricted Token Standard)](https://github.com/ethereum/EIPs/issues/1404) have tried to tackle KYC and AML regulatory requirement. This approach is novel because the RTA is solely responsible for performing KYC and AML and should be solely responsible for `transferFrom`, `mint`, and `burnFrom`. - -## Specification -`ERC-1450` extends `ERC-20`. - -### `ERC-1450` -`ERC-1450` requires that only the Issuer can create a token representing the security that only the RTA manages. Instantiating the `ERC-1450` requires the `Owned` and `IssuerControlled` modifiers, and only the Issuer should execute the `ERC-1450` constructor for a compliant token. `ERC-1450` extends the general `Ownable` modifier to describe a specific subset of owners that automate and decentralize compliance through the contract modifiers `Owned` and `IssuerControlled` and the function modifiers `onlyOwner` and `onlyIssuerTransferAgent`. The `Owned` contract modifier instantiates the `onlyOwner` modifier for functions. The `IssuerControlled` modifier instantiates the `onlyIssuerTransferAgent` modifier for functions. - -`ERC-1450` must prevent anyone from executing the `transfer`, `allowance`, and `approve` functions and/or implement these functions to always fail. `ERC-1450` updates the `transferFrom`, `mint`, and `burnFrom` functions. `transferFrom`, `mint`, and `burnFrom` may only be executed by the RTA and are restricted with the `onlyIssuerTransferAgent` modifier. Additionally, `ERC-1450` defines the functions `transferOwnership`, `setTransferAgent`, `setPhysicalAddressOfOperation`, and `isTransferAgent`. Only the issuer may call the `transferOwnership`, `setTransferAgent`, and `setPhysicalAddressOfOperation` functions. Anyone may call the `isTransferAgent` function. - -### Issuers and RTAs -For compliance reasons, the `ERC-1450` constructor must specify the issuer (the `owner`), the RTA (`transferAgent`), the security’s `name`, and the security’s `symbol`. - -#### Issuer Owned -`ERC-1450` must specify the `owner` in its constructor, apply the `Owned` modifier, and instantiate the `onlyOwner` modifier to enable specific functions to permit only the Issuer’s `owner` address to execute them. `ERC-1450` also defines the function `transferOwnership` which transfers ownership of the Issuer to the new `owner`’s address and can only be called by the `owner`. `transferOwnership` triggers the `OwnershipTransferred` event. - -#### Issuer Controlled -`IssuerControlled` maintains the Issuer’s ownership of their securities by owning the contract and enables the Issuer to set and update the RTA for the Issuer’s securities. `ERC-1450`‘s constructor must have an `IssuerControlled` modifier with the issuer specified in its `ERC-1450` constructor. `IssuerControlled` instantiates the `onlyIssuerTransferAgent` modifier for `ERC-1450` to enable specific functions (`setPhysicalAddressOfOperation` and `setTransferAgent`) to permit only the Issuer to execute these functions. - -#### Register Transfer Agent Controlled -`ERC-1450` defines the `setTransferAgent` function (to change the RTA) and `setPhysicalAddressOfOperation` function (to change the Issuer’s address) and must restrict execution to the Issuer’s owner with the `onlyOwner` modifier. `setTransferAgent` must emit the `TransferAgentUpdated` event. `setPhysicalAddressOfOperation` must emit the `PhysicalAddressOfOperationUpdated` event. - -`ERC-1450` must specify the `transferAgent` in its constructor and instantiate the `onlyIssuerTransferAgent` modifier to enable specific functions (`transferFrom`, `mint`, and `burnFrom`) to permit only the Issuer’s `transferAgent` address to execute them. `ERC-1450` also defines the public function `isTransferAgent` to lookup and identify the Issuer’s RTA. - -#### Securities -`ERC-1450` updates the `transferFrom`, `mint`, and `burnFrom` functions by applying the `onlyIssuerTransferAgent` to enable the issuance, re-issuance, and trading of securities. - -### ERC-20 Extension -`ERC-20` tokens provide the following functionality: - -```solidity -contract ERC20 { - function totalSupply() public view returns (uint256); - function balanceOf(address who) public view returns (uint256); - function transfer(address to, uint256 value) public returns (bool); - function allowance(address owner, address spender) public view returns (uint256); - function transferFrom(address from, address to, uint256 value) public returns (bool); - function approve(address spender, uint256 value) public returns (bool); - event Approval(address indexed owner, address indexed spender, uint256 value); - event Transfer(address indexed from, address indexed to, uint256 value); -} -``` - -`ERC-20` is extended as follows: - -```solidity -/** - * ERC-1450 is an ERC-20 compatible token that facilitates compliance with one or more of Securities Act Regulations CF, D and A. - * - * Implementations of the ERC-1450 standard must define the following optional ERC-20 - * fields: - * - * name - The name of the security - * symbol - The symbol of the security - * - * Implementations of the ERC-1450 standard must specify the following constructor - * arguments: - * - * _owner - the address of the owner - * _transferAgent - the address of the transfer agent - * _name - the name of the security - * _symbol - the symbol of the security - * - * Implementations of the ERC-1450 standard must implement the following contract - * modifiers: - * - * Owned - Only the address of the security’s issuer is permitted to execute the - * token’s constructor. This modifier also sets up the onlyOwner function modifier. - * IssuerControlled - This modifier sets up the onlyIssuerTransferAgent function modifier. - * - * Implementations of the ERC-1450 standard must implement the following function - * modifiers: - * - * onlyOwner - Only the address of the security’s issuer is permitted to execute the - * functions transferOwnership, setTransferAgent, and setPhysicalAddressOfOperation. - * onlyIssuerTransferAgent - Only the address of the issuer’s Registered Transfer - * Agent is permitted to execute the functions transferFrom, mint, and burnFrom. - * - * Implementations of the ERC-1450 standard must implement the following required ERC-20 - * event to always fail: - * - * Approval - Should never be called as the functions that emit this event must be - * implemented to always fail. - * - * Implementations of the ERC-1450 standard must implement the following required - * ERC-20 functions to always fail: - * - * transfer - Not a legal, regulated call for transferring securities because - * the token holder initiates the token transfer. The function must be implemented to - * always fail. - * allowance - Not a legal, regulated call for transferring securities because - * the token holder may not allow third parties to initiate token transfers. The - * function must be implemented to always fail. - * approve - Not a legal, regulated call for transferring securities because - * the token holder may not allow third parties to initiate token transfers. The - * function must be implemented to always fail. - * - * Implementations of the ERC-1450 standard must implement the following optional - * ERC-20 function: - * decimals - Must return '0' because securities are indivisible entities. - * - * Implementations of the ERC-1450 standard must implement the following functions: - * - * mint - Only the address of the issuer's Registered Transfer Agent may create new - * securities. - * burnFrom - Only the address of the issuer’s Registered Transfer Agent may burn or - * destroy securities. - */ - -Contract ERC-1450 is Owned, IssuerControlled { - - /** - * The constructor must implement a modifier (Owned) that creates the onlyOwner modifier - * to allow only the address of the issuer (the owner) to execute the transferOwnership, - * setTransferAgent, and setPhysicalAddressOfOperation functions. The construct must also - * implement a modifier (TransferAgentControlled) that creates the onlyIssuerTransferAgent - * modifier to allow only the address of the issuer’s Registered Transfer Agent to execute - * the functions transferFrom, mint, and burnFrom). - */ - constructor(address _owner, address _transferAgent, string _name, string _symbol) - Owned(_issuer) TransferAgentControlled(_transferAgent) public; - - /** - * Specify that only the owner (issuer) may execute a function. - * - * onlyOwner requires the msg.sender to be the owner’s address. - */ - modifier onlyOwner(); - - /** - * Specify that only the issuer’s transferAgent may execute a function. - * - * onlyIssuerTransferAgent requires the msg.sender to be the transferAgent’s address. - */ - modifier onlyIssuerTransferAgent(); - - /** - * Transfer ownership of a security from one issuer to another issuer. - * - * transferOwnership must implement the onlyOwner modifier to only allow the - * address of the issuer’s owner to transfer ownership. - * transferOwnership requires the _newOwner address to be the address of the new - * issuer. - */ - function transferOwnership(address _newOwner) public onlyOwner; - - /** - * Triggered after transferOwnership is executed. - */ - event OwnershipTransferred() - - /** - * Sets the transfer agent for the security. - * - * setTransferAgent must implement the onlyOwner modifier to only allow the - * address of the issuer’s specify the security’s transfer agent. - * setTransferAgent requires the _newTransferAgent address to be the address of the - * new transfer agent. - */ - function setTransferAgent(address _newTransferAgent) public onlyOwner; - - /** - * Triggered after setTransferAgent is executed. - */ - event TransferAgentUpdated(address indexed previousTransferAgent, address indexed - newTransferAgent); - - /** - * Sets the issuers physical address of operation. - * - * setPhysicalAddressOfOperation must implement the onlyOwner modifier to only allow - * the address of the issuer’s owner to transfer ownership. - * setPhysicalAddressOfOperation requires the _newPhysicalAddressOfOperation address - * to be the new address of the issuer. - */ - function setPhysicalAddressOfOperation(string _newPhysicalAddressOfOperation) public - onlyOwner; - - /** - * Triggered after setPhysicalAddressOfOperation is executed. - */ - event PhysicalAddressOfOperationUpdated(string previousPhysicalAddressOfOperation, - string newPhysicalAddressOfOperation); - - /** - * Look up the security’s transfer agent. - * - * isTransferAgent is a public function. - * isTransferAgent requires the _lookup address to determine if that address - * is the security’s transfer agent. - */ - function isTransferAgent(address _lookup) public view returns (bool); - - /** - * transfer is not a legal, regulated call and must be implemented to always fail. - */ - transfer(address to, uint tokens) public returns (bool success); - - /** - * Approval does not have to be implemented. This event should never be triggered as - * the functions that emit this even are not legal, regulated calls. - */ - event Approval(address indexed tokenOwner, address indexed spender, uint tokens); - - /** - * allowance is not a legal, regulated call and must be implemented to always fail. - */ - allowance(address tokenOwner, address spender) public constant returns (uint remaining); - - /** - * approve is not a legal, regulated call and must be implemented to always fail. - */ - approve(address spender, uint tokens) public returns (bool success); - - /** - * Transfer securities. - * - * transferFrom must implement the onlyIssuerTransferAgent modifier to only allow the - * address of the issuer’s Registered Transfer Agent to transfer `ERC-1450`s. - * transferFrom requires the _from address to have _value tokens. - * transferFrom requires that the _to address must not be 0 because securities must - * not destroyed in this manner. - */ - function transferFrom(address _from, address _to, uint256 _value) public - onlyIssuerTransferAgent returns (bool); - - /** - * Create new securities. - * - * mint must implement the onlyIssuerTransferAgent modifier to only allow the address - * of the issuer’s Registered Transfer Agent to mint `ERC-1450` tokens. - * mint requires that the _to address must not be 0 because securities must - * not destroyed in this manner. - * mint must add _value tokens to the _to address and increase the totalSupply by - * _value. - * mint must emit the Transfer event. - */ - function mint(address _to, uint256 _value) public onlyIssuerTransferAgent returns - (bool); - - /** - * Burn or destroy securities. - * - * burnFrom must implement the onlyIssuerTransferAgent modifier to only allow the - * address of the issuer’s Registered Transfer Agent to burn `ERC-1450`s. - * burnFrom requires the _from address to have _value tokens. - * burnFrom must subtract _value tokens from the _from address and decrease the - * totalSupply by _value. - * burnFrom must emit the Transfer event. - */ - function burnFrom(address _who, uint256 _value) public onlyIssuerTransferAgent returns - (bool); -} -``` - -### Securities Exchange Commission Requirements -The SEC has very strict requirements as to the specific roles that are allowed to perform specific actions. Specifically, only the RTA may `mint` and `transferFrom` securities. - -Implementers must maintain off-chain services and databases that record and track the Investor’s name, physical address, Ethereum address, and security ownership amount. The implementers and the SEC must be able to access the Investor’s private information on an as needed basis. Issuers and the RTA must be able to produce a current list of all Investors, including the names, addresses, and security ownership levels for every security at any given moment. Issuers and the RTA must be able to re-issue securities to Investors for a variety of regulated reasons. - -Private Investor information must never be publicly exposed on a public blockchain. - -### Managing Investor Information -Special care and attention must be taken to ensure that the personally identifiable information of Investors is never exposed or revealed to the public. - -### Issuers who lost access to their address or private keys -There is no recourse if the Issuer loses access to their address to an existing instance of their securities. Special care and efforts must be made by the Issuer to secure and safely store their address and associated private key. The Issuer can reassign ownership to another Issuer but not in the case where the Issuer loses their private key. - -If the Issuer loses access, the Issuer’s securities must be rebuilt using off-chain services. The Issuer must create (and secure) a new address. The RTA can read the existing Issuer securities, and the RTA can `mint` Investor securities accordingly under a new `ERC-1450` smart contract. - -### Registered Transfer Agents who lost access to their address or private keys -If the RTA loses access, the RTA can create a new Ethereum address, and the Issuer can execute the `setTransferAgent` function to reassign the RTA. - -### Handling Investors (security owners) who lost access to their addresses or private keys -Investors may “lose” their credentials for a number of reasons: they simply “lost” their credentials, they were hacked or the victim of fraud, they committed securities-related fraud, or a life event (like death) occurred. Because the RTA manages the Issuer’s securities, the RTA may authorize ownership related changes of securities (as long as they are properly notarized and verified). - -If an Investor (or, say, the Investor’s heir) loses their credentials, the Investor must go through a notarized process to notify the RTA of the situation and supply a new Investor address. From there, the RTA can `mint` the “lost” securities to the new Investor address and `burnFrom` the old Investor address (because the RTA knows all Investors’ addresses). - -## Rationale -The are currently no token standards that facilitate compliance with SEC regulations. The closest token is [ERC-884 (Delaware General Corporations Law (DGCL) compatible share token)](./eip-884.md) which states that SEC requirements are out of scope. [EIP-1404 (Simple Restricted Token Standard)](https://github.com/ethereum/EIPs/issues/1404) does not go far enough to address SEC requirements around re-issuing securities to Investors. - -## Backwards Compatibility -`ERC-1450` maintains compatibility with ERC-20 tokens with the following stipulations: -* `function allowance(address tokenOwner, address spender) public constant returns (uint remaining);` - * Must be implemented to always fail because allowance is not a legal, regulated call for a security. -* `function transfer(address to, uint tokens) public returns (bool success);` - * As the token holder initiates the transfer, must be implemented to always fail because transfer is not a legal, regulated call for a security. -* `function approve(address spender, uint tokens) public returns (bool success);` - * Must be implemented to always fail because approve is not a legal, regulated call for a security -* `function transferFrom(address from, address to, uint tokens) public returns (bool success);` - * Must be implemented so that only the Issuer’s RTA can perform this action -* `event Approval(address indexed tokenOwner, address indexed spender, uint tokens);` - * Does not have to be implemented. Approval should never be called as the functions that emit this event must be implemented to always fail - -## Test Cases -Test cases are available at [https://github.com/StartEngine/ldgr_smart_contracts/tree/master/test](https://github.com/StartEngine/ldgr_smart_contracts/tree/master/test). - -## Implementations -A reference implementation is available at [https://github.com/StartEngine/ldgr_smart_contracts](https://github.com/StartEngine/ldgr_smart_contracts). - -## Copyright Waiver -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1450.md diff --git a/EIPS/eip-1462.md b/EIPS/eip-1462.md index 9bb902088f7839..2def3349218258 100644 --- a/EIPS/eip-1462.md +++ b/EIPS/eip-1462.md @@ -1,117 +1 @@ ---- -eip: 1462 -title: Base Security Token -author: Maxim Kupriianov , Julian Svirsky -discussions-to: https://ethereum-magicians.org/t/erc-1462-base-security-token/1501 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-10-01 -requires: 20, 1066 ---- - -## Simple Summary - -An extension to ERC-20 standard token that provides compliance with securities regulations and legal enforceability. - -## Abstract - -This EIP defines a minimal set of additions to the default token standard such as [ERC-20](./eip-20.md), that allows for compliance with domestic and international legal requirements. Such requirements include KYC (Know Your Customer) and AML (Anti Money Laundering) regulations, and the ability to lock tokens for an account, and restrict them from transfer due to a legal dispute. Also the ability to attach additional legal documentation, in order to set up a dual-binding relationship between the token and off-chain legal entities. - -The scope of this standard is being kept as narrow as possible to avoid restricting potential use-cases of this base security token. Any additional functionality and limitations not defined in this standard may be enforced on per-project basis. - -## Motivation - -There are several security token standards that have been proposed recently. Examples include [ERC-1400](https://github.com/ethereum/EIPs/issues/1411), also [ERC-1450](https://eips.ethereum.org/EIPS/eip-1450). We have concerns about each of them, mostly because the scope of each of these EIPs contains many project-specific or market-specific details. Since many EIPs are coming from the respective backing companies, they capture many niche requirements that are excessive for a general case. - -For instance, ERC-1411 uses dependency on [ERC-1410](https://github.com/ethereum/eips/issues/1410) but it falls out of the "security tokens" scope. Also its dependency on [ERC-777](./eip-777.md) will block the adoption for a quite period of time before ERC-777 is finalized, but the integration guidelines for existing ERC-20 workflows are not described in that EIP, yet. Another attempt to make a much simpler base standard [ERC-1404](https://github.com/ethereum/EIPs/issues/1404) is missing a few important points, specifically it doesn't provide enough granularity to distinguish between different ERC-20 transfer functions such as `transfer` and `transferFrom`. It also doesn't provide a way to bind legal documentation to the issued tokens. - -What we propose in this EIP is a simple and very modular solution for creating a base security token for the widest possible scope of applications, so it can be used by other issuers to build upon. The issuers should be able to add more restrictions and policies to the token, using the functions and implementation proposed below, but they must not be limited in any way while using this ERC. - -## Specification - -The ERC-20 token provides the following basic features: - -```solidity -contract ERC20 { - function totalSupply() public view returns (uint256); - function balanceOf(address who) public view returns (uint256); - function transfer(address to, uint256 value) public returns (bool); - function allowance(address owner, address spender) public view returns (uint256); - function transferFrom(address from, address to, uint256 value) public returns (bool); - function approve(address spender, uint256 value) public returns (bool); - event Approval(address indexed owner, address indexed spender, uint256 value); - event Transfer(address indexed from, address indexed to, uint256 value); -} -``` - -This will be extended as follows: - -```solidity -interface BaseSecurityToken /* is ERC-20 */ { - // Checking functions - function checkTransferAllowed (address from, address to, uint256 value) public view returns (byte); - function checkTransferFromAllowed (address from, address to, uint256 value) public view returns (byte); - function checkMintAllowed (address to, uint256 value) public view returns (byte); - function checkBurnAllowed (address from, uint256 value) public view returns (byte); - - // Documentation functions - function attachDocument(bytes32 _name, string _uri, bytes32 _contentHash) external; - function lookupDocument(bytes32 _name) external view returns (string, bytes32); -} -``` - -### Transfer Checking Functions - -We introduce four new functions that should be used to check that the actions are allowed for the provided inputs. The implementation details of each function are left for the token issuer, it is the issuer's responsibility to add all necessary checks that will validate an operation in accordance with KYC/AML policies and legal requirements set for a specific token asset. - -Each function must return a status code from the common set of Ethereum status codes (ESC), according to [ERC-1066](./eip-1066.md). Localization of these codes is out of the scope of this proposal and may be optionally solved by adopting [ERC-1444](./eip-1444.md) on the application level. If the operation is allowed by a checking function, the return status code must be `0x11` (Allowed) or an issuer-specific code with equivalent but more precise meaning. If the operation is not allowed by a checking function, the status must be `0x10` (Disallowed) or an issuer-specific code with equivalent but more precise meaning. Upon an internal error, the function must return the most relevant code from the general code table or an issuer-specific equivalent, example: `0xF0` (Off-Chain Failure). - -**For [ERC-20](./eip-20.md) based tokens,** -* It is required that transfer function must be overridden with logic that checks the corresponding checkTransferAllowed return status code. -* It is required that `transferFrom` function must be overridden with logic that checks the corresponding `checkTransferFromAllowed` return status code. -* It is required that `approve` function must be overridden with logic that checks the corresponding `checkTransferFromAllowed` return status code. -* Other functions such as `mint` and `burn` must be overridden, if they exist in the token implementation, they should check `checkMintAllowed` and `checkBurnAllowed` status codes accordingly. - -**For [ERC-777](./eip-777.md) based tokens,** -* It is required that `send` function must be overridden with logic that checks the corresponding return status codes: - - `checkTransferAllowed` return status code, if transfer happens on behalf of the tokens owner; - - `checkTransferFromAllowed` return status code, if transfer happens on behalf of an operator (i.e. delegated transfer). -* It is required that `burn` function must be overridden with logic that checks the corresponding `checkBurnAllowed` return status code. -* Other functions, such as `mint` must be overridden, if they exist in the token implementation, e.g. if the security token is mintable. `mint` function must call `checkMintAllowed` ad check it return status code. - -For both cases, - -* It is required for guaranteed compatibility with ERC-20 and ERC-777 wallets that each checking function returns `0x11` (Allowed) if not overridden with the issuer's custom logic. -* It is required that all overridden checking functions must revert if the action is not allowed or an error occurred, according to the returned status code. - -Inside checker functions the logic is allowed to use any feature available on-chain: perform calls to registry contracts with whitelists/blacklists, use built-in checking logic that is defined on the same contract, or even run off-chain queries through an oracle. - -### Documentation Functions - -We also introduce two new functions that should be used for document management purposes. Function `attachDocument` adds a reference pointing to an off-chain document, with specified name, URI and contents hash. The hashing algorithm is not specified within this standard, but the resulting hash must not be longer than 32 bytes. Function `lookupDocument` gets the referenced document by its name. - -* It is not required to use documentation functions, they are optional and provided as a part of a legal framework. -* It is required that if `attachDocument` function has been used, the document reference must have a unique name, overwriting the references under same name is not allowed. All implementations must check if the reference under the given name is already existing. - -## Rationale - -This EIP targets both ERC-20 and ERC-777 based tokens, although the most emphasis is given to ERC-20 due to its widespread adoption. However, this extension is designed to be compatible with the forthcoming ERC-777 standard, as well. - -All checking functions are named with prefixes `check` since they return check status code, not booleans, because that is important to facilitate the debugging and tracing process. It is responsibility of the issuer to implement the logic that will handle the return codes appropriately. Some handlers will simply throw errors, other handlers would log information for future process mining. More rationale for status codes can be seen in [ERC-1066](./eip-1066.md). - -We require two different transfer validation functions: `checkTransferAllowed` and `checkTransferFromAllowed` since the corresponding `transfer` and `transferFrom` are usually called in different contexts. Some token standards such as [ERC-1450](./eip-1450.md) explicitly disallow use of `transfer`, while allowing only `transferFrom`. There might be also different complex scenarios, where `transfer` and `transferFrom` should be treated differently. ERC-777 is relying on its own `send` for transferring tokens, so it is reasonable to switch between checker functions based on its call context. We decided to omit the `checkApprove` function since it would be used in exactly the same context as `checkTransferFromAllowed`. In many cases it is required not only regulate securities transfers, but also restrict burn and `mint` operations, and additional checker functions have been added for that. - -The documentation functions that we propose here are a must-have tool to create dual-bindings with off-chain legal documents, a great example of this can be seen in [Neufund's Employee Incentive Options Plan](https://medium.com/@ZoeAdamovicz/37376fd0384a) legal framework that implements full legal enforceability: the smart contract refers to printed ESOP Terms & Conditions Document, which itself refers back to smart contract. This is becoming a widely adopted practice even in cases where there are no legal requirements to reference the documents within the security token. However they're almost always required, and it's a good way to attach useful documentation of various types. - -## Backwards Compatibility - -This EIP is fully backwards compatible as its implementation extends the functionality of ERC-20 and ERC-777 tokens. - -## Implementation - -* https://github.com/AtlantPlatform/BaseSecurityToken - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1462.md diff --git a/EIPS/eip-1484.md b/EIPS/eip-1484.md index 396602186bb305..9f2271dcbc34b6 100644 --- a/EIPS/eip-1484.md +++ b/EIPS/eip-1484.md @@ -1,544 +1 @@ ---- -eip: 1484 -title: Digital Identity Aggregator -author: Anurag Angara , Andy Chorlian , Shane Hampton , Noah Zinsmeister -discussions-to: https://github.com/ethereum/EIPs/issues/1495 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-10-12 -requires: 191 ---- - -## Simple Summary -A protocol for aggregating digital identity information that's broadly interoperable with existing, proposed, and hypothetical future digital identity standards. - -## Abstract -This EIP proposes an identity management and aggregation framework on the Ethereum blockchain. It allows entities to claim an `Identity` via a singular `Identity Registry` smart contract, associate it with Ethereum addresses in a variety of meaningful ways, and use it to interact with smart contracts. This enables arbitrarily complex identity-related functionality. Notably (among other features) ERC-1484 `Identities`: are self-sovereign, can natively support [ERC-725](./eip-725.md) and [ERC-1056](./eip-1056.md) identities, are [DID compliant](https://github.com/NoahZinsmeister/ERC-1484/blob/master/best-practices/DID-Method.md), and can be fully powered by [meta-transactions](https://github.com/NoahZinsmeister/ERC-1484/tree/master/contracts/examples/Providers/MetaTransactions). - -## Motivation -Emerging identity standards and related frameworks proposed by the Ethereum community (including ERCs/EIPs [725](./eip-725.md), [735](https://github.com/ethereum/EIPs/issues/735), [780](https://github.com/ethereum/EIPs/issues/780), [1056](./eip-1056.md), etc.) define and instrumentalize digital identity in a variety of ways. As existing approaches mature, new standards emerge, and isolated, non-standard approaches to identity develop, coordinating on identity will become increasingly burdensome for blockchain users and developers, and involve the unnecessary duplication of work. - -The proliferation of on-chain identity solutions can be traced back to the fact that each codifies a notion of identity and links it to specific aspects of Ethereum (claims protocols, per-identity smart contracts, signature verification schemes, etc.). This proposal eschews that approach, instead introducing a protocol layer in between the Ethereum network and individual identity applications. This solves identity management and interoperability challenges by enabling any identity-driven application to leverage an un-opinionated identity management protocol. - -## Definitions -- `Identity Registry`: A single smart contract which is the hub for all `Identities`. The primary responsibility of the `Registry` is to define and enforce the rules of a global namespace for `Identities`, which are individually denominated by Ethereum Identification Numbers (EINs). - -- `Identity`: A data structure containing all the core information relevant to an identity, namely: a `Recovery Address`, an `Associated Addresses` set, a `Providers` set, and a `Resolvers` set. `Identities` are denominated by EINs (incrementing `uint` identifiers starting at 1), which are unique but otherwise uninformative. Each `Identity` is a Solidity struct: - -```solidity -struct Identity { - address recoveryAddress; - AddressSet.Set associatedAddresses; - AddressSet.Set providers; - AddressSet.Set resolvers; -} -``` - -- `Associated Address`: An Ethereum address publicly associated with an `Identity`. In order for an address to become an `Associated Address`, an `Identity` must either transact from or produce an appropriately signed message from the candidate address and an existing `Associated Address`, indicating intent to associate. An `Associated Address` can be removed from an `Identity` by transacting/producing a signature indicating intent to disassociate. A given address may only be an `Associated Address` for one `Identity` at any given time. - -- `Provider`: An Ethereum address (typically but not by definition a smart contract) authorized to act on behalf of `Identities` who have authorized them to do so. This includes but is not limited to managing the `Associated Address`, `Provider`, and `Resolver` sets for an `Identity`. `Providers` exist to facilitate user adoption by making it easier to manage `Identities`. - -- `Resolver`: A smart contract containing arbitrary information pertaining to `Identities`. A resolver may implement an identity standard, such as ERC-725, or may consist of a smart contract leveraging or declaring identifying information about `Identities`. These could be simple attestation structures or more sophisticated financial dApps, social media dApps, etc. Each `Resolver` added to an `Identity` makes the `Identity` more informative. - -- `Recovery Address`: An Ethereum address (either an account or smart contract) that can be used to recover lost `Identities` as outlined in the [Recovery](#recovery) section. - -- `Destruction`: In the event of irrecoverable loss of control of an `Identity`, `Destruction` is a contingency measure to permanently disable the `Identity`. It removes all `Associated Addresses`, `Providers`, and optionally `Resolvers` while preserving the `Identity`. Evidence of the existence of the `Identity` persists, while control over the `Identity` is nullified. - -## Specification -A digital identity in this proposal can be viewed as an omnibus account, containing more information about an identity than any individual identity application could. This omnibus identity is resolvable to an unlimited number of sub-identities called `Resolvers`. This allows an atomic entity, the `Identity`, to be resolvable to abstract data structures, the `Resolvers`. `Resolvers` recognize `Identities` by any of their `Associated Addresses`, or by their `EIN`. - -The protocol revolves around claiming an `Identity` and managing `Associated Addresses`, `Providers` and `Resolvers`. Identities can delegate much or all of this responsibility to one or more `Providers`, or perform it directly from an `Associated Address`. `Associated Addresses`/`Providers` may add and remove `Resolvers` and `Providers` indiscriminately. `Associated Addresses` may only be added or removed with the appropriate permission. - -### Identity Registry -The `Identity Registry` contains functionality to create new `Identities` and for existing `Identities` to manage their `Associated Addresses`, `Providers`, and `Resolvers`. It is important to note that this registry fundamentally requires transactions for every aspect of building out an `Identity`. However, recognizing the importance of accessibility to dApps and identity applications, we empower `Providers` to build out `Identities` on the behalf of users, without requiring users to pay gas costs. An example of this pattern, often referred to as a meta transactions, can be [seen in the reference implementation](https://github.com/NoahZinsmeister/ERC-1484/tree/master/contracts/examples/Providers/MetaTransactions). - -Due to the fact that multiple addresses can be associated with a given identity (though not the reverse), `Identities` are denominated by `EIN`. This `uint` identifier can be encoded in QR format or mapped to more user-friendly formats, such as a `string`, in registries at the `Provider` or `Resolver` level. - -### Address Management -The address management function consists of trustlessly connecting multiple user-owned `Associated Addresses` to an `Identity`. It does not give special status to any particular `Associated Address`, rather leaving this (optional) specification to identity applications built on top of the protocol - for instance, `management`, `action`, `claim` and `encryption` keys denominated in the ERC-725 standard, or `identifiers` and `delegates` as denominated in ERC-1056. This allows a user to access common identity data from multiple wallets while still: - -- retaining the ability to interact with contracts outside of their identity -- taking advantage of address-specific permissions established at the application layer of a user's identity. - -Trustlessness in the address management function is achieved through a robust permissioning scheme. To add an `Associated Address` to an `Identity`, implicit permission from a transaction sender or explicit permission from a signature is required from 1) an address already within the registry and 2) an address to be claimed. Importantly, the transaction need not come from any particular address, as long as permission is established, which allows not only users but third parties (companies, governments, etc.) to bear the overhead of managing identities. To prevent a compromised `Associated Address` from unilaterally removing other `Associated Addresses`, it's only possible to remove an `Associated Address` by transacting or producing a signature from it. - -All signatures required in ERC-1484 are designed per the [ERC-191](./eip-191.md) v0 specification. To avoid replay attacks, all signatures must include a timestamp within a rolling lagged window of the current `block.timestamp`. For more information, see this [best practices document](https://github.com/NoahZinsmeister/ERC-1484/blob/master/best-practices/VerifyingSignatures.md) in the reference implementation. - -### Provider Management -While the protocol allows users to directly call identity management functions, it also aims to be more robust and future-proof by allowing `Providers`, typically smart contracts, to perform identity management functions on a user's behalf. A `Provider` set by an `Identity` can perform address management and resolver management functions by passing a user's `EIN` in function calls. - -### Resolver Management -A `Resolver` is any smart contract that encodes information which resolves to an `Identity`. We remain agnostic about the specific information that can be encoded in a resolver and the functionality that this enables. The existence of `Resolvers` is primarily what makes this ERC an identity *protocol* rather than an identity *application*. `Resolvers` resolve abstract data in smart contracts to an atomic entity, the `Identity`. - -### Recovery -If users lose control over an `Associated Address`, the `Recovery Address` provides a fallback mechanism. Upon `Identity` creation, a `Recovery Address` is passed as a parameter by the creator. Recovery functionality is triggered in three scenarios: - -**1. Changing Recovery Address**: If a recovery key is lost, an `Associated Address`/`Provider` can [triggerRecoveryAddressChange](#triggerrecoveryaddresschange)/[triggerRecoveryAddressChangeFor](#triggerrecoveryaddresschangefor). To prevent malicious behavior from someone who has gained control of an `Associated Address` or `Provider` and is changing the `Recovery Address` to one under their control, this action triggers a 14 day challenge period during which the old `Recovery Address` may reject the change by [triggering recovery](#triggerrecovery). If the `Recovery Address` does not reject the change within 14 days, the `Recovery Address` is changed. - -**2. Recovery**: Recovery occurs when a user recognizes that an `Associated Address` or the `Recovery Address` belonging to the user is lost or stolen. In this instance the `Recovery Address` must call [triggerRecovery](#triggerrecovery). This removes all `Associated Addresses` and `Providers` from the corresponding `Identity` and replaces them with an address passed in the function call. The `Identity` and associated `Resolvers` maintain integrity. The user is now responsible for adding the appropriate un-compromised addresses back to their `Identity`. - -*Importantly, the `Recovery Address` can be a user-controlled wallet or another address, such as a multisig wallet or smart contract. This allows for arbitrarily sophisticated recovery logic! This includes the potential for recovery to be fully compliant with standards such as [DID](https://decentralized.id/).* - -**3. Destruction** -The Recovery scheme offers considerable power to a `Recovery Address`; accordingly, `Destruction` is a nuclear option to combat malicious control over an `Identity` when a `Recovery Address` is compromised. If a malicious actor compromises a user's `Recovery Address` and triggers recovery, any address removed in the `Recovery` process can call [triggerDestruction](#triggerdestruction) within 14 days to permanently disable the `Identity`. The user would then need to create a new `Identity`, and would be responsible for engaging in recovery schemes for any identity applications built in the `Resolver` or `Provider` layers. - -#### Alternative Recovery Considerations -We considered many possible alternatives when devising the Recovery process outlined above. We ultimately selected the scheme that was most un-opinionated, modular, and consistent with the philosophy behind the `Associated Address`, `Provider`, and `Resolver` components. Still, we feel that it is important to highlight some of the other recovery options we considered, to provide a rationale as to how we settled on what we did. - -**High Level Concerns** -Fundamentally, a Recovery scheme needs to be resilient to a compromised address taking control of a user's `Identity`. A secondary concern is preventing a compromised address from maliciously destroying a user's identity due to off-chain utility, which is not an optimal scenario, but is strictly better than if they've gained control. - -**Alternative 1: Nuclear Option** -This approach would allow any `Associated Address` to destroy an `Identity` whenever another `Associated Address` is compromised. While this may seem severe, we strongly considered it because this ERC is an identity *protocol*, not an identity *application*. This means that though a user's compromised `Identity` is destroyed, they should still have recourse to whatever restoration mechanisms are available in each of their actual identities at the `Resolver` and/or `Provider` level. We ultimately dismissed this approach for two main reasons: - -- It is not robust in cases where a user has only one `Associated Address` -- It would increase the frequency of recovery requests to identity applications due to its unforgiving nature. - -**Alternative 2: Unilateral Address Removal via Providers** -This would allow `Associated Addresses`/`Providers` to remove `Associated Addresses` without a signature from said address. This implementation would allow `Providers` to include arbitrarily sophisticated schemes for removing a rogue address - for instance, multi-sig requirements, centralized off-chain verification, user controlled master addresses, deferral to a jurisdictional contract, and more. To prevent a compromised `Associated Address` from simply setting a malicious `Provider` to remove un-compromised addresses, it would have required a waiting period between when a `Provider` is set and when they would be able to remove an `Associated Address`. We dismissed this approach because we felt it placed too high of a burden on `Providers`. If a `Provider` offered a sophisticated range of functionality to a user, but post-deployment a threat was found in the Recovery logic of the provider, `Provider`-specific infrastructure would need to be rebuilt. We also considered including a flag that would allow a user to decide whether or not a `Provider` may remove `Associated Addresses` unilaterally. Ultimately, we concluded that only allowing removal of `Associated Addresses` via the `Recovery Address` enables equally sophisticated recovery logic while separating the functionality from `Providers`, leaving less room for users to relinquish control to potentially flawed implementations. - -## Rationale -We find that at a protocol layer, identities should not rely on specific claim or attestation structures, but should instead be a part of a trustless framework upon which arbitrarily sophisticated claim and attestation structures may be built. - -The main criticism of existing identity solutions is that they're overly restrictive. We aim to limit requirements, keep identities modular and future-proof, and remain un-opinionated regarding any functionality a particular identity component may have. This proposal gives users the option to interact on the blockchain using an robust `Identity` rather than just an address. - -## Implementation -**The reference implementation for ERC-1484 may be found in [NoahZinsmeister/ERC-1484](https://github.com/NoahZinsmeister/ERC-1484).** - -#### identityExists - -Returns a `bool` indicating whether or not an `Identity` denominated by the passed `EIN` exists. - -```solidity -function identityExists(uint ein) public view returns (bool); -``` - -#### hasIdentity - -Returns a `bool` indicating whether or not the passed `_address` is associated with an `Identity`. - -```solidity -function hasIdentity(address _address) public view returns (bool); -``` - -#### getEIN - -Returns the `EIN` associated with the passed `_address`. Throws if the address is not associated with an `EIN`. - -```solidity -function getEIN(address _address) public view returns (uint ein); -``` - -#### isAssociatedAddressFor - -Returns a `bool` indicating whether or not the passed `_address` is associated with the passed `EIN`. - -```solidity -function isAssociatedAddressFor(uint ein, address _address) public view returns (bool); -``` - -#### isProviderFor - -Returns a `bool` indicating whether or not the passed `provider` has been set by the passed `EIN`. - -```solidity -function isProviderFor(uint ein, address provider) public view returns (bool); -``` - -#### isResolverFor - -Returns a `bool` indicating whether or not the passed `resolver` has been set by the passed `EIN`. - -```solidity -function isResolverFor(uint ein, address resolver) public view returns (bool); -``` - -#### getIdentity - -Returns the `recoveryAddress`, `associatedAddresses`, `providers` and `resolvers` of the passed `EIN`. - -```solidity -function getIdentity(uint ein) public view - returns ( - address recoveryAddress, - address[] memory associatedAddresses, address[] memory providers, address[] memory resolvers - ); -``` - -#### createIdentity - -Creates an `Identity`, setting the `msg.sender` as the sole `Associated Address`. Returns the `EIN` of the new `Identity`. - -```solidity -function createIdentity(address recoveryAddress, address[] memory providers, address[] memory resolvers) - public returns (uint ein); -``` - -Triggers event: [IdentityCreated](#identitycreated) - -#### createIdentityDelegated - -Performs the same logic as `createIdentity`, but can be called by any address. This function requires a signature from the `associatedAddress` to ensure their consent. - -```solidity -function createIdentityDelegated( - address recoveryAddress, address associatedAddress, address[] memory providers, address[] memory resolvers, - uint8 v, bytes32 r, bytes32 s, uint timestamp -) - public returns (uint ein); -``` - -Triggers event: [IdentityCreated](#identitycreated) - -#### addAssociatedAddress - -Adds the `addressToAdd` to the `EIN` of the `approvingAddress`. The `msg.sender` must be either of the `approvingAddress` or the `addressToAdd`, and the signature must be from the other one. - -```solidity -function addAssociatedAddress( - address approvingAddress, address addressToAdd, uint8 v, bytes32 r, bytes32 s, uint timestamp -) - public -``` - -Triggers event: [AssociatedAddressAdded](#associatedaddressadded) - -#### addAssociatedAddressDelegated - -Adds the `addressToAdd` to the `EIN` of the `approvingAddress`. Requires signatures from both the `approvingAddress` and the `addressToAdd`. - -```solidity -function addAssociatedAddressDelegated( - address approvingAddress, address addressToAdd, - uint8[2] memory v, bytes32[2] memory r, bytes32[2] memory s, uint[2] memory timestamp -) - public -``` - -Triggers event: [AssociatedAddressAdded](#associatedaddressadded) - -#### removeAssociatedAddress - -Removes the `msg.sender` as an `Associated Address` from its `EIN`. - -```solidity -function removeAssociatedAddress() public; -``` - -Triggers event: [AssociatedAddressRemoved](#associatedaddressremoved) - - -#### removeAssociatedAddressDelegated - -Removes the `addressToRemove` from its associated `EIN`. Requires a signature from the `addressToRemove`. - -```solidity -function removeAssociatedAddressDelegated(address addressToRemove, uint8 v, bytes32 r, bytes32 s, uint timestamp) - public; -``` - -Triggers event: [AssociatedAddressRemoved](#associatedaddressremoved) - -#### addProviders - -Adds an array of `Providers` to the `Identity` of the `msg.sender`. - -```solidity -function addProviders(address[] memory providers) public; -``` - -Triggers event: [ProviderAdded](#provideradded) - -#### addProvidersFor - -Performs the same logic as `addProviders`, but must be called by a `Provider`. - -```solidity -function addProvidersFor(uint ein, address[] memory providers) public; -``` - -Triggers event: [ProviderAdded](#provideradded) - -#### removeProviders - -Removes an array of `Providers` from the `Identity` of the `msg.sender`. - -```solidity -function removeProviders(address[] memory providers) public; -``` - -Triggers event: [ProviderRemoved](#providerremoved) - - -#### removeProvidersFor - -Performs the same logic as `removeProviders`, but is called by a `Provider`. - -```solidity -function removeProvidersFor(uint ein, address[] memory providers) public; -``` - -Triggers event: [ProviderRemoved](#providerremoved) - - -#### addResolvers - -Adds an array of `Resolvers` to the `EIN` of the `msg.sender`. - -```solidity -function addResolvers(address[] memory resolvers) public; -``` - -Triggers event: [ResolverAdded](#resolveradded) - -#### addResolversFor - -Performs the same logic as `addResolvers`, but must be called by a `Provider`. - -```solidity -function addResolversFor(uint ein, address[] memory resolvers) public; -``` - -Triggers event: [ResolverAdded](#resolveradded) - -#### removeResolvers - -Removes an array of `Resolvers` from the `EIN` of the `msg.sender`. - -```solidity -function removeResolvers(address[] memory resolvers) public; -``` - -Triggers event: [ResolverRemoved](#resolverremoved) - -#### removeResolversFor - -Performs the same logic as `removeResolvers`, but must be called by a `Provider`. - -```solidity -function removeResolversFor(uint ein, address[] memory resolvers) public; -``` - -Triggers event: [ResolverRemoved](#resolverremoved) - -#### triggerRecoveryAddressChange - -Initiates a change in the current `recoveryAddress` for the `EIN` of the `msg.sender`. - -```solidity -function triggerRecoveryAddressChange(address newRecoveryAddress) public; -``` - -Triggers event: [RecoveryAddressChangeTriggered](#recoveryaddresschangetriggered) - -#### triggerRecoveryAddressChangeFor - -Initiates a change in the current `recoveryAddress` for a given `EIN`. - -```solidity -function triggerRecoveryAddressChangeFor(uint ein, address newRecoveryAddress) public; -``` - -Triggers event: [RecoveryAddressChangeTriggered](#recoveryaddresschangetriggered) - -#### triggerRecovery - -Triggers `EIN` recovery from the current `recoveryAddress`, or the old `recoveryAddress` if changed within the last 2 weeks. - -```solidity -function triggerRecovery(uint ein, address newAssociatedAddress, uint8 v, bytes32 r, bytes32 s, uint timestamp) public; -``` - -Triggers event: [RecoveryTriggered](#recoverytriggered) - -#### triggerDestruction - -Triggers destruction of an `EIN`. This renders the `Identity` permanently unusable. - -```solidity -function triggerDestruction(uint ein, address[] memory firstChunk, address[] memory lastChunk, bool clearResolvers) - public; -``` - -Triggers event: [IdentityDestroyed](#identitydestroyed) - -### Events - -#### IdentityCreated - -MUST be triggered when an `Identity` is created. - -```solidity -event IdentityCreated( - address indexed initiator, uint indexed ein, - address recoveryAddress, address associatedAddress, address[] providers, address[] resolvers, bool delegated -); -``` - -#### AssociatedAddressAdded - -MUST be triggered when an address is added to an `Identity`. - -```solidity -event AssociatedAddressAdded( - address indexed initiator, uint indexed ein, address approvingAddress, address addedAddress, bool delegated -); -``` - -#### AssociatedAddressRemoved - -MUST be triggered when an address is removed from an `Identity`. - -```solidity -event AssociatedAddressRemoved(address indexed initiator, uint indexed ein, address removedAddress, bool delegated); -``` - -#### ProviderAdded - -MUST be triggered when a provider is added to an `Identity`. - -```solidity -event ProviderAdded(address indexed initiator, uint indexed ein, address provider, bool delegated); -``` - -#### ProviderRemoved - -MUST be triggered when a provider is removed. - -```solidity -event ProviderRemoved(address indexed initiator, uint indexed ein, address provider, bool delegated); -``` - -#### ResolverAdded - -MUST be triggered when a resolver is added. - -```solidity -event ResolverAdded(address indexed initiator, uint indexed ein, address resolvers, bool delegated); -``` - -#### ResolverRemoved - -MUST be triggered when a resolver is removed. - -```solidity -event ResolverRemoved(address indexed initiator, uint indexed ein, address resolvers, bool delegated); -``` - -#### RecoveryAddressChangeTriggered - -MUST be triggered when a recovery address change is triggered. - -```solidity -event RecoveryAddressChangeTriggered( - address indexed initiator, uint indexed ein, - address oldRecoveryAddress, address newRecoveryAddress, bool delegated -); -``` - -#### RecoveryTriggered - -MUST be triggered when recovery is triggered. - -```solidity -event RecoveryTriggered( - address indexed initiator, uint indexed ein, address[] oldAssociatedAddresses, address newAssociatedAddress -); -``` - -#### IdentityDestroyed - -MUST be triggered when an `Identity` is destroyed. - -```solidity -event IdentityDestroyed(address indexed initiator, uint indexed ein, address recoveryAddress, bool resolversReset); -``` - -### Solidity Interface -```solidity -interface IdentityRegistryInterface { - function isSigned(address _address, bytes32 messageHash, uint8 v, bytes32 r, bytes32 s) - external pure returns (bool); - - // Identity View Functions ///////////////////////////////////////////////////////////////////////////////////////// - function identityExists(uint ein) external view returns (bool); - function hasIdentity(address _address) external view returns (bool); - function getEIN(address _address) external view returns (uint ein); - function isAssociatedAddressFor(uint ein, address _address) external view returns (bool); - function isProviderFor(uint ein, address provider) external view returns (bool); - function isResolverFor(uint ein, address resolver) external view returns (bool); - function getIdentity(uint ein) external view returns ( - address recoveryAddress, - address[] memory associatedAddresses, address[] memory providers, address[] memory resolvers - ); - - // Identity Management Functions /////////////////////////////////////////////////////////////////////////////////// - function createIdentity(address recoveryAddress, address[] calldata providers, address[] calldata resolvers) - external returns (uint ein); - function createIdentityDelegated( - address recoveryAddress, address associatedAddress, address[] calldata providers, address[] calldata resolvers, - uint8 v, bytes32 r, bytes32 s, uint timestamp - ) external returns (uint ein); - function addAssociatedAddress( - address approvingAddress, address addressToAdd, uint8 v, bytes32 r, bytes32 s, uint timestamp - ) external; - function addAssociatedAddressDelegated( - address approvingAddress, address addressToAdd, - uint8[2] calldata v, bytes32[2] calldata r, bytes32[2] calldata s, uint[2] calldata timestamp - ) external; - function removeAssociatedAddress() external; - function removeAssociatedAddressDelegated(address addressToRemove, uint8 v, bytes32 r, bytes32 s, uint timestamp) - external; - function addProviders(address[] calldata providers) external; - function addProvidersFor(uint ein, address[] calldata providers) external; - function removeProviders(address[] calldata providers) external; - function removeProvidersFor(uint ein, address[] calldata providers) external; - function addResolvers(address[] calldata resolvers) external; - function addResolversFor(uint ein, address[] calldata resolvers) external; - function removeResolvers(address[] calldata resolvers) external; - function removeResolversFor(uint ein, address[] calldata resolvers) external; - - // Recovery Management Functions /////////////////////////////////////////////////////////////////////////////////// - function triggerRecoveryAddressChange(address newRecoveryAddress) external; - function triggerRecoveryAddressChangeFor(uint ein, address newRecoveryAddress) external; - function triggerRecovery(uint ein, address newAssociatedAddress, uint8 v, bytes32 r, bytes32 s, uint timestamp) - external; - function triggerDestruction( - uint ein, address[] calldata firstChunk, address[] calldata lastChunk, bool resetResolvers - ) external; - - // Events ////////////////////////////////////////////////////////////////////////////////////////////////////////// - event IdentityCreated( - address indexed initiator, uint indexed ein, - address recoveryAddress, address associatedAddress, address[] providers, address[] resolvers, bool delegated - ); - event AssociatedAddressAdded( - address indexed initiator, uint indexed ein, address approvingAddress, address addedAddress - ); - event AssociatedAddressRemoved(address indexed initiator, uint indexed ein, address removedAddress); - event ProviderAdded(address indexed initiator, uint indexed ein, address provider, bool delegated); - event ProviderRemoved(address indexed initiator, uint indexed ein, address provider, bool delegated); - event ResolverAdded(address indexed initiator, uint indexed ein, address resolvers); - event ResolverRemoved(address indexed initiator, uint indexed ein, address resolvers); - event RecoveryAddressChangeTriggered( - address indexed initiator, uint indexed ein, address oldRecoveryAddress, address newRecoveryAddress - ); - event RecoveryTriggered( - address indexed initiator, uint indexed ein, address[] oldAssociatedAddresses, address newAssociatedAddress - ); - event IdentityDestroyed(address indexed initiator, uint indexed ein, address recoveryAddress, bool resolversReset); -} -``` - -## Backwards Compatibility -`Identities` established under this standard consist of existing Ethereum addresses; accordingly, there are no backwards compatibility issues. Deployed, non-upgradeable smart contracts that wish to become `Resolvers` for `Identities` will need to write wrapper contracts that resolve addresses to `EIN`-denominated `Identities`. - -## Additional References -- [ERC-1484 Reference Implementation](https://github.com/NoahZinsmeister/ERC-1484) -- [ERC-191 Signatures](./eip-191.md) -- [ERC-725 Identities](./eip-725.md) -- [ERC-1056 Identities](./eip-1056.md) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1484.md diff --git a/EIPS/eip-1491.md b/EIPS/eip-1491.md index 9007dcb0bee6a2..7e29d42096644a 100644 --- a/EIPS/eip-1491.md +++ b/EIPS/eip-1491.md @@ -1,528 +1 @@ ---- -eip: 1491 -title: Human Cost Accounting Standard (Like Gas but for humans) -author: Iamnot Chris (@cohabo) -discussions-to: https://github.com/freeworkculture/kazini/issues/11 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-10-12 ---- - -## Simple Summary -A standard interface for Human Capital Accounting tokens. - -## Abstract -The following standard allows for the implementation of a standard API for HUCAP tokens within smart contracts. This standard provides basic functionality to discover, track and transfer the motivational hierarchy of human resources. While blockchain architecture has succeeded in the financialisation of integrity by way of transparency; correspondingly real world outcomes will be proportional to the degree of individualisation of capital by way of knowledge. - -## Motivation -The Ethereum protocol architecture has a deterministic world-view bounded to the random reality of the human domain that supplies the intentions and logic. The yellow paper formally defines the EVM as a state machine with only deterministic parameters and state transition operators. Oracle requests to another on-chain contract, and/or off-chain HTTP lookups still make for multiple deterministic transactions. - -A standard interface that allows the appraisal of individual capabilities concurrently with output and the overall knowledge-base will reduce market search costs and increase the autonomous insertion of mindful innovation into the blockchain ecosystem. We provide for simple smart contracts to define and track an arbitrarily large number of HUCAP assets. Additional applications are discussed below. - -The Belief-Desire-Intention model is a plan-theoretic framework for establishing means-end coherence in agent based modelling system. -The blockchain's cryptographic security architecture reliably scales to a blockchain based PKI web-of-trust hierarchies. -ERC-20 token standard allows any tokens on Ethereum to be re-used by other applications: from wallets to decentralized exchanges. -ERC-721 token standard allows wallet/broker/auction applications to work with any NFT on Ethereum. -ERC-1155 Crypto Item standard allows a smart contract interface where one can represent any number of ERC-20 and ERC-721 assets in a single contract. - -This standard is inspired by the belief–desire–intention (BDI) model of human practical reasoning developed by Michael Bratman as a way of explaining future-directed intention. A BDI agent is a particular type of bounded rational software agent, imbued with particular mental attitudes, viz: Beliefs, Desires and Intentions (BDI). The model identifies commitment as the distinguishing factor between desire and intention, and a noteworthy property that leads to (1) temporal persistence in plans and in the sense of explicit reference to time, (2) further plans being made on the basis of those to which it is already committed, (3) hierarchical nature of plans, since the overarching plan remains in effect while subsidiary plans are being executed. - -The BDI software model is an attempt to solve a problem of plans and planning choice and the execution thereof. The complement of which tenders a sufficient metric for indicating means-end coherence and ascribing cost baselines to such outcomes. - -## Specification - -#### Main Interface -```solidity -pragma solidity ^0.4.25; -pragma experimental ABIEncoderV2; - -/** - @title ERC-**** Human Capital Accounting Standard - @dev See https://github.com/freeworkculture/kazini/issues/11 - Note: the ERC-165 identifier for this interface is 0xf23a6e61. - */ - -interface IERC_HUCAP { - - /** - @notice Compute the index value of an Agents BDI in the ecosystem. - @param _address Set the stance of an agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function updateIndex() internal returns (bool); - - /** - @notice Get the active/inactive and states of an Agent in the ecosystem. - @param _address Set the stance of an agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function iam() view public returns (bool iam_, IERC_HUCAP_TYPES.IS state_); - - /** - @notice Fetch the bdi index value of an Agent in the ecosystem. - @param _address Set the stance of an agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function index() view public returns (uint8 index_); - - /** - @notice Count of Public Keys in key ring of an Agent in the ecosystem. - @param _address Set the stance of an agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function ringLength() view public returns (uint ringlength_); - - /** - @notice Get the PGP Public Key Id of an Agent in the ecosystem. - @param "" Set the stance of an agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function keyId() view public returns (bytes32 KEYID_); - - /** - @notice Get the merit data of an Agent in the ecosystem. - @param "" Set the stance of an agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function merits() view public returns ( - uint experience_, - bytes32 reputation_, - bytes32 talent_, - uint8 index_, - bytes32 hash_); - - /** - @notice Get the accreditation of an Agent in the ecosystem. - @param "" Set the stance of an agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function kbase() view public returns (IERC_HUCAP_TYPES.KBase kbase_); - - /** - @notice Get the desire of an Agent in the ecosystem. - @param _desire Pro-attitude - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function desire(bytes1 _desire) view external returns (bytes32); - - /** - @notice Get the intention of an Agent in the ecosystem. - @param _intention Conduct-controlling pro-attitude - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function intention(bool _intention) view external returns (bytes32); - - /** - @notice Cycle the intention of an Agent in the ecosystem. - @param _intention Conduct-controlling pro-attitude - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function flipIntention() external returns (bool); - - - /** - @notice Get the user data of an Agent in the ecosystem. - @param "" Conduct-controlling pro-attitude - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function getDoer() view external returns ( - bytes32 fPrint, - bool iam_, - bytes32 email, - bytes32 fName, - bytes32 lName, - uint age, - bytes32 data_); - - /** - @notice Get the belief data of an Agent in the ecosystem. - @param _kbase Source address - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function getBelief(IERC_HUCAP_TYPES.KBase _kbase) view external returns ( - bytes32 country_, - bytes32 cAuthority_, - bytes32 score_); - - /** - @notice Get the desire data of an Agent in the ecosystem. - @param _desire Pro-attitides - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function getDesire(bytes1 _desire) view external returns (bytes32,bool); - - /** - @notice Get the intention of an Agent in the ecosystem. - @param _intention Conduct-controlling pro-attitude - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function getIntention(bool _intention) view external returns (IERC_HUCAP_TYPES.IS,bytes32,uint256); - - /** - @notice Sign the Public Key of an Agent in the ecosystem. - @param _address Address of key to sign, must belong to an Agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function sign(address _address) public onlyOwner returns (uint, bool signed); - - /** - @notice Sign the Public Key of an Agent in the ecosystem. - @param "" internal helper function to add key in keyring - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function sign() external onlyDoer returns (uint, bool signed); - - /** - @notice Revoke the Public Key of an Agent in the ecosystem. - @param _address Address of key to revoke, must belong to an Agent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function revoke(address _address) external onlyDoer returns (uint, bool revoked); - - /** - @notice Revoke the Public Key of an Agent in the ecosystem. - @param "" internal helper function to remove key from keyring - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function revoke() external onlyDoer returns (uint, bool revoked); - - /** - @notice Set the trust level for a Public Key of an Agent in the ecosystem. - @param _level Degree of trust - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function trust(Trust _level) returns (bool); - - /** - @notice Increment the number of keys in the keyring of an Agent in the ecosystem. - @param _keyd Target key - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function incSigns(bytes32 _keyd) external ProxyKey returns (uint); - - /** - @notice Decrement the number of keys in the keyring of an Agent in the ecosystem. - @param _keyd Target key - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function decSigns(bytes32 _keyd) external ProxyKey returns (uint); - - /** - @notice Set the knowledge credentials of an Agent in the ecosystem. - @param _kbase Level of accreditation - @param _country Source country - @param _cAuthority Accreditation authority - @param _score Accreditation - @param _year Year of Accreditation - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function setbdi( - KBase _kbase, - bytes32 _country, - bytes32 _cAuthority, - bytes32 _score, - uint _year - ) external ProxyBDI returns (bool qualification_); - - /** - @notice Set the SNA metrics of an Agent in the ecosystem - @param _refMSD Minimum shortest distance - @param _refRank Rank of target key - @param _refSigned No of keys signed I have signed - @param _refSigs No. of keys that have signed my key - @param _refTrust Degree of tructThrows on any error rather than return a false flag to minimize user errors - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function setbdi( - uint _refMSD, - uint _refRank, - uint _refSigned, - uint _refSigs, - bytes32 _refTrust - ) external ProxyBDI returns (bool reputation_); - - /** - @notice Set the talents of an Agent in the ecosystem - @param _talent Agent's talent - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function setbdi(bytes32 _talent) external ProxyBDI returns (bool talent_); - - /** - @notice Set the desires of an Agent in the ecosystem - @param _desire Pro-attitude - @param _goal A goal is an instatiated pro-attitude - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function setbdi(bytes1 _desire, Desire _goal) public onlyDoer returns (bool); - - /** - @notice Set the intention of an Agent in the ecosystem - @param _service Conducting-controlling pro-attitude - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - */ - function setbdi(Intention _service) public onlyDoer returns (bool); - - /** - @notice Set the targeted intention of an Agent in the ecosystem. - @param _intention Conduct-controlling pro-attitude - @param _state Agent stance - @dev For the purpose of - Throws on any error rather than return a false flag to minimize user errors - - */ - function intention(bool _intention, IERC_HUCAP_TYPES.IS _state) external returns (IERC_HUCAP_TYPES.IS); - -/* End of interface IERC_HUCAP */ -} - - -``` -#### User Defined Types Extension Interface - -```solidity - -interface IERC_HUCAP_TYPES { - -/* Enums*/ - - // Weights 1, 2, 4, 8, 16, 32, 64, 128 256 - enum KBase {PRIMARY,SECONDARY,TERTIARY,CERTIFICATION,DIPLOMA,LICENSE,BACHELOR,MASTER,DOCTORATE} - - - enum IS { CLOSED, CREATOR, CURATOR, ACTIVE, INACTIVE, RESERVED, PROVER } - -/* Structus */ - - struct Clearance { - bytes32 Zero; - bytes32 Unknown; - bytes32 Generic; - bytes32 Poor; - bytes32 Casual; - bytes32 Partial; - bytes32 Complete; - bytes32 Ultimate; - } -/* End of interface IERC_HUCAP_TYPES */ -} - -``` -#### Web-of-trust Extension Interface - -```solidity -pragma solidity ^0.4.25; -pragma experimental ABIEncoderV2; - -interface IERC_HUCAP_KEYSIGNING_EXTENSION { - - bytes32 constant public _InterfaceId_ERC165_ = "CREATOR 0.0118 XOR OF ALL FUNCTIONS IN THE INTERFACE"; // Complies to ERC165 - -// KEY MASKING TABLE -// bytes32 constant public MASK = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; -// bytes32 constant public KEYID = 0xffffffffffffffffffffffffffffffffff90EBAC34FC40EAC30FC9CB464A2E56; // EXAMPLE PGP PUBLIC KEY ID -// bytes32 constant public KEY_CERTIFICATION = 0x01ffffffffffffff << 192; // “C” Key Certification -// bytes32 constant public SIGN_DATA = 0x02ffffffffffffff << 192; // “S” Sign Data -// bytes32 constant public ENCRYPT_COMMUNICATIONS = 0x04ffffffffffffff << 192; // “E” Encrypt Communications -// Clearance constant public Trust = 0x03ff << 192; // Trust: Unknown - // BYTES32 Value with - // Public Key Id, masking - // Key Certification masking - // Split Key masking - // Generic masking - // Ordinary masking - // Trust.Unknown masking - // bytes32 constant public DOER = 0x11ff10ff100f03ffff00ffffffffffffffff90EBAC34FC40EAC30FC9CB464A2E56; - - bytes32 constant public KEY_CERTIFICATION = 0x01ffffffffffffff << 192; // “C” Key Certification - bytes32 constant public SIGN_DATA = 0x02ffffffffffffff << 192; // “S” Sign Data - bytes32 constant public ENCRYPT_COMMUNICATIONS = 0x04ffffffffffffff << 192; // “E” Encrypt Communications - bytes32 constant public ENCRYPT_STORAGE = 0x08ffffffffffffff << 192; // “E” Encrypt Storage - bytes32 constant public SPLIT_KEY = 0x10ffffffffffffff << 192; // Split key - bytes32 constant public AUTHENTICATION = 0x20ffffffffffffff << 192; // “A” Authentication - bytes32 constant public MULTI_SIGNATURE = 0x80ffffffffffffff << 192; // Held by more than one person - bytes32 constant public TRUST_AMOUNT = 0xffffffffffff00ff << 192; - bytes32 constant public BINARY_DOCUMENT = 0xffff00ffffffffff << 192; // 0x00: Signature of a binary document. - bytes32 constant public CANONICAL_DOCUMENT = 0xffff01ffffffffff << 192; // 0x01: Signature of a canonical text document. - bytes32 constant public STANDALONE_SIGNATURE = 0xffff02ffffffffff << 192; // 0x02: Standalone signature. - bytes32 constant public GENERIC = 0xffff10ffffffffff << 192; // 0x10: Generic certification of a User ID and Public-Key packet. - bytes32 constant public PERSONA = 0xffff11ffffffffff << 192; // 0x11: Persona certification of a User ID and Public-Key packet. - bytes32 constant public CASUAL = 0xffff12ffffffffff << 192; // 0x12: Casual certification of a User ID and Public-Key packet. - bytes32 constant public POSITIVE = 0xffff13ffffffffff << 192; // 0x13: Positive certification of a User ID and Public-Key packet. - bytes32 constant public SUBKEY_BINDING = 0xffff18ffffffffff << 192; // 0x18: Subkey Binding Signature - bytes32 constant public PRIMARY_KEY_BINDING = 0xffff19ffffffffff << 192; // 0x19: Primary Key Binding Signature - bytes32 constant public DIRECTLY_ON_KEY = 0xffff1Fffffffffff << 192; // 0x1F: Signature directly on a key - bytes32 constant public KEY_REVOCATION = 0xffff20ffffffffff << 192; // 0x20: Key revocation signature - bytes32 constant public SUBKEY_REVOCATION = 0xffff28ffffffffff << 192; // 0x28: Subkey revocation signature - bytes32 constant public CERTIFICATION_REVOCATION = 0xffff30ffffffffff << 192; // 0x30: Certification revocation signature - bytes32 constant public TIMESTAMP = 0xffff40ffffffffff << 192; // 0x40: Timestamp signature. - bytes32 constant public THIRD_PARTY_CONFIRMATION = 0xffff50ffffffffff << 192; // 0x50: Third-Party Confirmation signature. - bytes32 constant public ORDINARY = 0xffffffff100fffff << 192; - bytes32 constant public INTRODUCER = 0xffffffff010fffff << 192; - bytes32 constant public ISSUER = 0xffffffff001fffff << 192; - -// EDGES MASKING TABLE - Clearance internal TRUST = Clearance({ - Zero: 0x01ff << 192, - Unknown: 0x03ff << 192, - Generic: 0x07ff << 192, - Poor: 0xF0ff << 192, - Casual: 0xF1ff << 192, - Partial: 0xF3ff << 192, - Complete: 0xF7ff << 192, - Ultimate: 0xFFff << 192 - }); - - /** - /// @notice Cycle through state transition of an Agent in the ecosystem. - /// @param _address toggle on/off a doer agent - // @dev `anybody` can retrieve the talent data in the contract - */ - function flipTo(address _address) external onlyOwner returns (IS); - - /** - /// @notice Turn Agent in the ecosystem to on/off. - /// @param _address toggle on/off a doer agent - // @dev `anybody` can retrieve the talent data in the contract - */ - function toggle(address _address) external onlyOwner returns (bool); - - /** - /// @notice Set the trust level of an Agent in the ecosystem. - /// @param _level toggle on/off a doer agent - // @dev `anybody` can retrieve the talent data in the contract - */ - function trust(Trust _level) returns (bytes32 Trust); - - event LogCall(address indexed from, address indexed to, address indexed origin, bytes _data); - -/* End of interface IERC_HUCAP_KEYSIGNING_EXTENSION */ -} - -``` -#### Human Capital Accounting Extension Interface - -```solidity -pragma solidity ^0.4.25; -pragma experimental ABIEncoderV2; - -interface IERC_HUCAP_TRACKUSERS_EXTENSION { - - /// @notice Instantiate an Agent in the ecosystem with default data. - /// @param _address initialise a doer agent - // @dev `anybody` can retrieve the talent data in the contract - function initAgent(Doers _address) external onlyControlled returns (bool); - - /// @notice Get the data by uuid of an Agent in the ecosystem. - /// @param _uuid Get the address of a unique uid - // @dev `anybody` can retrieve the talent data in the contract - function getAgent(bytes32 _uuid) view external returns (address); - - /// @notice Get the data of all Talents in the ecosystem. - /// @param _address Query if address belongs to an agent - // @dev `anybody` can retrieve the talent data in the contract - function iam(address _address) view public returns (bool); - - /// @notice Get the data of all Talents in the ecosystem. - /// @param _address Query if address belongs to a doer - // @dev `anybody` can retrieve the talent data in the contract - function isDoer(address _address) view public returns (IS); - - /// @notice Get the number of doers that can be spawned by a Creators. - /// The query condition of the contract - // @dev `anybody` can retrieve the count data in the contract - function getAgent(address _address) - view public returns (bytes32 keyid_, IS state_, bool active_, uint myDoers_); - - /// @notice Get the data of all Talents in the ecosystem. - /// @param _talent The talent whose frequency is being queried - // @dev `anybody` can retrieve the talent data in the contract - function getTalents(bytes32 _talent) - view external returns (uint talentK_, uint talentI_, uint talentR_, uint talentF_); - - /// @notice Increment a kind of talent in the ecosystem. - /// @param The talent whose frequency is being queried - // @dev `anybody` can retrieve the talent data in the contract - function incTalent() payable public onlyDoer returns (bool); - - /// @notice Decrement a kind of talent in the ecosystem.. - /// @param The talent whose frequency is being queried - // @dev `anybody` can retrieve the talent data in the contract - function decTalent() payable public onlyDoer returns (bool); - - /// @notice Set the Public-Key Id of an Agent in the ecosystem. - /// @param _address Set the Public-key Id of an agent - // @dev `anybody` can retrieve the talent data in the contract - function setAgent(address _address, bytes32 _keyId) external onlyControlled returns (bytes32); - - /// @notice Transition the states of an Agent in the ecosystem. - /// @param _address Set the stance of an agent - // @dev `anybody` can retrieve the talent data in the contract - function setAgent(address _address, IS _state) external onlyControlled returns (IS); - - /// @notice Set the active status of an Agent in the ecosystem. - /// @param _address Toggle the true/false status of an agent - // @dev `anybody` can retrieve the talent data in the contract - function setAgent(address _address, bool _active) external onlyControlled returns (bool); - - /// @notice Set the data of all Intentions of Agents in the ecosystem. - /// @param _serviceId Track number of offers available - // @dev `anybody` can retrieve the talent data in the contract - function setAllPromises(bytes32 _serviceId) external onlyControlled; - -/* End of interface IERC_HUCAP_TRACKUSERS_EXTENSION */ -} - - -``` -## Rationale -[WIP] - -## Backwards Compatibility -[WIP] - -## Test Cases -[WIP] - -## Implementation -[WIP] - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1491.md diff --git a/EIPS/eip-1504.md b/EIPS/eip-1504.md index afed611194173d..d67f263a7dcdd2 100644 --- a/EIPS/eip-1504.md +++ b/EIPS/eip-1504.md @@ -1,351 +1 @@ ---- -eip: 1504 -title: Upgradable Smart Contract -author: Kaidong Wu , Chuqiao Ren , Ruthia He , Yun Ma , Xuanzhe Liu -discussions-to: https://github.com/ethereum/EIPs/issues/1503 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-10-17 ---- - -## Simple Summary - -A standard interface/guideline that makes a smart contract upgradable. - -## Abstract - -Ethereum smart contracts have suffered a number of security issues in the past few years. The cost of fixing such a bug in smart contract is significant; for example, the consequences of The DAO attack in June 2016 caused tremendous financial loss and the hard fork of Ethereum blockchain. - -The following standard makes it possible to upgrade a standard API within smart contracts. This standard provides basic functionalities to upgrade the operations of the contract without data migration. To ensure the decentralization/community interests, it also contains a voting mechanism to control the upgrading process. - -## Motivation - -Smart contract is immutable after deployment. If any security risk is identified or program bug is detected, developers always have to destruct the old contract, deploy a new one and potentially migrate the data (hard fork) to the new contract. In some cases, deploying a smart contract with bugs and potential security vulnerabilities can cause a significant amount of financial loss. - -We propose this upgradable contract to fix the current situation. With the upgradable contract, developers can deploy a new version of smart contract after previous deployment and retain the data at the same time. - -For example, after an ERC20-compliant token contract is deployed, the users exploit a vulnerability in the source code. Without the support of upgradable contract, developers have to fix this issue by deploy a new, secured contract otherwise the attackers would take advantage of the security hole, which may cause a tremendous financial loss. A challenge is how to migrate data from the old contract to a new one. With the upgradable contract below, this will become relatively easy as developers only have to upgrade the Handler contract to fix bugs while the Data contract will remain the same. - -## Specification - -The upgradable contract consists of three parts: - -- **Handler contract** (implements **Handler interface**) defines operations and provides services. This contract can be upgraded; -- **Data contract** keeps the resources (data) and is controlled by the Handler contract; -- **Upgrader contract (optional)** deals with the voting mechanism and upgrades the Handler contract. The voters are pre-defined by the contract owner. - -> The following codes are exact copies of the [ERC-1504 Upgradable Smart Contract.](https://gist.github.com/swordghost/77c96a972106af6ec6ccea9c2d66e768) - -### Handler contract and Handler interface - -Functions of the Handler contract vary with requirements, so developers would better design interfaces for Handler contracts to limit them and make sure external applications are always supported. - -Below is the specification of Handler interface. In the Handler interface we define the following actions: - -- Initialize the Data contract; -- Register the Upgrader contract address; -- Destruct the Handler contract after upgrading is done; -- Verify the current Handler is the working one → it should always return true. - -Developers have to define their business-related functions as well. - - -```solidity -/// Handler interface. -/// Handler defines business related functions. -/// Use the interface to ensure that your external services are always supported. -/// Because of function live(), we design IHandler as an abstract contract rather than a true interface. -contract IHandler { - - /// Initialize the data contarct. - /// @param _str value of exmStr of Data contract. - /// @param _int value of exmInt of Data contract. - /// @param _array value of exmArray of Data contract. - function initialize (string _str, uint256 _int, uint16 [] _array) public; - - /// Register Upgrader contract address. - /// @param _upgraderAddr address of the Upgrader contract. - function registerUpgrader (address _upgraderAddr) external; - - /// Upgrader contract calls this to check if it is registered. - /// @return if the Upgrader contract is registered. - function isUpgraderRegistered () external view returns(bool); - - /// Handler has been upgraded so the original one has to self-destruct. - function done() external; - - /// Check if the Handler contract is a working Handler contract. - /// It is used to prove the contract is a Handler contract. - /// @return always true. - function live() external pure returns(bool) { - return true; - } - - /** Functions - define functions here */ - - /** Events - add events here */ -} -``` - - -The process of deploying a Handler contract: - -1. Deploy Data contract; -2. Deploy a Handler contract at a given address specified in the Data contract; -3. Register the Handler contract address by calling setHandler() in the Data contract, or use an Upgrader contract to switch the Handler contract, which requires that Data contract is initialized; -4. Initialize Data contract if haven’t done it already. - -### Data Contract - -Below is the specification of Data contract. There are three parts in the Data contract: - -- **Administrator Data**: owner’s address, Handler contract’s address and a boolean indicating whether the contract is initialized or not; -- **Upgrader Data**: Upgrader contract’s address, upgrade proposal’s submission timestamp and proposal’s time period; -- **Resource Data**: all other resources that the contract needs to keep and manage. - - -```solidity -/// Data Contract -contract DataContract { - - /** Management data */ - /// Owner and Handler contract - address private owner; - address private handlerAddr; - - /// Ready? - bool private valid; - - /** Upgrader data */ - address private upgraderAddr; - uint256 private proposalBlockNumber; - uint256 private proposalPeriod; - /// Upgrading status of the Handler contract - enum UpgradingStatus { - /// Can be upgraded - Done, - /// In upgrading - InProgress, - /// Another proposal is in progress - Blocked, - /// Expired - Expired, - /// Original Handler contract error - Error - } - - /** Data resources - define variables here */ - - /** Modifiers */ - - /// Check if msg.sender is the Handler contract. It is used for setters. - /// If fail, throw PermissionException. - modifier onlyHandler; - - /// Check if msg.sender is not permitted to call getters. It is used for getters (if necessary). - /// If fail, throw GetterPermissionException. - modifier allowedAddress; - - /// Check if the contract is working. - /// It is used for all functions providing services after initialization. - /// If fail, throw UninitializationException. - modifier ready; - - /** Management functions */ - - /// Initializer. Just the Handler contract can call it. - /// @param _str default value of this.exmStr. - /// @param _int default value of this.exmInt. - /// @param _array default value of this.exmArray. - /// exception PermissionException msg.sender is not the Handler contract. - /// exception ReInitializationException contract has been initialized. - /// @return if the initialization succeeds. - function initialize (string _str, uint256 _int, uint16 [] _array) external onlyHandler returns(bool); - - /// Set Handler contract for the contract. Owner must set one to initialize the Data contract. - /// Handler can be set by owner or Upgrader contract. - /// @param _handlerAddr address of a deployed Handler contract. - /// @param _originalHandlerAddr address of the original Handler contract, only used when an Upgrader contract want to set the Handler contract. - /// exception PermissionException msg.sender is not the owner nor a registered Upgrader contract. - /// exception UpgraderException Upgrader contract does not provide a right address of the original Handler contract. - /// @return if Handler contract is successfully set. - function setHandler (address _handlerAddr, address _originalHandlerAddr) external returns(bool); - - /** Upgrader contract functions */ - - /// Register an Upgrader contract in the contract. - /// If a proposal has not been accepted until proposalBlockNumber + proposalPeriod, it can be replaced by a new one. - /// @param _upgraderAddr address of a deployed Upgrader contract. - /// exception PermissionException msg.sender is not the owner. - /// exception UpgraderConflictException Another Upgrader contract is working. - /// @return if Upgrader contract is successfully registered. - function startUpgrading (address _upgraderAddr) public returns(bool); - - /// Getter of proposalPeriod. - /// exception UninitializationException uninitialized contract. - /// exception GetterPermissionException msg.sender is not permitted to call the getter. - /// @return this.proposalPeriod. - function getProposalPeriod () public view isReady allowedAddress returns(uint256); - - /// Setter of proposalPeriod. - /// @param _proposalPeriod new value of this.proposalPeriod. - /// exception UninitializationException uninitialized contract. - /// exception PermissionException msg.sender is not the owner. - /// @return if this.proposalPeriod is successfully set. - function setProposalPeriod (uint256 _proposalPeriod) public isReady returns(bool); - - /// Return upgrading status for Upgrader contracts. - /// @param _originalHandlerAddr address of the original Handler contract. - /// exception UninitializationException uninitialized contract. - /// @return Handler contract's upgrading status. - function canBeUpgraded (address _originalHandlerAddr) external view isReady returns(UpgradingStatus); - - /// Check if the contract has been initialized. - /// @return if the contract has been initialized. - function live () external view returns(bool); - - /** Getters and setters of data resources: define functions here */ -} -``` - - -### Upgrader Contract (Optional) - -Handler contract can be upgraded by calling setHandler() of Data contract. If the owner wants to collect ideas from users, an Upgrader contract will help him/her manage voting and upgrading. - -Below is the specification of Upgrader contract: - -- The Upgrader contract has the ability to take votes from the registered voters. - - The contract owner is able to add voters any time before the proposal expires; - - Voter can check the current status of the proposal (succeed or expired). -- Developers are able to delete this Upgrader contract by calling done() any time after deployment. - -The Upgrader contract works as follows: - -1. Verify the Data contract, its corresponding Handler contract and the new Handler contract have all been deployed; -2. Deploy an Upgrader contract using Data contract address, previous Handler contract address and new Handler contract address; -3. Register upgrader address in the new Handler contract first, then the original handler and finally the Data contract; -4. Call startProposal() to start the voting process; -5. Call getResolution() before the expiration; -6. Upgrading succeed or proposal is expired. - -Note: - -- Function done() can be called at any time to let upgrader destruct itself. -- Function status() can be called at any time to show caller status of the upgrader. - - -```solidity -/// Handler upgrader -contract Upgrader { - // Data contract - DataContract public data; - // Original Handler contract - IHandler public originalHandler; - // New Handler contract - address public newHandlerAddr; - - /** Marker */ - enum UpgraderStatus { - Preparing, - Voting, - Success, - Expired, - End - } - UpgraderStatus public status; - - /// Check if the proposal is expired. - /// If so, contract would be marked as expired. - /// exception PreparingUpgraderException proposal has not been started. - /// exception ReupgradingException upgrading has been done. - /// exception ExpirationException proposal is expired. - modifier notExpired { - require(status != UpgraderStatus.Preparing, "Invalid proposal!"); - require(status != UpgraderStatus.Success, "Upgrading has been done!"); - require(status != UpgraderStatus.Expired, "Proposal is expired!"); - if (data.canBeUpgraded(address(originalHandler)) != DataContract.UpgradingStatus.InProgress) { - status = UpgraderStatus.Expired; - require(false, "Proposal is expired!"); - } - _; - } - - /// Start voting. - /// Upgrader must do upgrading check, namely checking if Data contract and 2 Handler contracts are ok. - /// exception RestartingException proposal has been already started. - /// exception PermissionException msg.sender is not the owner. - /// exception UpgraderConflictException another upgrader is working. - /// exception NoPreparationException original or new Handler contract is not prepared. - function startProposal () external; - - /// Anyone can try to get resolution. - /// If voters get consensus, upgrade the Handler contract. - /// If expired, self-destruct. - /// Otherwise, do nothing. - /// exception PreparingUpgraderException proposal has not been started. - /// exception ExpirationException proposal is expired. - /// @return status of proposal. - function getResolution() external returns(UpgraderStatus); - - /// Destruct itself. - /// exception PermissionException msg.sender is not the owner. - function done() external; - - /** Other voting mechanism related variables and functions */ -} -``` - - -### Caveats - -Since the Upgrader contract in [ERC-1504](./eip-1504.md) has a simple voting mechanism, it is prone to all the limitations that the voting contract is facing: - -- The administrator can only be the owner of data and Handler contracts. Furthermore, only the administrator has the power to add voters and start a proposal. -- It requires voters to be constantly active, informative and attentive to make a upgrader succeed. -- The voting will only be valid in a given time period. If in a given time period the contract cannot collect enough “yes” to proceed, the proposal will be marked expired. - -## Rationale - -### Data Contract and Handler Contract - -A smart contract is actually a kind of software, which provides some kind of services. From the perspective of software engineering, a service consists of **resources** that abstract the data and **operations** that abstract the process logic on the data. The requirement of upgrading is mostly on the logic part. Therefore, in order to make a smart contract upgradable, we divide it into two parts: - -1. Data contract keeps the resources; -2. Handler contract contains operations. - -The Handler contract can be upgraded in the future while the Data contract is permanent. Handler contract can manipulate the variables in Data contract through the getter and setter functions provided by Data contract. - -### Upgrader Contract and Voting Mechanism - -In order to prevent centralization and protect the interests of the community and stakeholders, we also design a voting mechanism in the Upgrader contract. Upgrader contract contains addresses of Data contract and two Handler contracts, and collects votes from pre-defined voters to upgrade the Handler contract when the pre-set condition is fulfilled. - -For simplicity, the upgradable contract comes with a very minimal version of the voting mechanism. If the contract owner wants to implement a more complex voting mechanism, he/she can modify the existing voting mechanism to incorporate upgradability. The expiration mechanism (see modifier notExpried in Upgrader contract and related functions in Data contract) and upgrading check (see function startProposal() in Upgrader contract) to the contract are mandatory. - -### Gas and Complexity (regarding the enumeration extension) - -Using an upgrader will cost some gas. If the Handler contract is upgraded by the owner, it just costs gas that a contract call will cost, which is usually significantly lower than creating and deploying a new contract. - -Although upgrading contract may take some efforts and gas, it is a much less painful than deprecating the insecure contract/creating a new contract or hard fork (e.g. DAO attack). Contract creation requires a significant amount of effort and gas. One of the advantages of upgradable contracts is that the contract owners don’t have to create new contracts; instead, they only need to upgrade parts of contract that cause issues, which is less expensive compared to data loss and blockchain inconsistency. In other words, upgradable contracts make Data contract more scalable and flexible. - -### Community Consensus - -Thank you to those who helped on review and revise the proposal: - -- [@lsankar4033](https://github.com/lsankar4033) from MIT -- more - -The proposal is initiated and developed by the team Renaissance and the Research Group of Blockchain System @ Center for Operating System at Peking University. - -We have been very inclusive in this process and invite anyone with questions or contributions into our discussion. However, this standard is written only to support the identified use cases which are listed herein. - -## Implementations - -1. [Renaissance](https://www.renaissance.app) - a protocol that connect creators and fans financially -2. [ERC-1504](./eip-1504.md) - a reference implementation - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1504.md diff --git a/EIPS/eip-1523.md b/EIPS/eip-1523.md index 2469e83d69d4a9..4393bb04cb2202 100644 --- a/EIPS/eip-1523.md +++ b/EIPS/eip-1523.md @@ -1,125 +1 @@ ---- -eip: 1523 -title: Standard for Insurance Policies as ERC-721 Non Fungible Tokens -author: Christoph Mussenbrock (@christoph2806) -discussions-to: https://github.com/ethereum/EIPs/issues/1523 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-10-10 -requires: 721 ---- - -## Simple Summary -A standard interface for insurance policies, based on ERC 721. - -## Abstract -The following standard allows for the implementation of a standard API for insurance policies within smart contracts. -Insurance policies are financial assets which are unique in some aspects, as they are connected to a customer, a specific risk, or have other unique properties like premium, period, carrier, underwriter etc. -Nevertheless, there are many potential applications where insurance policies can be traded, transferred or otherwise treated as an asset. -The ERC 721 standard already provides the standard and technical means to handle policies as a specific class of non fungible tokens. -insurance In this proposal, we define a minimum metadata structure with properties which are common to the greatest possible class of policies. - -## Motivation -For a decentralized insurance protocol, a standard for insurance policies is crucial for interoperability of the involved services and application. -It allows policies to be bundled, securitized, traded in a uniform and flexible way by many independent actors like syndicates, brokers, and insurance companies. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -An ERC-1523 compliant insurance policy is a non-fungible token which **MUST adhere to the ERC-721 token standard** and **MUST implement theERC721Metadata and the ERC721Enumerable interface**: - -```solidity -/// @title ERC-1523 Insurance Policy Standard -/// Note: the ERC-165 identifier for this interface is 0x5a04be32 -interface ERC1523 /* is ERC721, ERC721Metadata, ERC721Enumerable */ { - -} -``` - -The implementor MAY choose values for the ```name``` and ```symbol```. - -The **policy metadata extension** is **RECOMMENDED** for ERC-1523 smart contracts. -This allows your smart contract to be interrogated for policy metadata. - -```solidity -/// @title ERC-1523 Insurance Policy Standard, optional policy metadata extension -/// @dev See ... -/// Note: the ERC-165 identifier for this interface is 0x5a04be32 -interface ERC1523PolicyMetadata /* is ERC1523 */ { - - /// @notice Metadata string for a given property. - /// Properties are identified via hash of their property path. - /// e.g. the property "name" in the ERC721 Metadata JSON Schema has the path /properties/name - /// and the property path hash is the keccak256() of this property path. - /// this allows for efficient addressing of arbitrary properties, as the set of properties is potentially unlimited. - /// @dev Throws if `_propertyPathHash` is not a valid property path hash. - function policyMetadata(uint256 _tokenId, bytes32 _propertyPathHash) external view returns (string _property); - -} -``` - -In analogy to the “ERC721 Metadata JSON Schema”, the tokenURI **MUST** point to a JSON file with the following properties: -```json -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents", - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents", - }, - \[additional parameters according to the following table\] - } -} -``` - -### Additional parameters for the metadata JSON Schema - -| Parameter | Type | Mandatory | Description | -| ------------- | ------------- | ----------| ---------------------------------------------------------------------------------- | -| carrier | string | yes | Describes the carrier which takes the primary risk | -| risk | string | yes | Describes the risk | -| status | string | yes | Describes the status of the policy, e.g. applied for, underwritten, expired | -| parameters | string | no | Describes further parameters characterizing the risk | -| terms | string | no | Describes legal terms & conditions which apply for this policy | -| premium | string | no | A string representation of the premium, **MAY** contain currency denominator | -| sum_insured | string | no | A string representation of the sum insured, **MAY** contain currency denominator | - -Parameters which are mandatory **MUST** be included in the metadata JSON. Other parameters **MAY** be included. However, the proposed optional parameters **SHOULD** be used for the intended purpose, so e.g. if the premium amount would be included in the metadata, the parameter name **SHOULD** be "premium". -All parameters **MAY** be plain text or **MAY** also be URIs pointing to resources which contain the respective information, and which **MAY** be protected by an authentication mechanism. - -## Rationale -Insurance policies form an important class of financial assets, and it is natural to express those assets as a class of non-fungible tokens which adhere to the established ERC-721 standard. -We propose a standard for the accompanying metadata structures which are needed to uniquely define an insurance policy. Standardization is key because we expect decentralized insurance to receive widespread adoption and it is crucial to establish a unified standard to enable composability and the creation of universal toolsets. -We therefore propose a standardized naming scheme for the different parameters describing an insurance policy. We propose three mandatory parameters which need to be included in every NFT and further parameters which **MAY** be used, and for which we only standardize the naming conventions. -### Mandatory parameters -While policies can have a multitude of possible properties, it is common that policies are issued by some entity, which is basically the entity responsible for paying out claims. -Second, an insurance policy is typically related to a specific risk. Some risks are unique, but there are cases where many policies share the same risk -(e.g. all flight delay policies for the same flight). -In general, the relation of policies to risks is a many-to-one relation with the special case of a one-to-one relation. -Third, a policy has a lifecycle of different statuses. Therefore the NFT -We believe that those four properties are necessary to describe a policy. For many applications, those properties may be even sufficient. - -### Optional parameters -Most policies need more parameters to characterize the risk and other features, like premium, period etc. The naming conventions are listed in the above table. -However, any implementation **MAY** chose to implement more properties. - -### On-chain vs. off-chain metadata -For some applications it will be sufficient to store the metadata in an off-chain repository or database which can be addressed by the tokenURI resource locator. -For more advanced applications, it can be desirable to have metadata available on-chain. -Therefore, we require that the ```tokenURI``` **MUST** point to a JSON with the above structure, while the implementation of the ```policyMetadata``` function is **OPTIONAL**. - - -## Backwards Compatibility - -## Test Cases - -## Implementation - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1523.md diff --git a/EIPS/eip-1538.md b/EIPS/eip-1538.md index afa991a7e7961d..3510f843a1e49f 100644 --- a/EIPS/eip-1538.md +++ b/EIPS/eip-1538.md @@ -1,468 +1 @@ ---- -eip: 1538 -title: Transparent Contract Standard -author: Nick Mudge -discussions-to: https://github.com/ethereum/EIPs/issues/1538 -status: Withdrawn -type: Standards Track -category: ERC -created: 2018-10-31 ---- - -Replaced by [EIP-2535 Diamond Standard](./eip-2535.md). - -## Simple Summary -This standard provides a contract architecture that makes upgradeable contracts flexible, unlimited in size, and transparent. - -A transparent contract publicly documents the full history of all changes made to it. - -All changes to a transparent contract are reported in a standard format. - -## Abstract -A transparent contract is a proxy contract design pattern that provides the following: - -1. A way to add, replace and remove multiple functions of a contract atomically (at the same time). -1. Standard events to show what functions are added, replaced and removed from a contract, and why the changes are made. -2. A standard way to query a contract to discover and retrieve information about all functions exposed by it. -3. Solves the 24KB maximum contract size limitation, making the maximum contract size of a transparent contract practically unlimited. This standard makes the worry about contract size a thing of the past. -4. Enables an upgradeable contract to become immutable in the future if desired. - -## Motivation -A fundamental benefit of Ethereum contracts is that their code is immutable, thereby acquiring trust by trustlessness. People do not have to trust others if it is not possible for a contract to be changed. - -However, a fundamental problem with trustless contracts that cannot be changed is that they cannot be changed. - -#### Bugs - -Bugs and security vulnerabilities are unwittingly written into immutable contracts that ruin them. - -#### Improvements - -Immutable, trustless contracts cannot be improved, resulting in increasingly inferior contracts over time. - -Contract standards evolve, new ones come out. People, groups and organizations learn over time what people want and what is better and what should be built next. Contracts that cannot be improved not only hold back the authors that create them, but everybody who uses them. - -#### Upgradeable Contracts vs. Centralized Private Database -Why have an upgradeable contract instead of a centralized, private, mutable database? -Here are some reasons: -1. Because of the openness of storage data and verified code, it is possible to show a provable history of trustworthiness. -2. Because of the openness, bad behavior can be spotted and reported when it happens. -3. Independent security and domain experts can review the change history of contracts and vouch for their history of trustworthiness. -4. It is possible for an upgradeable contract to become immutable and trustless. -5. An upgradeable contract can have parts of it that are not upgradeable and so are partially immutable and trustless. - -#### Immutability - -In some cases immutable, trustless contracts are the right fit. This is the case when a contract is only needed for a short time or it is known ahead of time that there will never be any reason to change or improve it. - -### Middle Ground - -Transparent contracts provide a middle ground between immutable trustless contracts that can't be improved and upgradeable contracts that can't be trusted. - -### Purposes - -1. Create upgradeable contracts that earn trust by showing a provable history of trustworthiness. -2. Document the development of contracts so their development and change is provably public and can be understood. -3. Create upgradeable contracts that can become immutable in the future if desired. -4. Create contracts that are not limited by a max size. - -### Benefits & Use Cases -This standard is for use cases that benefit from the following: -1. The ability to add, replace or remove multiple functions of a contract atomically (at the same time). -2. Each time a function is added, replaced or removed, it is documented with events. -3. Build trust over time by showing all changes made to a contract. -4. Unlimited contract size. -5. The ability to query information about functions currently supported by the contract. -6. One contract address that provides all needed functionality and never needs to be replaced by another contract address. -7. The ability for a contract to be upgradeable for a time, and then become immutable. -8. Add trustless guarantees to a contract with "unchangeable functions". - -### New Software Possibilities - -This standard enables a form of contract version control software to be written. - -Software and user interfaces can be written to filter the `FunctionUpdate` and `CommitMessage` events of a contract address. Such software can show the full history of changes of any contract that implements this standard. - -User interfaces and software can also use this standard to assist or automate changes of contracts. - -## Specification - -> **Note:** -The solidity `delegatecall` opcode enables a contract to execute a function from another contract, but it is executed as if the function was from the calling contract. Essentially `delegatecall` enables a contract to "borrow" another contract's function. Functions executed with `delegatecall` affect the storage variables of the calling contract, not the contract where the functions are defined. - -### General Summary - -A transparent contract delegates or forwards function calls to it to other contracts using `delegatecode`. - -A transparent contract has an `updateContract` function that enables multiple functions to be added, replaced or removed. - -An event is emitted for every function that is added, replaced or removed so that all changes to a contract can be tracked in a standard way. - -A transparent contract is a contract that implements and complies with the design points below. - -### Terms - -1. In this standard a **delegate contract** is a contract that a transparent contract fallback function forwards function calls to using `delegatecall`. -2. In this standard an **unchangeable function** is a function that is defined directly in a transparent contract and so cannot be replaced or removed. - -### Design Points - -A contract is a transparent contract if it implements the following design points: - -1. A transparent contract is a contract that contains a fallback function, a constructor, and zero or more unchangeable functions that are defined directly within it. -2. The constructor of a transparent contract associates the `updateContract` function with a contract that implements the ERC1538 interface. The `updateContract` function can be an "unchangeable function" that is defined directly in the transparent contract or it can be defined in a delegate contract. Other functions can also be associated with contracts in the constructor. -3. After a transparent contract is deployed functions are added, replaced and removed by calling the `updateContract` function. -4. The `updateContract` function associates functions with contracts that implement those functions, and emits the `CommitMessage` and `FunctionUpdate` events that document function changes. -5. The `FunctionUpdate` event is emitted for each function that is added, replaced or removed. The `CommitMessage` event is emitted one time for each time the `updateContract` function is called and is emitted after any `FunctionUpdate` events are emitted. -6. The `updateContract` function can take a list of multiple function signatures in its `_functionSignatures` parameter and so add/replace/remove multiple functions at the same time. -7. When a function is called on a transparent contract it executes immediately if it is an "unchangeable function". Otherwise the fallback function is executed. The fallback function finds the delegate contract associated with the function and executes the function using `delegatecall`. If there is no delegate contract for the function then execution reverts. -8. The source code of a transparent contract and all delegate contracts used by it are publicly viewable and verified. - -The transparent contract address is the address that users interact with. The transparent contract address never changes. Only delegate addresses can change by using the `updateContracts` function. - -Typically some kind of authentication is needed for adding/replacing/removing functions from a transparent contract, **however the scheme for authentication or ownership is not part of this standard**. - -### Example - -Here is an example of an implementation of a transparent contract. Please note that the example below is an **example only. It is not the standard**. A contract is a transparent contract when it implements and complies with the design points listed above. - -```solidity -pragma solidity ^0.5.7; - -contract ExampleTransparentContract { - // owner of the contract - address internal contractOwner; - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - // maps functions to the delegate contracts that execute the functions - // funcId => delegate contract - mapping(bytes4 => address) internal delegates; - - // maps each function signature to its position in the funcSignatures array. - // signature => index+1 - mapping(bytes => uint256) internal funcSignatureToIndex; - - event CommitMessage(string message); - event FunctionUpdate(bytes4 indexed functionId, address indexed oldDelegate, address indexed newDelegate, string functionSignature); - - // this is an example of an "unchangeable function". - // return the delegate contract address for the supplied function signature - function delegateAddress(string calldata _functionSignature) external view returns(address) { - require(funcSignatureToIndex[bytes(_functionSignature)] != 0, "Function signature not found."); - return delegates[bytes4(keccak256(bytes(_functionSignature)))]; - } - - // add a function using the updateContract function - // this is an internal helper function - function addFunction(address _erc1538Delegate, address contractAddress, string memory _functionSignatures, string memory _commitMessage) internal { - // 0x03A9BCCF == bytes4(keccak256("updateContract(address,string,string)")) - bytes memory funcdata = abi.encodeWithSelector(0x03A9BCCF, contractAddress, _functionSignatures, _commitMessage); - bool success; - assembly { - success := delegatecall(gas, _erc1538Delegate, add(funcdata, 0x20), mload(funcdata), funcdata, 0) - } - require(success, "Adding a function failed"); - } - - constructor(address _erc1538Delegate) public { - contractOwner = msg.sender; - emit OwnershipTransferred(address(0), msg.sender); - - // adding ERC1538 updateContract function - bytes memory signature = "updateContract(address,string,string)"; - bytes4 funcId = bytes4(keccak256(signature)); - delegates[funcId] = _erc1538Delegate; - emit FunctionUpdate(funcId, address(0), _erc1538Delegate, string(signature)); - emit CommitMessage("Added ERC1538 updateContract function at contract creation"); - - // associate "unchangeable functions" with this transparent contract address - // prevents function selector clashes with delegate contract functions - // uses the updateContract function - string memory functions = "delegateAddress(string)"; - addFunction(_erc1538Delegate, address(this), functions, "Associating unchangeable functions"); - - // adding ERC1538Query interface functions - functions = "functionByIndex(uint256)functionExists(string)delegateAddresses()delegateFunctionSignatures(address)functionById(bytes4)functionBySignature(string)functionSignatures()totalFunctions()"; - // "0x01234567891011121314" is an example address of an ERC1538Query delegate contract - addFunction(_erc1538Delegate, 0x01234567891011121314, functions, "Adding ERC1538Query functions"); - - // additional functions could be added at this point - } - - // Making the fallback function payable makes it work for delegate contract functions - // that are payable and not payable. - function() external payable { - // Delegate every function call to a delegate contract - address delegate = delegates[msg.sig]; - require(delegate != address(0), "Function does not exist."); - assembly { - let ptr := mload(0x40) - calldatacopy(ptr, 0, calldatasize) - let result := delegatecall(gas, delegate, ptr, calldatasize, 0, 0) - let size := returndatasize - returndatacopy(ptr, 0, size) - switch result - case 0 {revert(ptr, size)} - default {return (ptr, size)} - } - } -} -``` -As can be seen in the above example, every function call is delegated to a delegate contract, unless the function is defined directly in the transparent contract (making it an unchangeable function). - -The constructor function adds the `updateContract` function to the transparent contract, which is then used to add other functions to the transparent contract. - -Each time a function is added to a transparent contract the events `CommitMessage` and `FunctionUpdate` are emitted to document exactly what functions where added or replaced and why. - -The delegate contract that implements the `updateContract` function implements the following interface: -### ERC1538 Interface - -```solidity -pragma solidity ^0.5.7; - -/// @title ERC1538 Transparent Contract Standard -/// @dev Required interface -/// Note: the ERC-165 identifier for this interface is 0x61455567 -interface ERC1538 { - /// @dev This emits when one or a set of functions are updated in a transparent contract. - /// The message string should give a short description of the change and why - /// the change was made. - event CommitMessage(string message); - - /// @dev This emits for each function that is updated in a transparent contract. - /// functionId is the bytes4 of the keccak256 of the function signature. - /// oldDelegate is the delegate contract address of the old delegate contract if - /// the function is being replaced or removed. - /// oldDelegate is the zero value address(0) if a function is being added for the - /// first time. - /// newDelegate is the delegate contract address of the new delegate contract if - /// the function is being added for the first time or if the function is being - /// replaced. - /// newDelegate is the zero value address(0) if the function is being removed. - event FunctionUpdate( - bytes4 indexed functionId, - address indexed oldDelegate, - address indexed newDelegate, - string functionSignature - ); - - /// @notice Updates functions in a transparent contract. - /// @dev If the value of _delegate is zero then the functions specified - /// in _functionSignatures are removed. - /// If the value of _delegate is a delegate contract address then the functions - /// specified in _functionSignatures will be delegated to that address. - /// @param _delegate The address of a delegate contract to delegate to or zero - /// to remove functions. - /// @param _functionSignatures A list of function signatures listed one after the other - /// @param _commitMessage A short description of the change and why it is made - /// This message is passed to the CommitMessage event. - function updateContract(address _delegate, string calldata _functionSignatures, string calldata _commitMessage) external; -} -``` -### Function Signatures String Format - -The text format for the `_functionSignatures` parameter is simply a string of function signatures. For example: `"myFirstFunction()mySecondFunction(string)"` This format is easy to parse and is concise. - -Here is an example of calling the `updateContract` function that adds the ERC721 standard functions to a transparent contract: -```javascript -functionSignatures = "approve(address,uint256)balanceOf(address)getApproved(uint256)isApprovedForAll(address,address)ownerOf(uint256)safeTransferFrom(address,address,uint256)safeTransferFrom(address,address,uint256,bytes)setApprovalForAll(address,bool)transferFrom(address,address,uint256)" -tx = await transparentContract.updateContract(erc721Delegate.address, functionSignatures, "Adding ERC721 functions"); -``` - -### Removing Functions - -Functions are removed by passing `address(0)` as the first argument to the `updateContract` function. The list of functions that are passed in are removed. - -### Source Code Verification - -The transparent contract source code and the source code for the delegate contracts should be verified in a provable way by a third party source such as etherscan.io. - - -### Function Selector Clash -A function selector clash occurs when a function is added to a contract that hashes to the same four-byte hash as an existing function. This is unlikely to occur but should be prevented in the implementation of the `updateContract` function. See the [reference implementation of ERC1538](https://github.com/mudgen/transparent-contracts-erc1538) to see an example of how function clashes can be prevented. - -### ERC1538Query - -Optionally, the function signatures of a transparent contract can be stored in an array in the transparent contract and queried to get what functions the transparent contract supports and what their delegate contract addresses are. - -The following is an optional interface for querying function information from a transparent contract: - -```solidity -pragma solidity ^0.5.7; - -interface ERC1538Query { - - /// @notice Gets the total number of functions the transparent contract has. - /// @return The number of functions the transparent contract has, - /// not including the fallback function. - function totalFunctions() external view returns(uint256); - - /// @notice Gets information about a specific function - /// @dev Throws if `_index` >= `totalFunctions()` - /// @param _index The index position of a function signature that is stored in an array - /// @return The function signature, the function selector and the delegate contract address - function functionByIndex(uint256 _index) - external - view - returns( - string memory functionSignature, - bytes4 functionId, - address delegate - ); - - /// @notice Checks to see if a function exists - /// @param The function signature to check - /// @return True if the function exists, false otherwise - function functionExists(string calldata _functionSignature) external view returns(bool); - - /// @notice Gets all the function signatures of functions supported by the transparent contract - /// @return A string containing a list of function signatures - function functionSignatures() external view returns(string memory); - - /// @notice Gets all the function signatures supported by a specific delegate contract - /// @param _delegate The delegate contract address - /// @return A string containing a list of function signatures - function delegateFunctionSignatures(address _delegate) external view returns(string memory); - - /// @notice Gets the delegate contract address that supports the given function signature - /// @param The function signature - /// @return The delegate contract address - function delegateAddress(string calldata _functionSignature) external view returns(address); - - /// @notice Gets information about a function - /// @dev Throws if no function is found - /// @param _functionId The id of the function to get information about - /// @return The function signature and the contract address - function functionById(bytes4 _functionId) - external - view - returns( - string memory signature, - address delegate - ); - - /// @notice Get all the delegate contract addresses used by the transparent contract - /// @return An array of all delegate contract addresses - function delegateAddresses() external view returns(address[] memory); -} -``` - -See the [reference implementation of ERC1538](https://github.com/mudgen/transparent-contracts-erc1538) to see how this is implemented. - -The text format for the list of function signatures returned from the `delegateFunctionSignatures` and `functionSignatures` functions is simply a string of function signatures. Here is an example of such a string: `"approve(address,uint256)balanceOf(address)getApproved(uint256)isApprovedForAll(address,address)ownerOf(uint256)safeTransferFrom(address,address,uint256)safeTransferFrom(address,address,uint256,bytes)setApprovalForAll(address,bool)transferFrom(address,address,uint256)"` - -### How To Deploy A Transparent Contract -1. Create and deploy to a blockchain a contract that implements the ERC1538 interface. You can skip this step if there is already such a contract deployed to the blockchain. -2. Create your transparent contract with a fallback function as given above. Your transparent contract also needs a constructor that adds the `updateContract` function. -3. Deploy your transparent contract to a blockchain. Pass in the address of the ERC1538 delegate contract to your constructor if it requires it. - -See the [reference implementation](https://github.com/mudgen/transparent-contracts-erc1538) for examples of these contracts. - -### Wrapper Contract for Delegate Contracts that Depend on Other Delegate Contracts -In some cases some delegate contracts may need to call external/public functions that reside in other delegate contracts. A convenient way to solve this problem is to create a contract that contains empty implementations of functions that are needed and import and extend this contract in delegate contracts that call functions from other delegate contracts. This enables delegate contracts to compile without having to provide implementations of the functions that are already given in other delegate contracts. This is a way to save gas, prevent reaching the max contract size limit, and prevent duplication of code. This strategy was given by @amiromayer. [See his comment for more information.](https://github.com/ethereum/EIPs/issues/1538#issuecomment-451985155) Another way to solve this problem is to use assembly to call functions provided by other delegate contracts. - -### Decentralized Authority -It is possible to extend this standard to add consensus functionality such as an approval function that multiple different people call to approve changes before they are submitted with the `updateContract` function. Changes only go into effect when the changes are fully approved. The `CommitMessage` and ` FunctionUpdate` events should only be emitted when changes go into effect. - -## Security -> This standard refers to **owner(s)** as one or more individuals that have the power to add/replace/remove functions of an upgradeable contract. - -### General - -The owners(s) of an upgradeable contract have the ability to alter, add or remove data from the contract's data storage. Owner(s) of a contract can also execute any arbitrary code in the contract on behalf of any address. Owners(s) can do these things by adding a function to the contract that they call to execute arbitrary code. This is an issue for upgradeable contracts in general and is not specific to transparent contracts. - ->**Note:** The design and implementation of contract ownership is **not** part of this standard. The examples given in this standard and in the reference implementation are just **examples** of how it could be done. - -### Unchangeable Functions - -"Unchangeable functions" are functions defined in a transparent contract itself and not in a delegate contract. The owner(s) of a transparent contract are not able to replace these functions. The use of unchangeable functions is limited because in some cases they can still be manipulated if they read or write data to the storage of the transparent contract. Data read from the transparent contract's storage could have been altered by the owner(s) of the contract. Data written to the transparent contract's storage can be undone or altered by the owner(s) of the contract. - -In some cases unchangeble functions add trustless guarantees to a transparent contract. - -### Transparency - -Contracts that implement this standard emit an event every time a function is added, replaced or removed. This enables people and software to monitor the changes to a contract. If any bad acting function is added to a contract then it can be seen. To comply with this standard all source code of a transparent contract and delegate contracts must be publicly available and verified. - -Security and domain experts can review the history of change of any transparent contract to detect any history of foul play. - -## Rationale - -### String of Function Signatures Instead of bytes4[] Array of Function Selectors - -The `updateContract` function takes a `string` list of functions signatures as an argument instead of a `bytes4[]` array of function selectors for three reasons: - -1. Passing in function signatures enables the implementation of `updateContract` to prevent selector clashes. -2. A major part of this standard is to make upgradeable contracts more transparent by making it easier to see what has changed over time and why. When a function is added, replaced or removed its function signature is included in the FunctionUpdate event that is emitted. This makes it relatively easy to write software that filters the events of a contract to display to people what functions have been added/removed and changed over time without needing access to the source code or ABI of the contract. If only four-byte function selectors were provided this would not be possible. -3. By looking at the source code of a transparent contract it is not possible to see all the functions that it supports. This is why the ERC1538Query interface exists, so that people and software have a way to look up and examine or show all functions currently supported by a transparent contract. Function signatures are used so that ERC1538Query functions can show them. - -### Gas Considerations - -Delegating function calls does have some gas overhead. This is mitigated in two ways: -1. Delegate contracts can be small, reducing gas costs. Because it costs more gas to call a function in a contract with many functions than a contract with few functions. -2. Because transparent contracts do not have a max size limitation it is possible to add gas optimizing functions for use cases. For example someone could use a transparent contract to implement the ERC721 standard and implement batch transfer functions from the [ERC1412 standard](https://github.com/ethereum/EIPs/issues/1412) to help reduce gas (and make batch transfers more convenient). - -### Storage - -The standard does not specify how data is stored or organized by a transparent contract. But here are some suggestions: - -**Inherited Storage** - -1. The storage variables of a transparent contract consist of the storage variables defined in the transparent contract source code and the source code of delegate contracts that have been added. - -2. A delegate contract can use any storage variable that exists in a transparent contract as long as it defines within it all the storage variables that exist, in the order that they exist, up to and including the ones being used. - -3. A delegate contract can create new storage variables as long as it has defined, in the same order, all storage variables that exist in the transparent contract. - -Here is a simple way inherited storage could be implemented: - -1. Create a storage contract that contains the storage variables that your transparent contract and delegate contracts will use. -2. Make your delegate contracts inherit the storage contract. -3. If you want to add a new delegate contract that adds new storage variables then create a new storage contract that adds the new storage variables and inherits from the old storage contract. Use your new storage contract with your new delegate contract. -4. Repeat steps 2 or 3 for every new delegate contract. - - -**Unstructured Storage** - -Assembly is used to store and read data at specific storage locations. An advantage to this approach is that previously used storage locations don't have to be defined or mentioned in a delegate contract if they aren't used by it. - -**Eternal Storage** - -Data can be stored using a generic API based on the type of data. [See ERC930 for more information.](https://github.com/ethereum/EIPs/issues/930) - -### Becoming Immutable -It is possible to make a transparent contract become immutable. This is done by calling the `updateContract` function to remove the `updateContract` function. With this gone it is no longer possible to add, replace and remove functions. - -### Versions of Functions - -Software or a user can verify what version of a function is called by getting the delegate contract address of the function. This can be done by calling the `delegateAddress` function from the ERC1538Query interface if it is implemented. This function takes a function signature as an argument and returns the delegate contract address where it is implemented. - -### Best Practices, Tools and More Information - -> More information, tools, tutorials and best practices concerning transparent contracts need to be developed and published. - -Below is a growing list of articles concerning transparent contracts and their use. If you have an article about transparent contracts you would like to share then please submit a comment to this issue about it to get it added. - -[ERC1538: Future Proofing Smart Contracts and Tokens](https://coinjournal.net/erc1538-future-proofing-smart-contacts-and-tokens/) - -[The ERC1538 improving towards the “transparent contract” standard](https://www.crypto-economy.net/en/ethereum-eth-erc1538-transparent-contract-standard/) - -### Inspiration - -This standard was inspired by ZeppelinOS's implementation of [Upgradeability with vtables](https://github.com/zeppelinos/labs/tree/master/upgradeability_with_vtable). - -This standard was also inspired by the design and implementation of the [Mokens contract](https://etherscan.io/address/0xc1eab49cf9d2e23e43bcf23b36b2be14fc2f8838#code) from the [Mokens project](https://github.com/Mokens/MIPs/blob/master/MIPS/mip-2-Goals-and-Objectives.md). The Mokens contract has been [upgraded to implement this standard](https://etherscan.io/address/0x0ac5637fe62ec14fd9e237a81a9679d4adef701f#code). - - -## Backwards Compatibility -This standard makes a contract compatible with future standards and functionality because new functions can be added and existing functions can be replaced or removed. - -This standard future proofs a contract. - -## Implementation -A reference implementation of this standard is given in the [transparent-contracts-erc1538](https://github.com/mudgen/transparent-contracts-erc1538) repository. - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1538.md diff --git a/EIPS/eip-1577.md b/EIPS/eip-1577.md index c555e53fe11c35..cc048c1cf6bd22 100644 --- a/EIPS/eip-1577.md +++ b/EIPS/eip-1577.md @@ -1,116 +1 @@ ---- -eip: 1577 -title: contenthash field for ENS -author: Dean Eigenmann , Nick Johnson -type: Standards Track -category: ERC -status: Stagnant -created: 2018-11-13 ---- - -## Abstract - -This EIP introduces the new `contenthash` field for ENS resolvers, allowing for a better defined system of mapping names to network and content addresses. Additionally the `content` and `multihash` fields are deprecated. - -## Motivation - -Multiple applications including [Metamask](https://metamask.io/) and mobile clients such as [Status](https://status.im) have begun resolving ENS names to content hosted on distributed systems such as [IPFS](https://ipfs.io/) and [Swarm](https://swarm-guide.readthedocs.io). Due to the various ways content can be stored and addressed, a standard is required so these applications know how to resolve names and that domain owners know how their content will be resolved. - -The `contenthash` field allows for easy specification of network and content addresses in ENS. - -## Specification - -The field `contenthash` is introduced, which permits a wide range of protocols to be supported by ENS names. Resolvers supporting this field MUST return `true` when the `supportsInterface` function is called with argument `0xbc1c58d1`. - -The fields `content` and `multihash` are deprecated. - -The value returned by `contenthash` MUST be represented as a machine-readable [multicodec](https://github.com/multiformats/multicodec). The format is specified as follows: - -``` - -``` - -protoCodes and their meanings are specified in the [multiformats/multicodec](https://github.com/multiformats/multicodec) repository. - -The encoding of the value depends on the content type specified by the protoCode. Values with protocodes of 0xe3 and 0xe4 represent IPFS and Swarm content; these values are encoded as v1 [CIDs](https://github.com/multiformats/cid) without a base prefix, meaning their value is formatted as follows: - -``` - -``` - -When resolving a `contenthash`, applications MUST use the protocol code to determine what type of address is encoded, and resolve the address appropriately for that protocol, if supported. - -### Example - -#### IPFS - -Input data: - -``` -storage system: IPFS (0xe3) -CID version: 1 (0x01) -content type: dag-pb (0x70) -hash function: sha2-256 (0x12) -hash length: 32 bytes (0x20) -hash: 29f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f -``` - -Binary format: - -``` -0xe3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f -``` - -Text format: - -``` -ipfs://QmRAQB6YaCyidP37UdDnjFY5vQuiBrcqdyoW1CuDgwxkD4 -``` - -### Swarm - -Input data: - -``` -storage system: Swarm (0xe4) -CID version: 1 (0x01) -content type: swarm-manifest (0xfa) -hash function: keccak256 (0x1b) -hash length: 32 bytes (0x20) -hash: d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162 -``` - -Binary format: -``` -0xe40101fa011b20d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162 -``` - -Text format: -``` -bzz://d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162 -``` - -Example usage with swarm hash: -``` -$ swarm hash ens contenthash d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162 -> e40101fa011b20d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162 -``` - -### Fallback - -In order to support names that have an IPFS or Swarm hash in their `content` field, a grace period MUST be implemented offering those name holders time to update their names. If a resolver does not support the `multihash` interface, it MUST be checked whether they support the `content` interface. If they do, the value of that field SHOULD be treated in a context dependent fashion and resolved. This condition MUST be enforced until at least March 31st, 2019. - -### Implementation - -To support `contenthash`, a new resolver has been developed and can be found [here](https://github.com/ensdomains/resolvers/blob/master/contracts/PublicResolver.sol), you can also find this smart contract deployed on: - -* Mainnet : [0xd3ddccdd3b25a8a7423b5bee360a42146eb4baf3](https://etherscan.io/address/0xd3ddccdd3b25a8a7423b5bee360a42146eb4baf3) -* Ropsten : [0xde469c7106a9fbc3fb98912bb00be983a89bddca](https://ropsten.etherscan.io/address/0xde469c7106a9fbc3fb98912bb00be983a89bddca) - -There are also implementations in multiple languages to encode and decode `contenthash`: - -* [JavaScript](https://github.com/pldespaigne/content-hash) -* [Python](https://github.com/filips123/ContentHashPy) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1577.md diff --git a/EIPS/eip-1581.md b/EIPS/eip-1581.md index d4634d745bc145..b3d3677f766f1e 100644 --- a/EIPS/eip-1581.md +++ b/EIPS/eip-1581.md @@ -1,49 +1 @@ ---- -eip: 1581 -title: Non-wallet usage of keys derived from BIP-32 trees -description: A derivation path structure for BIP32 trees to generate key pairs not meant to hold crypto assets. -author: Michele Balistreri (@bitgamma) -discussions-to: https://ethereum-magicians.org/t/non-wallet-usage-of-keys-derived-from-bip-32-trees/1817 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-11-13 ---- -## Abstract -BIP32 defines a way to generate hierarchical trees of keys which can be derived from a common master key. BIP32 and [BIP44](https://https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) defines the usage of these keys as wallets. In this EIP we describe the usage of such keys outside the scope of the blockchain defining a logical tree for key usage which can coexist (and thus share the same master) with existing BIP44 compatible wallets. - -## Motivation -Applications interacting with the blockchain often make use of additional, non-blockchain technologies to perform the task they are designed for. For privacy and security sensitive mechanisms, sets of keys are needed. Reusing keys used for wallets can prove to be insecure, while keeping completely independent keys make backup and migration of the full set of credentials more complex. Defining a separate (from BIP44 compliant wallets) derivation branch allows combining the security of independent keys with the convenience of having a single piece of information which needs to be backup or migrated. - -## Specification - -### Path levels -We define the following levels in BIP32 path: - -```m / purpose' / coin_type' / subpurpose' / key_type' / key_index``` - -Apostrophe in the path indicates that BIP32 hardened derivation is used. - -This structure follows the [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki) recommendations and its [amendments for non-Bitcoin usage](https://github.com/bitcoin/bips/pull/523/files). Each level has a special meaning, described in the chapters below. - -### Purpose/Coin Type/Subpurpose -This part is constant and set to ```m / 43' / 60' / 1581'```, meaning BIP 43 -> Ethereum -> This EIP. - -All subtrees under this prefix are the scope of this EIP. - -### Key type -Describes the purpose for which the key is being used. Key types should be generic. "Instant messaging" is a good example whereas "Whisper" is not. The reason is that you want to be able to use the same identity across different services. Key types are defined at: TBD - -Hardened derivation is used at this level. - -### Key index -The key index is a field of variable length identifying a specific key. In its simplest case, it is a number from 0 to 2^31-1. If a larger identifier is desired (for example representing a hash or a GUID), the value must be split -across several BIP32 nesting levels, most significant bit first and left aligned, bit-padded with 0s if needed. All levels, except the last one must used hardened key derivation. The last level must use public derivation. This means that every level can carry 31-bit of the identifier to represent. - -As an example, let's assume we have a key with key type 4' and a key_index representing a 62-bit ID represented as hexadecimal 0x2BCDEFFEDCBAABCD the complete keypath would be ```m / 43' / 60' / 1581' / 4' / ‭1469833213‬' / ‭1555737549‬ ```. If you are using random identifiers, it might be convenient to generate a conventional GUID, for example 128-bit just fix the value of the most significant bit of each 32-bit word to 1 for all of them, except the last one which will be 0. - -## Rationale -The structure proposed above follows the BIP43 generic structure and is similar to the widely adopted BIP44 specification. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1581.md diff --git a/EIPS/eip-1592.md b/EIPS/eip-1592.md index dd97e594393df0..657e189b5a7e68 100644 --- a/EIPS/eip-1592.md +++ b/EIPS/eip-1592.md @@ -1,171 +1 @@ ---- -eip: 1592 -title: Address and ERC20-compliant transfer rules -author: Cyril Lapinte , Laurent Aapro -discussions-to: https://github.com/ethereum/EIPs/issues/1597 -type: Standards Track -category: ERC -status: Stagnant -created: 2018-11-09 ---- - -## Simple Summary - -We propose a standard and an interface to define transfer rules, in the context of ERC20 tokens and possibly beyond. - - -A rule can act based on sender, destination and amount, and is triggered (and rejects the transfer) according to any required business logic. - - -To ease rule reusability and composition, we also propose an interface and base implementation for a rule engine. - -## Abstract - -This standard proposal should answer the following challenges: -- Enable integration of rules with interacting platforms such as exchanges, decentralized wallets and DApps. -- Externale code and storage, improve altogether reusability, gas costs and contracts' memory footprint. -- Highlight contract behavior and its evolution, in order to ease user interaction with such contract. - - -If these challenges are answered, this proposal will provide a unified basis for transfer rules and hopefully address the transfer restriction needs of other EIPs as well, e.g. -[EIP-902](./eip-902.md), -[EIP-1066](./eip-1066.md) -and [EIP-1175](./eip-1175.md). - -This document proposes specifications for a standard of **transfer rules** and interfaces to both the rules and the rule engine, which was made to be inherited by a token, but may have a much broader scope in the authors' opinion. - -The last section of this document illustrates the proposal with a rule template and links to rule implementations. - -## Motivation - -ERC20 was designed as a standard interface allowing any token on Ethereum to be handled by other applications: from wallets to decentralized exchanges. This has been extremely powerful, but future developments in the industry of tokenization are bringing new challenges. For example it is already hard to know exactly why an ERC20 transfer failed, and it will become even harder when many tokens add their own transfer rules to the mix; we propose that it should be trivial to determine before a tx is sent, whether the transfer should turn out valid or invalid, and why (unless conditions change in the meantime obviously). On the other hand, if the rules were changed, it should also be easily detected, so that the interacting party knows it must adjust its expectations or model. - -## Specification - -We define below an interface for a rule. Rules are meant to be as simple as possible, to limit gas expenditure, since that logic will be executed on every transfer. Another reason for keeping rules simple and short, and strive for atomicity, is to facilitate both composition and interpretation of rejected transfers. By knowing which rule was triggered, we obtain a clear picture of the reason for rejection. - -The engine we propose executes all the rules defined by its owner, on every transfer and it is easy to add and remove rules individually, although we have chosen to use quite a raw rule update method, to save on deployment costs, which are often tight when it comes to token smart contracts. - -Rules are deployed on the blockchain as individual smart contracts, and called upon by the rule engine they were attached to. But any third party, for example an exchange preparing a cashout for a customer, can very cheaply query the rule engine of the token, or a single rule directly, to verify the validity of a transfer before execution, so as to never get a rejected transaction. - -## Rule interface - -`IRule` interface should provide a way to validate if an address or a transfer is valid. - -If one of these two methods is not applicable, it can simply be made to return true systematically. -If any parameter of `isTransferValid` is not needed, its name should be commented out with `/* */`. - -```js -pragma solidity ^0.4.25; - -interface IRule { - function isAddressValid(address _address) external view returns (bool); - function isTransferValid(address _from, address _to, uint256 _amount) - external view returns (bool); -} -``` - -## WithRules interface - -`WithRules` interface describes the integration of rules to a rule engine. -Developers may choose to not implement this interface if their code will only deal with one rule, or if it is not desirable to update the rules. - -The rules ordering must be thought through carefully. -Rules which are cheaper to validate or have a higher chance to break should be put first to reduce global gas expenditure, then business logic should guide the ordering of rules. That is why rules for a given context should be defined as a whole and not individually. - -```js -pragma solidity ^0.4.25; - -import "./IRule.sol"; - -interface IWithRules { - function ruleLength() public view returns (uint256); - function rule(uint256 _ruleId) public view returns (IRule); - function validateAddress(address _address) public view returns (bool); - function validateTransfer(address _from, address _to, uint256 _amount) - public view returns (bool); - - function defineRules(IRule[] _rules) public; - - event RulesDefined(uint256 count); -} -``` - -## WithRules implementation - -We also propose a simple implementation of the rule engine, available [here](https://github.com/MtPelerin/MtPelerin-protocol/blob/master/contracts/rule/WithRules.sol). It has been kept minimal both to save on gas costs on each transfer, and to reduce the deployment cost overhead for the derived smart contract. - - -On top of implementing the interface above, this engine also defines two modifiers (`whenAddressRulesAreValid`and `whenTransferRulesAreValid`), which can be used throughout the token contract to restrict `transfer()`, `transferFrom` and any other function that needs to respect either a simple whitelist or complex transfer rules. - - -## Integration - -To use rules within a token is as easy as having the token inherit from WithRules, then writing rules according to the IRule interface and deploying each rule individually. The token owner can then use `defineRules()` to attach all rules in the chosen order, within a single transaction. - -Below is a template for a rule. - -```solidity -import "../interface/IRule.sol"; - -contract TemplateRule is IRule { - - // state vars for business logic - - constructor(/* arguments for init */) public { - - // initializations - - } - - function isAddressValid(address _from) public view returns (bool) { - boolean isValid; - - // business logic - - return isValid; - } - - function isTransferValid( - address _from, - address _to, - uint256 _amount) - public view returns (bool) - { - boolean isValid; - - // business logic - - return isValid; - } -} -``` - -*** Notes *** -The MPS (Mt Pelerin's Share) token is the current live implementation of this standard. -Other implementations may be written with different trade-offs: from gas savings to improved security. - -#### Example of rules implementations - -- [YesNo rule](https://github.com/MtPelerin/MtPelerin-protocol/tree/master/contracts/rule/YesNoRule.sol): Trivial rule used to demonstrate both a rule and the rule engine. - -- [Freeze rule](https://github.com/MtPelerin/MtPelerin-protocol/tree/master/contracts/rule/FreezeRule.sol): This rule allows to prevent any transfer of tokens to or from chosen addresses. A smart blacklist. - -- [Lock rule](https://github.com/MtPelerin/MtPelerin-protocol/tree/master/contracts/rule/LockRule.sol): Define a global transfer policy preventing either sending or receiving tokens within a period of time. Exceptions may be granted to some addresses by the token admin. A smart whitelist. - -- [User Kyc Rule](https://github.com/MtPelerin/MtPelerin-protocol/tree/master/contracts/rule/UserKycRule.sol): Rule example relying on an existing whitelist to assert transfer and addresses validity. It is a good example of a rule that completely externalizes it's tasks. - -#### Example implementations are available at -- [Mt Pelerin Bridge protocol rules implementation](https://github.com/MtPelerin/MtPelerin-protocol/tree/master/contracts/rule) -- [Mt Pelerin Token with rules](https://github.com/MtPelerin/MtPelerin-protocol/blob/master/contracts/token/component/TokenWithRules.sol) - -## History - -Historical links related to this standard: - -- The first regulated tokenized share issued by Mt Pelerin (MPS token) is using an early version of this proposal: https://www.mtpelerin.com/blog/world-first-tokenized-shares -The rule engine was updated several times, after the token issuance and during the tokensale, to match changing business and legal requirements, showcasing the solidity and flexibility of the rule engine. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). -External references outside this repository will have their own specific copyrights. +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1592.md diff --git a/EIPS/eip-1613.md b/EIPS/eip-1613.md index 64d24a4a90e25a..e92dba038a15f6 100644 --- a/EIPS/eip-1613.md +++ b/EIPS/eip-1613.md @@ -1,300 +1 @@ ---- -eip: 1613 -title: Gas stations network -author: Yoav Weiss , Dror Tirosh , Alex Forshtat -discussions-to: https://github.com/yoav-tabookey/EIPs/issues/1 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-11-18 -requires: 1077 ---- - -## Simple Summary -Make smart contracts (e.g. dapps) accessible to non-ether users by allowing contracts to accept "[collect-calls](https://en.wikipedia.org/wiki/Collect_call)", paying for incoming calls. -Let contracts "listen" on publicly accessible channels (e.g. web URL or a whisper address). -Incentivize nodes to run "gas stations" to facilitate this. -Require no network changes, and minimal contract changes. - -## Abstract -Communicating with dapps currently requires paying ETH for gas, which limits dapp adoption to ether users. -Therefore, contract owners may wish to pay for the gas to increase user acquisition, or let their users pay for gas with fiat money. -Alternatively, a 3rd party may wish to subsidize the gas costs of certain contracts. -Solutions such as described in [EIP-1077](./eip-1077.md) could allow transactions from addresses that hold no ETH. - -The gas stations network is an [EIP-1077](./eip-1077.md) compliant effort to solve the problem by creating an incentive for nodes to run gas stations, where gasless transactions can be "fueled up". -It abstracts the implementation details from both the dapp maintainer and the user, making it easy to convert existing dapps to accept "collect-calls". - -The network consists of a single public contract trusted by all participating dapp contracts, and a decentralized network of relay nodes (gas stations) incentivized to listen on non-ether interfaces such as web or whisper, -pay for transactions and get compensated by that contract. The trusted contract can be verified by anyone, and the system is otherwise trustless. -Gas stations cannot censor transactions as long as there's at least one honest gas station. Attempts to undermine the system can be proven on-chain and offenders can be penalized. - -## Motivation - -* Increase user adoption of smart contracts by: - * Removing the user hassle of acquiring ETH. Transactions are still paid by ETH but costs can be borne by the dapp or paid by the user through other means. - * Removing the need to interact directly with the blockchain, while maintaining decentralization and censorship-resistance. - Contracts can "listen" on multiple public channels, and users can interact with the contracts through common protocols that are generally permitted even in restrictive environments. -* Ethereum nodes get a revenue source without requiring mining equipment. The entire network benefits from having more nodes. -* No protocol changes required. The gas station network is self-organized via a smart contract, and dapps interact with the network by implementing an interface. - -## Specification - -The system consists of a `RelayHub` singleton contract, participating contracts inheriting the `RelayRecipient` contract, a decentralized network of `Relay` nodes, a.k.a. Gas Stations, -and user applications (e.g. mobile or web) interacting with contracts via relays. - -Roles of the `RelayHub`: - -* Maintain a list of active relays. Senders select a `Relay` from this list for each transaction. The selection process is discussed below. -* Mediate all communication between relays and contracts. -* Provide contracts with trusted versions of the real msg.sender and msg.data. -* Hold ETH stakes placed by relays. A minimum stake size is enforced. Stake can be withdrawn after a relay unregisters and waits for a cooldown period. -* Hold ETH prepayments made by contracts and use them to compensate relays. -* Penalize provably-offensive relays by giving their stakes to an address providing the proof, thus keeping relays honest. -* Provide a free way for relays to know whether they'll be compensated for a future transaction. - -Roles of a `Relay` node: - -* Maintain a hot wallet with a small amount of ETH, to pay for gas. -* Provide a public interface for user apps to send gasless transactions via channels such as https or whisper. -* Publish it's public interfaces and its price (as a multiplier of the actual transaction gas cost) in `RelayHub`. -* Optionally monitor reverted transactions of other relays through RelayHub, catching offending relays and claiming their stakes. This can be done by anyone, not just a relay. - -Implementing a `RelayRecipient` contract: - -* Know the address of `RelayHub` and trust it to provide information about the transaction. -* Maintain a small balance of ETH gas prepayment deposit in `RelayHub`. Can be paid directly by the `RelayRecipient` contract, or by the dapp's owner on behalf of the `RelayRecipient` address. - The dapp owner is responsible for ensuring sufficient balance for the next transactions, and can stop depositing if something goes wrong, thus limiting the potential for abuse of system bugs. In DAO usecases it will be up to the DAO logic to maintain a sufficient deposit. -* Use `getSender()` and `getMessageData()` instead of `msg.sender` and `msg.data`, everywhere. `RelayRecipient` provides these functions and gets the information from `RelayHub`. -* Implement a `acceptRelayedCall(address relay, address from, bytes memory encodedFunction, uint gasPrice, uint transactionFee, bytes memory approval)` view function that returns **zero** if and only if it is willing to accept a transaction and pay for it. - `acceptRelayedCall` is called by `RelayHub` as a view function when a `Relay` inquires it, and also during the actual transaction. Transactions are reverted if **non-zero**, and `Relay` only gets compensated for transactions (whether successful or reverted) if `acceptRelayedCall` returns **zero**. Some examples of `acceptRelayedCall()` implementations: - * Whitelist of trusted dapp members. - * Balance sheet of registered users, maintained by the dapp owner. Users pay the dapp with a credit card or other non-ETH means, and are credited in the `RelayRecipient` balance sheet. - Users can never cost the dapp more than they were credited for. - * A dapp can provide off-chain a signed message called `approval` to a transaction sender and validate it. - * Whitelist of known transactions used for onboarding new users. This allows certain anonymous calls and is subject to Sybil attacks. - Therefore it should be combined with a restricted gasPrice, and a whitelist of trusted relays, to reduce the incentive for relays to create bogus transactions and rob the dapp's prepaid gas deposit. - Dapps allowing anonymous onboarding transactions might benefit from registering their own `Relay` and accepting anonymous transactions only from that `Relay`, whereas other transactions can be accepted from any relay. - Alternatively, dapps may use the balance sheet method for onboarding as well, by applying the methods suggested in the attacks/mitigations section below. -* Implement `preRelayedCall(address relay, address from, bytes memory encodedFunction, uint transactionFee) returns (bytes32)`. This method is called before a transaction is relayed. By default, it does nothing. - -* Implement `postRelayedCall(ddress relay, address from, bytes memory encodedFunction, bool success, uint usedGas, uint transactionFee, bytes32 preRetVal)`. This method is called after a transaction is relayed. By default, it does nothing. - - These two methods can be used to charge the user in dapp-specific manner. - -Glossary of terms used in the processes below: - -* `RelayHub` - the RelayHub singleton contract, used by everyone. -* `Recipient` - a contract implementing `RelayRecipient`, accepting relayed transactions from the RelayHub contract and paying for the incoming transactions. -* `Sender` - an external address with a valid key pair but no ETH to pay for gas. -* `Relay` - a node holding ETH in an external address, listed in RelayHub and relaying transactions from Senders to RelayHub for a fee. - -![Sequence Diagram](../assets/eip-1613/sequence.png) - -The process of registering/refreshing a `Relay`: - -* Relay starts listening as a web app (or on some other communication channel). -* If starting for the first time (no key yet), generate a key pair for Relay's address. -* If Relay's address doesn't hold sufficient funds for gas (e.g. because it was just generated), Relay stays inactive until its owner funds it. -* Relay's owner funds it. -* Relay's owner sends the required stake to `RelayHub` by calling `RelayHub.stake(address relay, uint unstakeDelay)`. -* `RelayHub` puts the `owner` and `unstake delay` in the relays map, indexed by `relay` address. -* Relay calls `RelayHub.registerRelay(uint transactionFee, string memory url)` with the relay's `transaction fee` (as a multiplier on transaction gas cost), and a URL for incoming transactions. -* `RelayHub` ensures that Relay has a sufficient stake. -* `RelayHub` puts the `transaction fee` in the relays map. -* `RelayHub` emits an event, `RelayAdded(Relay, owner, transactionFee, relayStake, unstakeDelay, url)`. -* Relay starts a timer to perform a `keepalive` transaction every 6000 blocks. -* `Relay` goes to sleep and waits for signing requests. - -The process of sending a relayed transaction: - -* `Sender` selects a live `Relay` from RelayHub's list by looking at `RelayAdded` events from `RelayHub`, and sorting based on its own criteria. Selection may be based on a mix of: - * Relay published transaction fees. - * Relay stake size and lock-up time. - * Recent relay transactions (visible through `TransactionRelayed` events from `RelayHub`). - * Optionally, reputation/blacklist/whitelist held by the sender app itself, or its backend, on per-app basis (not part of the gas stations network). -* Sender prepares the transaction with Sender's address, the recipient address, the actual transaction data, Relay's transaction fee, gas price, gas limit, its current nonce from `RelayHub.nonces`, RelayHub's address, and Relay's address, and then signs it. -* Sender verifies that `RelayHub.balances[recipient]` holds enough ETH to pay Relay's fee. -* Sender verifies that `Relay.balance` has enough eth to send the transaction -* Sender reads the Relay's current `nonce` value and decides on the `max_nonce` parameter. -* Sender sends the signed transaction amd metadata to Relay's web interface. -* `Relay` wraps the transaction with a transaction to `RelayHub`, with zero ETH value. -* `Relay` signs the wrapper transaction with its key in order to pay for gas. -* `Relay` verifies that: - * The transaction's recipient contract will accept this transaction when submitted, by calling `RelayHub.canRelay()`, a view function, - which checks the recipient's `acceptRelayedCall`, also a view function, stating whether it's willing to accept the charges). - * The transaction nonce matches `RelayHub.nonces[sender]`. - * The relay address in the transaction matches Relay's address. - * The transaction's recipient has enough ETH deposited in `RelayHub` to pay the transaction fee. - * Relay has enough ETH to pay for the gas required by the transaction. - * Value of `max_nonce` is higher than current Relay's `nonce` -* If any of Relay's checks fail, it returns an error to sender, and doesn't proceed. -* Relay submits the signed wrapped transaction to the blockchain. -* Relay immediately returns the signed wrapped transaction to the sender. This step is discussed below, in attacks/mitigations. -* `Sender` receives the wrapped transaction and verifies that: - * It's a valid relay call to `RelayHub`. from Relay's address. - * The transaction's ethereum nonce matches Relay's current nonce. - * The transaction's ethereum nonce is lower than or equal to `max_nonce`. - * `Relay` is sufficiently funded to pay for it. - * The wrapped transaction is valid and signed by `sender`. - * Recipient contract has sufficient funds in `RelayHub.balances` to pay for Relay's fee as stated in the transaction. -* If any of sender's checks fails, it goes back to selecting a new Relay. Sender may also file a report on the unresponsive relay to its backend or save it locally, to down-sort this relay in future transactions. -* `Sender` may also submit the raw wrapped transaction to the blockchain without paying for gas, through any Ethereum node. - This submission is likely ignored because an identical transaction is already in the network's pending transactions, but no harm in putting it twice, to ensure that it happens. - This step is not strictly necessary, for reasons discussed below in attacks/mitigations, but may speed things up. -* `Sender` monitors the blockchain, waiting for the transaction to be mined. - The transaction was verified, with Relay's current nonce, so mining must be successful unless Relay submitted another (different) transaction with the same nonce. - If mining fails due to such attack, sender may call `RelayHub.penalizeRepeatedNonce` through another relay, to collect his reward and burn the remainder of the offending relay's stake, and then go back to selecting a new Relay for the transaction. - See discussion in the attacks/mitigations section below. -* `RelayHub` receives the transaction: - * Records `gasleft()` as `initialGas` for later payment. - * Verifies the transaction is sent from a registered relay. - * Verifies that the signature of the internal transaction matches its stated origin (sender's key). - * Verifies that the relay address written in the transaction matches msg.sender. - * Verifies that the transaction's `nonce` matches the stated origin's nonce in `RelayHub.nonces`. - * Calls recipient's `acceptRelayedCall` function, asking whether it's going to accept the transaction. If not, the `TransactionRelayed` will be emitted with status `CanRelayFailed`, and `chargeOrCanRelayStatus` will contain the return value of `acceptRelayedCall`. In this case, Relay doesn't get paid, as it was its responsibility to check `RelayHub.canRelay` before releasing the transaction. - * Calls recipient's `preRelayedCall` function. If this call reverts the `TransactionRelayed` will be emitted with status `PreRelayedFailed`. - * Sends the transaction to the recipient. If this call reverts the `TransactionRelayed` will be emitted with status `RelayedCallFailed`. - When passing gas to `call()`, enough gas is preserved by `RelayHub`, for post-call handling. Recipient may run out of gas, but `RelayHub` never does. - `RelayHub` also sends sender's address at the end of `msg.data`, so `RelayRecipient.getSender()` will be able to extract the real sender, and trust it because the transaction came from the known `RelayHub` address. -* Recipient contract handles the transaction. -* `RelayHub` calls recipient's `postRelayedCall`. -* `RelayHub` checks call's return value of call, and emits `TransactionRelayed(address relay, address from, address to, bytes4 selector, uint256 status, uint256 chargeOrCanRelayStatus)`. -* `RelayHub` increases `RelayHub.nonces[sender]`. -* `RelayHub` transfers ETH balance from recipient to `Relay.owner`, to pay the transaction fee, based on the measured transaction cost. - Note on relay payment: The relay gets paid for actual gas used, regardless of whether the recipient reverted. - The only case where the relay sustains a loss, is if `canRelay` returns non-zero, since the relay was responsible to verify this view function prior to submitting. - Any other revert is caught and paid for. See attacks/mitigations below. -* `Relay` keeps track of transactions it sent, and waits for `TransactionRelayed` events to see the charge. - If a transaction reverts and goes unpaid, which means the recipient's `acceptRelayedCall()` function was inconsistent, `Relay` refuses service to that recipient for a while (or blacklists it indefinitely, if it happens often). - See attacks/mitigations below. - -The process of winding a `Relay` down: - -* Relay's owner (the address that initially funded it) calls `RelayHub.removeRelayByOwner(Relay)`. -* `RelayHub` ensures that the sender is indeed Relay's owner, then removes `Relay`, and emits `RelayRemoved(Relay)`. -* `RelayHub` starts the countdown towards releasing the owner's stake. -* `Relay` receives its `RelayRemoved` event. -* `Relay` sends all its remaining ETH to its owner. -* `Relay` shuts down. -* Once the owner's unstake delay is over, owner calls `RelayHub.unstake()`, and withdraws the stake. - -## Rationale -The rationale for the gas stations network design is a combination of two sets of requirements: Easy adoption, and robustness. - -For easy adoption, the design goals are: - -* No network changes. -* Minimal changes to contracts, apps and frameworks. - -The robustness requirement translates to decentralization and attack resistance. The gas stations network is decentralized, and we have to assume that any entity may attack other entities in the system. - -Specifically we've considered the following types of attacks: - -* Denial-of-service attacks against individual senders, i.e. transactions censorship. -* Denial-of-service and financial attacks against individual relays. -* Denial-of-service and financial attacks against individual contracts. -* Denial-of-service attacks against the entire network, either by attacking existing entities, or by introducing any number of malicious entities. - -#### Attacks and mitigations - -##### Attack: Relay attempts to censor a transaction by not signing it, or otherwise ignoring a user request. -Relay is expected to return the signed transaction to the sender, immediately. -Sender doesn't need to wait for the transaction to be mined, and knows immediately whether it's request has been served. -If a relay doesn't return a signed transaction within a couple of seconds, sender cancels the operation, drops the connection, and switches to another relay. -It also marks Relay as unresponsive in its private storage to avoid using it in the near future. - -Therefore, the maximal damage a relay can cause with such attack, is a one-time delay of a couple of seconds. After a while, senders will avoid it altogether. - -##### Attack: Relay attempts to censor a transaction by signing it, returning it to the sender, but never putting it on the blockchain. -This attack will backfire and not censor the transaction. -The sender can submit the transaction signed by Relay to the blockchain as a raw transaction through any node, so the transaction does happen, -but Relay may be unaware and therefore be stuck with a bad nonce which will break its next transaction. - -##### Attack: Relay attempts to censor a transaction by signing it, but publishing a different transaction with the same nonce. -Reusing the nonce is the only DoS performed by a Relay, that cannot be detected within a couple of seconds during the http request. -It will only be detected when the malicious transaction with the same nonce gets mined and triggers the `RelayHub.TransactionRelayed` event. -However, the attack will backfire and cost Relay its entire stake. - -Sender has a signed transaction from Relay with nonce N, and also gets a mined transaction from the blockchain with nonce N, also signed by Relay. -This proves that Relay performed a DoS attack against the sender. -The sender calls `RelayHub.penalizeRepeatedNonce(bytes transaction1, bytes transaction2)`, which verifies the attack, confiscates Relay's stake, -and sends half of it to the sender who delivered the `penalizeRepeatedNonce` call. The other half of the stake is burned by sending it to `address(0)`. Burning is done to prevent cheating relays from effectively penalizing themselves and getting away without any loss. -The sender then proceeds to select a new relay and send the original transaction. - -The result of such attack is a delay of a few blocks in sending the transaction (until the attack is detected) but the relay gets removed and loses its entire stake. -Scaling such attack would be prohibitively expensive, and actually quite profitable for senders and honest relays. - -##### Attack: Relay attempts to censor a transaction by signing it, but using a nonce higher than it's current nonce. -In this attack, the Relay did create and return a perfectly valid transaction, but it will not be mined until this Relay fills the gap in the nonce with 'missing' transactions. -This may delay the relaying of some transactions indefinitely. In order to mitigate that, the sender includes a `max_nonce` parameter with it's signing request. -It is suggested to be higher by 2-3 from current nonce, to allow the relay process several transactions. - -When the sender receives a transaction signed by a Relay he validates that the nonce used is valid, and if it is not, the client will ignore the given relay and use other relays to relay given transaction. Therefore, there will be no actual delay introduced by such attack. - -##### Attack: Dapp attempts to burn relays funds by implementing an inconsistent acceptRelayedCall() and using multiple sender addresses to generate expensive transactions, thus performing a DoS attack on relays and reducing their profitability. -In this attack, a contract sets an inconsistent acceptRelayedCall (e.g. return zero for even blocks, nonzero for odd blocks), and uses it to exhaust relay resources through unpaid transactions. -Relays can easily detect it after the fact. -If a transaction goes unpaid, the relay knows that the recipient contract's acceptRelayedCall has acted inconsistently, because the relay has verified its view function before sending the transaction. -It might be the result of a rare race condition where the contract's state has changed between the view call and the transaction, but if it happens too frequently, relays will blacklist this contract and refuse to serve transactions to it. -Each offending contract can only cause a small damage (e.g. the cost of 2-3 transactions) to a relay, before getting blacklisted. - -Relays may also look at recipients' history on the blockchain, looking for past unpaid transactions (reverted by RelayHub without pay), and denying service to contracts with a high failure rate. -If a contract caused this minor loss to a few relays, all relays will stop serving it, so it can't cause further damage. - -This attack doesn't scale because the cost of creating a malicious contract is in the same order of magnitude as the damage it can cause to the network. -Causing enough damage to exhaust the resources of all relays, would be prohibitively expensive. - -The attack can be made even more impractical by setting RelayHub to require a stake from dapps before they can be served, and enforcing an unstaking delay, -so that attackers will have to raise a vast amount of ETH in order to simultaneously create enough malicious contracts and attack relays. -This protection is probably an overkill, since the attack doesn't scale regardless. - -##### Attack: User attempts to rob dapps by registering its own relay and sending expensive transactions to dapps. -If a malicious sender repeatedly abuses a recipient by sending meaningless/reverted transactions and causing the recipient to pay a relay for nothing, -it is the recipient's responsibility to blacklist that sender and have its acceptRelayedCall function return nonzero for that sender. -Collect calls are generally not meant for anonymous senders unknown to the recipient. -Dapps that utilize the gas station networks should have a way to blacklist malicious users in their system and prevent Sybil attacks. - -A simple method that mitigates such Sybil attack, is that the dapp lets users buy credit with a credit card, and credit their account in the dapp contract, -so acceptRelayedCall() only returns zero for users that have enough credit, and deduct the amount paid to the relay from the user's balance, whenever a transaction is relayed for the user. -With this method, the attacker can only burn its own resources, not the dapp's. - -A variation of this method, for free dapps (that don't charge the user, and prefer to pay for their users transactions) is to require a captcha during user creation in their web interface, -or to login with a Google/Facebook account, which limits the rate of the attack to the attacker's ability to open many Google/Facebook accounts. -Only a user that passed that process is given credit in RelayRecipient. The rate of such Sybil attack would be too low to cause any real damage. - -##### Attack: Attacker attempts to reduce network availability by registering many unreliable relays. -Registering a relay requires placing a stake in RelayHub, and the stake can only be withdrawn after the relay is unregistered and a long cooldown period has passed, e.g. a month. - -Each unreliable relay can only cause a couple of seconds delay to senders, once, and then it gets blacklisted by them, as described in the first attack above. -After it caused this minor delay and got blacklisted, the attacker must wait a month before reusing the funds to launch another unreliable relay. -Simultaneously bringing up a number of unreliable relays, large enough to cause a noticeable network delay, would be prohibitively expensive due to the required stake, -and even then, all those relays will get blacklisted within a short time. - -##### Attack: Attacker attempts to replay a relayed transaction. -Transactions include a nonce. RelayHub maintains a nonce (counter) for each sender. Transactions with bad nonces get reverted by RelayHub. Each transaction can only be relayed once. - -##### Attack: User does not execute the raw transaction received from the Relayer, therefore blocking the execution of all further transactions signed by this relayer -The user doesn't really have to execute the raw transaction. It's enough that the user can. The relationship between relay and sender is mutual distrust. The process described above incentivizes the relay to execute the transaction, so the user doesn't need to wait for actual mining to know that the transaction has been executed. - -Once relay returns the signed transaction, which should happen immediately, the relay is incentivized to also execute it on chain, so that it can advance its nonce and serve the next transaction. The user can (but doesn't have to) also execute the transaction. To understand why the attack isn't viable, consider the four possible scenarios after the signed transaction was returned to the sender: - -1. Relay executes the transaction, and the user doesn't. In this scenario the transaction is executed, so no problem. This is the case described in this attack. -2. Relay doesn't execute the transaction, but the user does. Similarly to 1, the transaction is executed, so no problem. -3. Both of them execute the transaction. The transactions are identical in the pending transactions pool, so the transaction gets executed once. No problem. -4. None of them execute the transaction. In this case the transaction doesn't get executed, but the relay is stuck. It can't serve the next transaction with the next nonce, because its nonce hasn't been advanced on-chain. It also can't serve the next transaction with the current nonce, as this can be proven by the user, having two different transactions signed by the same relay, with the same nonce. The user could use this to take the relay's nonce. So the relay is stuck unless it executes the transaction. - -As this matrix shows, the relay is __always__ incentivized to execute the transaction, once it returned it to the user, in order to end up in #1 or #3, and avoid the risk of #4. It's just a way to commit the relay to do its work, without requiring the user to wait for on-chain confirmation. - -## Backwards Compatibility - -The gas stations network is implemented as smart contracts and external entities, and does not require any network changes. - -Dapps adding gas station network support remain backwards compatible with their existing apps/users. The added methods apply on top of the existing ones, so no changes are required for existing apps. - -## Implementation - -A working implementation of the [**gas stations network**](https://github.com/tabookey-dev/tabookey-gasless) is being developed by **TabooKey**. It consists of `RelayHub`, `RelayRecipient`, `web3 hooks`, an implementation of a gas station inside `geth`, and sample dapps using the gas stations network. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1613.md diff --git a/EIPS/eip-1616.md b/EIPS/eip-1616.md index 52107d4a0a95cd..3bece945cabb7f 100644 --- a/EIPS/eip-1616.md +++ b/EIPS/eip-1616.md @@ -1,387 +1 @@ ---- -eip: 1616 -title: Attribute Registry Standard -author: 0age (@0age), Santiago Palladino (@spalladino), Leo Arias (@elopio), Alejo Salles (@fiiiu), Stephane Gosselin (@thegostep) -discussions-to: https://github.com/ethereum/EIPs/issues/1616 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-11-23 -requires: 165 ---- - - -## Simple Summary -EIP-1616 provides a basic interface for querying a registry for attribute metadata assigned to Ethereum accounts. - -## Abstract -This EIP contains the following core ideas: -1. Instead of relying directly on the reputation of a claims issuer to assess the veracity of a given claim, trust can be brought up to the level of a registry curator. This registry which we call an "**Attribute Registry**" allows for reduced complexity in implementation since a party needing to verify an attribute can now work with a trusted claims aggregator instead of relying on individual claim providers. -2. Claims are abstracted as standard "attributes" which represent metadata assigned to an account, with claims decoupled from the issuing party. Attributes are registered as a flat `uint256 -> uint256` key-value pair on each account, with the important property that **each attribute type has one canonical value per address**. This property allows for composability of attribute registries and advanced attribute formation. -3. There is a generic method for determining the set of attribute keys or IDs made available by the registry. The standard does not specify requirements or recommendations for how attributes and their values are managed, or what additional metadata may be associated with attributes. It is likely that a standard set of attribute names and metadata schema could be proposed in a separate EIP. - -Potential advanced uses of attribute registries include: -* Encoding complex boolean expressions which combine multiple attributes into a single uint256 key, which is then parsed and evaluated by the registry logic. -* Using values associated with an attribute to query additional on-chain or off-chain metadata. -* Resolving attribute values by calling into separate attribute registries or other contracts, delegating authority without changing the interface of the registry. - -## Motivation -This EIP is motivated by the need for contracts and external accounts to be able to verify information about a given address from a single trusted source **without concerning themselves with the particular details of how the information was obtained**, and to do so in as simple a manner as possible. It is also motivated by the desire to promote broad **cross-compatibility and composability** between attribute registries, a property which is amplified by both the simplicity of the interface as well as by the guarantees on uniqueness provided by the proposed standard. - -Existing EIPs for assigning metadata to an account include EIP-735 and EIP-780, which both allow for multiple claims to be issued on the same address for any given claim topic. This forces verifiers of said metadata to assess the veracity of each claim, taking into account the relative reputation of each claim issuer. It also prescribes a methodology for adding and removing claims, which may not be appropriate for all use cases. - -This EIP proposes a light-weight abstraction layer for a standard account metadata registry interface. This abstraction layer can sit on top of claims registries like EIP-735 and EIP-780 or others as the attribute registry curator selects trusted data sources. - -## Specification -The Attribute Registry interface contains four functions, outlined as follows: -```solidity -/** - * @title EIP-1616 Attribute Registry Standard interface. EIP-165 ID: 0x5f46473f - */ -interface AttributeRegistryInterface { - function hasAttribute(address account, uint256 attributeTypeID) external view returns (bool); - function getAttributeValue(address account, uint256 attributeTypeID) external view returns (uint256); - function countAttributeTypes() external view returns (uint256); - function getAttributeTypeID(uint256 index) external view returns (uint256); -} -``` - -Contracts that comply with the Attribute Registry EIP MUST implement the above interface. - -As an additional requirement, the ERC-165 interface MUST be included: -```solidity -/** - * @title EIP-165 interface. EIP-165 ID: 0x01ffc9a7 - */ -interface EIP-165 { - /** - * @notice EIP-165 support. Attribute Registry interface ID is 0x5f46473f. - * @param _interfaceID The interface identifier, as specified in EIP-165 - * @return True for 0x01ffc9a7 & 0x5f46473f, false for unsupported interfaces. - */ - function supportsInterface(bytes4 _interfaceID) external view returns (bool); -} -``` - -The implementation MUST follow the specifications described below. - -### View Functions -The view functions detailed below MUST be implemented. - -#### `hasAttribute` function -```solidity -function hasAttribute(address account, uint256 attributeTypeID) external view returns (bool) -``` - -Check if an attribute has been assigned to a given account on the registry and is currently valid. - -_**NOTE**_: This function MUST return either true or false - i.e. calling this function MUST NOT cause the caller to revert. Implementations that wish to call into another contract during execution of this function MUST catch any `revert` and instead return `false`. - -_**NOTE**_: This function MUST return two equal values when performing two directly consecutive function calls with identical `account` and `attributeTypeID` parameters, regardless of differences in the caller's address, the transaction origin, or other out-of-band information. - - - -#### `getAttributeValue` function -```solidity -function getAttributeValue(address account, uint256 attributeTypeID) external view returns (uint256) -``` - -Retrieve the `uint256` value of an attribute on a given account on the registry, assuming the attribute is currently valid. - -_**NOTE**_: This function MUST revert if a directly preceding or subsequent function call to `hasAttribute` with identical `account` and `attributeTypeID` parameters would return false. - -_**NOTE**_: This function MUST return two equal values when performing two directly consecutive function calls with identical `account` and `attributeTypeID` parameters, regardless of differences in the caller's address, the transaction origin, or other out-of-band information. - -#### `countAttributeTypes` function -```solidity -function countAttributeTypes() external view returns (uint256) -``` - -Retrieve the total number of valid attribute types defined on the registry. Used alongside `getAttributeTypeID` to determine all of the attribute types that are available on the registry. - -_**NOTE**_: This function MUST return a positive integer value - i.e. calling this function MUST NOT cause the caller to revert. - -_**NOTE**_: This function MUST return a value that encompasses all indexes of attribute type IDs whereby a call to `hasAttribute` on some address with an attribute type ID at the given index would return `true`. - -#### `getAttributeTypeID` function -```solidity -function getAttributeTypeID(uint256 index) external view returns (uint256) -``` - -Retrieve an ID of an attribute type defined on the registry by index. Used alongside `countAttributeTypes` to determine all of the attribute types that are available on the registry. - -_**NOTE**_: This function MUST revert if the provided `index` value falls outside of the range of the value returned from a directly preceding or subsequent function call to `countAttributeTypes`. It MUST NOT revert if the provided `index` value falls inside said range. - -_**NOTE**_: This function MUST return an `attributeTypeID` value on *some* index if the same `attributeTypeID` value would cause a given call to `hasAttribute` to return `true` when passed as a parameter. - -## Rationale -This standard extends the applicability of metadata assignment to those use cases that are not adequately represented by EIP-735, EIP-780, or similar proposals. Namely, it enforces the constraint of one attribute value per attribute ID per address, as opposed to one value per ID per address *per issuer*. - -Aside from the prescribed attribute value, attribute properties are deliberately omitted from the standard. While many attribute registries will require additional metadata on attributes at both the instance and the class level, reliable and flexible interoperability between highly variable registry extensions is facilitated more effectively by enforcing a widely-applicable base layer for attributes. - -## Backwards Compatibility -There are no backwards compatibility concerns. - -## Test Cases -Targeted test cases with 100% code coverage can be found at [this repository](https://github.com/0age/AttributeRegistry). See [here](https://github.com/TPL-protocol/tpl-contracts) for tests on a more complex contract that implements the application registry interface. - -## Implementation -The basic implementation that follows can be found at [this repository](https://github.com/0age/AttributeRegistry) (see [here](https://github.com/TPL-protocol/tpl-contracts/blob/master/contracts/BasicJurisdiction.sol#L399) for an example of a more complex implementing contract): - -```solidity -pragma solidity ^0.4.25; - -/** - * @title Attribute Registry interface. EIP-165 ID: 0x5f46473f - */ -interface AttributeRegistryInterface { - /** - * @notice Check if an attribute of the type with ID `attributeTypeID` has - * been assigned to the account at `account` and is currently valid. - * @param account address The account to check for a valid attribute. - * @param attributeTypeID uint256 The ID of the attribute type to check for. - * @return True if the attribute is assigned and valid, false otherwise. - * @dev This function MUST return either true or false - i.e. calling this - * function MUST NOT cause the caller to revert. - */ - function hasAttribute( - address account, - uint256 attributeTypeID - ) external view returns (bool); - - /** - * @notice Retrieve the value of the attribute of the type with ID - * `attributeTypeID` on the account at `account`, assuming it is valid. - * @param account address The account to check for the given attribute value. - * @param attributeTypeID uint256 The ID of the attribute type to check for. - * @return The attribute value if the attribute is valid, reverts otherwise. - * @dev This function MUST revert if a directly preceding or subsequent - * function call to `hasAttribute` with identical `account` and - * `attributeTypeID` parameters would return false. - */ - function getAttributeValue( - address account, - uint256 attributeTypeID - ) external view returns (uint256); - - /** - * @notice Count the number of attribute types defined by the registry. - * @return The number of available attribute types. - * @dev This function MUST return a positive integer value - i.e. calling - * this function MUST NOT cause the caller to revert. - */ - function countAttributeTypes() external view returns (uint256); - - /** - * @notice Get the ID of the attribute type at index `index`. - * @param index uint256 The index of the attribute type in question. - * @return The ID of the attribute type. - * @dev This function MUST revert if the provided `index` value falls outside - * of the range of the value returned from a directly preceding or subsequent - * function call to `countAttributeTypes`. It MUST NOT revert if the provided - * `index` value falls inside said range. - */ - function getAttributeTypeID(uint256 index) external view returns (uint256); -} - - -/** - * @title A simple example of an Attribute Registry implementation. - */ -contract AttributeRegistry is AttributeRegistryInterface { - // This particular implementation just defines two attribute types. - enum Affiliation { Whitehat, Blackhat } - - // Top-level information about attribute types held in a static array. - uint256[2] private _attributeTypeIDs; - - // The number of attributes currently issued tracked in a static array. - uint256[2] private _issuedAttributeCounters; - - // Issued attributes held in a nested mapping by account & attribute type. - mapping(address => mapping(uint256 => bool)) private _issuedAttributes; - - // Issued attribute values held in a nested mapping by account & type. - mapping(address => mapping(uint256 => uint256)) private _issuedAttributeValues; - - /** - * @notice The constructor function, defines the two attribute types available - * on this particular registry. - */ - constructor() public { - // Set the attribute type IDs for whitehats (8008) and blackhats (1337). - _attributeTypeIDs = [8008, 1337]; - } - - /** - * @notice Assign a "whitehat" attribute type to `msg.sender`. - * @dev The function may not be called by accounts with a "blackhat" attribute - * type already assigned. This function is arbitrary and not part of the - * Attribute Registry specification. - */ - function joinWhitehats() external { - // Get the index of the blackhat attribute type on the attribute registry. - uint256 blackhatIndex = uint256(Affiliation.Blackhat); - - // Get the attribute type ID of the blackhat attribute type. - uint256 blackhatAttributeTypeID = _attributeTypeIDs[blackhatIndex]; - - // Do not allow the whitehat attribute to be set if blackhat is already set. - require( - !_issuedAttributes[msg.sender][blackhatAttributeTypeID], - "no blackhats allowed!" - ); - - // Get the index of the whitehat attribute type on the attribute registry. - uint256 whitehatIndex = uint256(Affiliation.Whitehat); - - // Get the attribute type ID of the whitehat attribute type. - uint256 whitehatAttributeTypeID = _attributeTypeIDs[whitehatIndex]; - - // Mark the attribute as issued on the given address. - _issuedAttributes[msg.sender][whitehatAttributeTypeID] = true; - - // Calculate the new number of total whitehat attributes. - uint256 incrementCounter = _issuedAttributeCounters[whitehatIndex] + 1; - - // Set the attribute value to the new total assigned whitehat attributes. - _issuedAttributeValues[msg.sender][whitehatAttributeTypeID] = incrementCounter; - - // Update the value of the counter for total whitehat attributes. - _issuedAttributeCounters[whitehatIndex] = incrementCounter; - } - - /** - * @notice Assign a "blackhat" attribute type to `msg.sender`. - * @dev The function may be called by any account, but assigned "whitehat" - * attributes will be removed. This function is arbitrary and not part of the - * Attribute Registry specification. - */ - function joinBlackhats() external { - // Get the index of the blackhat attribute type on the attribute registry. - uint256 blackhatIndex = uint256(Affiliation.Blackhat); - - // Get the attribute type ID of the blackhat attribute type. - uint256 blackhatAttributeTypeID = _attributeTypeIDs[blackhatIndex]; - - // Mark the attribute as issued on the given address. - _issuedAttributes[msg.sender][blackhatAttributeTypeID] = true; - - // Calculate the new number of total blackhat attributes. - uint256 incrementCounter = _issuedAttributeCounters[blackhatIndex] + 1; - - // Set the attribute value to the new total assigned blackhat attributes. - _issuedAttributeValues[msg.sender][blackhatAttributeTypeID] = incrementCounter; - - // Update the value of the counter for total blackhat attributes. - _issuedAttributeCounters[blackhatIndex] = incrementCounter; - - // Get the index of the whitehat attribute type on the attribute registry. - uint256 whitehatIndex = uint256(Affiliation.Whitehat); - - // Get the attribute type ID of the whitehat attribute type. - uint256 whitehatAttributeTypeID = _attributeTypeIDs[whitehatIndex]; - - // Determine if a whitehat attribute type has been assigned. - if (_issuedAttributes[msg.sender][whitehatAttributeTypeID]) { - // If so, delete the attribute. - delete _issuedAttributes[msg.sender][whitehatAttributeTypeID]; - - // Delete the attribute value as well. - delete _issuedAttributeValues[msg.sender][whitehatAttributeTypeID]; - - // Set the attribute value to the new total assigned whitehat attributes. - uint256 decrementCounter = _issuedAttributeCounters[whitehatIndex] - 1; - - // Update the value of the counter for total whitehat attributes. - _issuedAttributeCounters[whitehatIndex] = decrementCounter; - } - } - - /** - * @notice Get the total number of assigned whitehat and blackhat attributes. - * @return Array with counts of assigned whitehat and blackhat attributes. - * @dev This function is arbitrary and not part of the Attribute Registry - * specification. - */ - function totalHats() external view returns (uint256[2]) { - // Return the array containing counter values. - return _issuedAttributeCounters; - } - - /** - * @notice Check if an attribute of the type with ID `attributeTypeID` has - * been assigned to the account at `account` and is currently valid. - * @param account address The account to check for a valid attribute. - * @param attributeTypeID uint256 The ID of the attribute type to check for. - * @return True if the attribute is assigned and valid, false otherwise. - * @dev This function MUST return either true or false - i.e. calling this - * function MUST NOT cause the caller to revert. - */ - function hasAttribute( - address account, - uint256 attributeTypeID - ) external view returns (bool) { - // Return assignment status of attribute by account and attribute type ID - return _issuedAttributes[account][attributeTypeID]; - } - - /** - * @notice Retrieve the value of the attribute of the type with ID - * `attributeTypeID` on the account at `account`, assuming it is valid. - * @param account address The account to check for the given attribute value. - * @param attributeTypeID uint256 The ID of the attribute type to check for. - * @return The attribute value if the attribute is valid, reverts otherwise. - * @dev This function MUST revert if a directly preceding or subsequent - * function call to `hasAttribute` with identical `account` and - * `attributeTypeID` parameters would return false. - */ - function getAttributeValue( - address account, - uint256 attributeTypeID - ) external view returns (uint256 value) { - // Revert if attribute with given account & attribute type ID is unassigned - require( - _issuedAttributes[account][attributeTypeID], - "could not find a value with the provided account and attribute type ID" - ); - - // Return the attribute value. - return _issuedAttributeValues[account][attributeTypeID]; - } - - /** - * @notice Count the number of attribute types defined by the registry. - * @return The number of available attribute types. - * @dev This function MUST return a positive integer value - i.e. calling - * this function MUST NOT cause the caller to revert. - */ - function countAttributeTypes() external view returns (uint256) { - // Return the length of the attribute type IDs array. - return _attributeTypeIDs.length; - } - - /** - * @notice Get the ID of the attribute type at index `index`. - * @param index uint256 The index of the attribute type in question. - * @return The ID of the attribute type. - * @dev This function MUST revert if the provided `index` value falls outside - * of the range of the value returned from a directly preceding or subsequent - * function call to `countAttributeTypes`. It MUST NOT revert if the provided - * `index` value falls inside said range. - */ - function getAttributeTypeID(uint256 index) external view returns (uint256) { - // Revert if the provided index is out of range. - require( - index < _attributeTypeIDs.length, - "provided index is outside of the range of defined attribute type IDs" - ); - - // Return the attribute type ID at the given index in the array. - return _attributeTypeIDs[index]; - } -} -``` - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1616.md diff --git a/EIPS/eip-162.md b/EIPS/eip-162.md index 03764438559841..82c88e7b08b6d4 100644 --- a/EIPS/eip-162.md +++ b/EIPS/eip-162.md @@ -1,248 +1 @@ ---- -eip: 162 -title: Initial ENS Hash Registrar -author: Maurelian, Nick Johnson , Alex Van de Sande -status: Final -type: Standards Track -category: ERC -created: 2016-10-25 ---- - -## Contents -- Abstract -- Motivations -- Specification - - Initial restrictions - - Name format for hash registration - - Auctioning names - - Deeds - - Deployment and Upgrade process - - Registrar Interface -- Rationale - - Not committing to a permanent registrar at the outset - - Valid names >= 7 characters - - Restricting TLD to `.eth` - - Holding ether as collateral -- Prior work - - - -## Abstract - -This ERC describes the implementation, as deployed to the main ethereum network on 2017-05-04, of a registrar contract to govern the allocation of names in the Ethereum Name Service (ENS). The corresponding source code is [here](https://github.com/ethereum/ens/blob/mainnet/contracts/HashRegistrarSimplified.sol). - -For more background, refer to [EIP-137](./eip-137.md). - -> Registrars are responsible for allocating domain names to users of the system, and are the only entities capable of updating the ENS; the owner of a node in the ENS registry is its registrar. Registrars may be contracts or externally owned accounts, though it is expected that the root and top-level registrars, at a minimum, will be implemented as contracts. -> -> \- EIP 137 - -A well designed and governed registrar is essential to the success of the ENS described in EIP 137, but is described separately in this document as it is external to the core ENS protocol. - -In order to maximize utility and adoption of a new namespace, the registrar should mitigate speculation and "name squatting", however the best approach for mitigation is unclear. Thus an "initial" registrar is proposed, which implements a simple approach to name allocation. During the initial period, the available namespace will be significantly restricted to the `.eth` top level domain, and subdomain shorter than 7 characters in length disallowed. This specification largely describes @alexvandesande and @arachnid's [hash registrar implementation](https://github.com/ethereum/ens/blob/mainnet/contracts/HashRegistrarSimplified.sol) in order to facilitate discussion. - -The intent is to replace the Initial Registrar contract with a permanent registrar contract. The Permanent Registrar will increase the available namespace, and incorporate lessons learned from the performance of the Initial Registrar. This upgrade is expected to take place within approximately 2 years of initial deployment. - -## Motivations - -The following factors should be considered in order to optimize for adoption of the ENS, and good governance of the Initial Registrar's namespace. - -**Upgradability:** The Initial Registrar should be safely upgradeable, so that knowledge gained during its deployment can be used to replace it with an improved and permanent registrar. - -**Effective allocation:** Newly released namespaces often create a land grab situation, resulting in many potentially valuable names being purchased but unused, with the hope of re-selling at a profit. This reduces the availability of the most useful names, in turn decreasing the utility of the name service to end users. - -Achieving an effective allocation may or may not require human intervention for dispute resolution and other forms of curation. The Initial Registrar should not aim to create to most effective possible allocation, but instead limit the cost of misallocation in the long term. - -**Security:** The registrar will hold a balance of ether without an explicit limit. It must be designed securely. - -**Simplicity:** The ENS specification itself emphasizes a separation of concerns, allowing the most essential element, the registry to be as simple as possible. The interim registrar in turn should be as simple as possible while still meeting its other design goals. - -**Adoption:** Successful standards become more successful due to network effects. The registrar should consider what strategies will encourage the adoption of the ENS in general, and the namespace it controls in particular. - -## Specification - -### Initial restrictions - -The Initial Registrar is expected to be in service for approximately two years, prior to upgrading. This should be sufficient time to learn, observe, and design an updated system. - -During the initial two year period, the available name space will be restricted to the `.eth` TLD. - -This restriction is enforced by the owner of the ENS root node who should not assign any nodes other than `.eth` to the Initial Registrar. The ENS's root node should be controlled by multiple parties using a multisig contract. - -The Initial Registrar will also prohibit registration of names 6 characters or less in length. - -### Name format for hash registration - -Names submitted to the initial registrar must be hashed using Ethereum's sha3 function. Note that the hashes submitted to the registrar are the hash of the subdomain label being registered, not the namehash as defined in EIP 137. - -For example, in order to register `abcdefg.eth`, one should submit `sha3('abcdefg')`, not `sha3(sha3(0, 'eth'), 'abcdefg')`. - -### Auctioning names - -The registrar will allocate the available names through a Vickrey auction: - -> A Vickrey auction is a type of sealed-bid auction. Bidders submit written bids without knowing the bid of the other people in the auction. The highest bidder wins but the price paid is the second-highest bid. This type of auction... gives bidders an incentive to bid their true value. -> -> \- [Vickrey Auction, Wikipedia](https://en.wikipedia.org/wiki/Vickrey_auction) - -The auction lifecycle of a name has 5 possible states, or Modes. - -1. **Not-yet-available:** The majority of names will be initially unavailable for auction, and will become available some time during the 8 weeks after launch. -2. **Open:** The earliest availability for a name is determined by the most significant byte of its sha3 hash. `0x00` would become available immediately, `0xFF` would become available after 8 weeks, and the availability of other names is distributed accordingly. Once a name is available, it is possible to start an auction on it. -3. **Auction:** Once the auction for a name has begun, there is a 72 hour bidding period. Bidders must submit a payment of ether, along with sealed bids as a hash of `sha3(bytes32 hash, address owner, uint value, bytes32 salt)`. The bidder may obfuscate the true bid value by sending a greater amount of ether. -4. **Reveal:** After the bidding period, a 48 hour reveal period commences. During this time, bidders must reveal the true parameters of their sealed bid. As bids are revealed, ether payments are returned according to the schedule of "refund ratios" outlined in the table below. If no bids are revealed, the name will return to the Open state. -5. **Owned:** After the reveal period has finished, the winning bidder must submit a transaction to finalize the auction, which then calls the ENS's `setSubnodeOwner` function, recording the winning bidder's address as the owner of the hash of the name. - -The following table outlines important parameters which define the Registrar's auction mechanism. - -#### Registrar Parameters - -| Name | Description | Value | -|--------------------|----------------------------------------------------------------------------------------------------|------------| -| totalAuctionLength | The full time period from start of auction to end of the reveal period. | 5 days | -| revealPeriod | The length of the time period during which bidding is no longer allowed, and bids must be revealed. | 48 hours | -| launchLength | The time period during which all names will become available for auction. | 8 weeks | -| minPrice | The minimum amount of ether which must be locked up in exchange for ownership of a name. | 0.01 ether | - -### Deeds - -The Initial Registrar contract does not hold a balance itself. All ether sent to the Registrar will be held in a separate `Deed` contracts. A deed contract is first created and funded when a sealed bid is submitted. After an auction is completed and a hash is registered, the deed for the winning bid is held in exchange for ownership of the hash. Non-winning bids are refunded. - -A deed for an owned name may be transferred to another account by its owner, thus transferring ownership and control of the name. - -After 1 year of registration, the owner of a hash may choose to relinquish ownership and have the value of the deed returned to them. - -Deeds for non-winning bids can be closed by various methods, at which time any ether held will either be returned to the bidder, burnt, or sent to someone else as a reward for actions which help the registrar. - -The following table outlines what portion of the balance held in a deed contract will be returned upon closure, and to whom. The remaining balance will be burnt. - -#### Refund schedule - -| Reason for Deed closure | Refund Recipient | Refund Percentage | -| --- | --- | --- | -| A valid non-winning bid is revealed. | Bidder | 99.5% | -| A bid submitted after the auction period is revealed. | Bidder | 99.5% | -| An otherwise valid bid is revealed on an owned name. 1 | Bidder | 0.5% | -| An expired sealed bid is cancelled. 2 | Canceler | 0.5% | -| A registered hash is reported as invalid. 3 | Reporter | 50% | -| A registered hash is reported as invalid. 3 | Owner | 50% | - -##### Notes: - -1. This incentivizes all bids to be revealed in time. If bids could be revealed late, an extortion attack on the current highest bidder could be made by threatening to reveal a new second highest bid. -2. A bid which remains sealed after more than 2 weeks and 5 days may be cancelled by anyone to collect a small reward. -2. Since names are hashed before auctioning and registration, the Initial Registrar is unable to enforce character length restrictions independently. A reward is therefore provided for reporting invalid names. - -### Deployment and Upgrade process - -The Initial Registrar requires the ENS's address as a constructor, and should be deployed after the ENS. The multisig account owning the root node in the ENS should then set the Initial Registrar's address as owner of the `eth` node. - -The Initial Registrar is expected to be replaced by a Permanent Registrar approximately 2 years after deployment. The following process should be used for the upgrade: -1. The Permanent Registrar contract will be deployed. -2. The multisig account owning the root node in the ENS will assign ownership of the `.eth` node to the Permanent Registrar. -3. Owners of hashes in the Initial Registrar will be responsible for registering their deeds to the Permanent Registrar. A couple options are considered here: - 1. Require owners to transfer their ownership prior to a cutoff date in order to maintain ownership and/or continue name resolution services. - 2. Have the Permanent Registrar query the Initial Registrar for ownership if it is lacking an entry. - -### Planned deactivation - -In order to limit dependence on the Initial Registrar, new auctions will stop after 4 years, and all ether held in deeds after 8 years will become unreachable. - -### Registrar Interface - -`function state(bytes32 _hash) constant returns (Mode)` -- Implements a state machine returning the current state of a name - -`function entries(bytes32 _hash) constant returns (Mode, address, uint, uint, uint)` -- Returns the following information regarding a registered name: - * state - * deed address - * registration date - * balance of the deed - * highest value bid at auction - -`function getAllowedTime(bytes32 _hash) constant returns (uint timestamp)` -- Returns the time at which the hash will no longer be in the initial `not-yet-available` state. - -`function isAllowed(bytes32 _hash, uint _timestamp) constant returns (bool allowed)` -- Takes a hash and a time, returns true if and only if it has passed the initial `not-yet-available` state. - -`function startAuction(bytes32 _hash);` -- Moves the state of a hash from Open to Auction. Throws if state is not Open. - -`function startAuctions(bytes32[] _hashes);` -- Starts multiple auctions on an array of hashes. This enables someone to open up an auction for a number of dummy hashes when they are only really interested in bidding for one. This will increase the cost for an attacker to simply bid blindly on all new auctions. Dummy auctions that are open but not bid on are closed after a week. - -`function shaBid(bytes32 hash, address owner, uint value, bytes32 salt) constant returns (bytes32 sealedBid);` -- Takes the parameters of a bid, and returns the sealedBid hash value required to participate in the bidding for an auction. This obfuscates the parameters in order to mimic the mechanics of placing a bid in an envelope. - -`function newBid(bytes32 sealedBid);` -- Bids are sent by sending a message to the main contract with a sealedBid hash and an amount of ether. The hash contains information about the bid, including the bidded name hash, the bid value, and a random salt. Bids are not tied to any one auction until they are revealed. The value of the bid itself can be masqueraded by sending more than the value of your actual bid. This is followed by a 48h reveal period. Bids revealed after this period will be burned and the ether unrecoverable. Since this is an auction, it is expected that most public hashes, like known domains and common dictionary words, will have multiple bidders pushing the price up. - -`function startAuctionsAndBid(bytes32[] hashes, bytes32 sealedBid)` -- A utility function allowing a call to `startAuctions` followed by `newBid` in a single transaction. - - -`function unsealBid(bytes32 _hash, address _owner, uint _value, bytes32 _salt);` -- Once the bidding period is completed, there is a reveal period during with the properties of a bid are submitted to reveal them. The registrar hashes these properties using the `shaBid()` function above to verify that they match a pre-existing sealed bid. If the unsealedBid is the new best bid, the old best bid is returned to its bidder. - -`function cancelBid(bytes32 seal);` -- Cancels an unrevealed bid according to the rules described in the notes on the refund schedule above. - -`function finalizeAuction(bytes32 _hash);` - -After the registration date has passed, this function can be called to finalize the auction, which then calls the ENS function `setSubnodeOwner()` updating the ENS record to set the winning bidder as owner of the node. - -`function transfer(bytes32 _hash, address newOwner);` -- Update the owner of the ENS node corresponding to the submitted hash to a new owner. This function must be callable only by the current owner. - -`function releaseDeed(bytes32 _hash);` -- After some time, the owner can release the property and get their ether back. - -`function invalidateName(string unhashedName);` -- Since registration is done on the hash of a name, the registrar itself cannot validate names. This function can be used to report a name which is 6 characters long or less. If it has been registered, the submitter will earn 10% of the deed value. We are purposefully handicapping the simplified registrar as a way to force it into being restructured in a few years. - -`function eraseNode(bytes32[] labels)` -- Allows anyone to delete the owner and resolver records for a subdomain of a name that is not currently owned in the registrar. For instance, to zero `foo.bar.eth` on a registrar that owns `.eth`, pass an array containing `[sha3('foo'), sha3('bar')]`. - -`function transferRegistrars(bytes32 _hash) onlyOwner(_hash);` -- Used during the upgrade process to a permanent registrar. If this registrar is no longer the owner of the its root node in the ENS, this function will transfers the deed to the current owner, which should be a new registrar. This function throws if this registrar still owns its root node. - -## Rationale - -### Starting with a temporary registrar - -Anticipating and designing for all the potential issues of name allocation names is unlikely to succeed. This approach chooses not to be concerned with getting it perfect, but allows us to observe and learn with training wheels on, and implement improvements before expanding the available namespace to shorter names or another TLD. - -### Valid names >= 7 characters - -Preserving the shortest, and often most valuable, domain names for the upgraded registrar provides the opportunity to implement processes for dispute resolution (assuming they are found to be necessary). - -### Delayed release of names - -A slower release allows for extra time to identify, and address any issues which may arise after launch. - -### Restricting TLD to `.eth` - -Choosing a single TLD helps to maximize network effects by focusing on one namespace. - -A three letter TLD is a pattern made familiar by it's common usage in internet domain names. This familiarity significantly increases the potential of the ENS to be integrated into pre-existing DNS systems, and reserved as a [special-use domain name](https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml#special-use-domain). A recent precedent for this is the [reservation of the `.onion` domain](https://tools.ietf.org/html/rfc7686). - -### Holding ether as collateral - -This approach is simpler than the familiar model of requiring owners to make recurring payments to retain ownership of a domain name. It also makes the initial registrar a revenue neutral service. - -## Prior work - -This document borrows heavily from several sources: -- [EIP-137](./eip-137.md) outlines the initial implementation of the Registry Contract (ENS.sol) and associated Resolver contracts. -- [ERC-26](https://github.com/ethereum/EIPs/issues/26) was the first ERC to propose a name service at the contract layer -- @alexvandesande's current implementation of the [HashRegistrar](https://github.com/ethereum/ens/blob/mainnet/contracts/HashRegistrarSimplified.sol) - -### Edits: -- 2016-10-26 Added link Alex's design in abstract -- 2016-11-01 change 'Planned deactivation' to h3' -- 2017-03-13 Update timelines for bidding and reveal periods - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-162.md diff --git a/EIPS/eip-1620.md b/EIPS/eip-1620.md index 694c9d6a1cabcf..0bb34083f44ddc 100644 --- a/EIPS/eip-1620.md +++ b/EIPS/eip-1620.md @@ -1,296 +1 @@ ---- -eip: 1620 -title: Money Streaming -author: Paul Berg (@PaulRBerg) -discussions-to: https://github.com/ethereum/EIPs/issues/1620 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-11-24 ---- - -## Simple Summary -Money streaming represents the idea of continuous payments over a finite period of time. Block numbers are used as a proxy of time to continuously update balances. - -## Abstract -The following describes a standard whereby time is measured using block numbers and streams are mappings in a master contract. - -1. A provider sets up a money streaming contract. -2. A prospective payer can interact with the contract and start the stream right away by depositing the funds required for the chosen period. -3. The payee is able to withdraw money from the contract based on its ongoing solvency. That is: `payment rate * (current block height - starting block height)` -4. The stream terms (payment rate, length, metadata) can be updated at any time if both parties pledge their signatures. -5. The stream can be stopped at any point in time by any party without on-chain consensus. -6. If the stream period ended and it was not previously stopped by any party, the payee is entitled to withdraw all the deposited funds. - -## Motivation -This standardised interface aims to change the way we think about long-term financial commitments. Thanks to blockchains, payments need not be sent in chunks (e.g. monthly salaries), as there is much less overhead in paying-as-you-go. Money as a function of time would better align incentives in a host of scenarios. - -### Use Cases - -This is just a preliminary list of use cases. There are other spooky ideas interesting to explore, such as time-dependent disincetivisation, but, for brevity, we have not included them here. - -- Salaries -- Subscriptions -- Consultancies -- CDPs -- Rent -- Parking - -### Crowdsales -[RICOs](https://github.com/lukso-network/rico), or Reversible ICOs, were introduced at Devcon4 by @frozeman. The idea is to endow investors with more power and safety guarantees by allowing them to "reverse" the investment based on the evolution of the project. We previously discussed a similar concept called SICOs, or Streamable ICOs, in this research [thread](https://ethresear.ch/t/chronos-a-quirky-application-proposal-for-plasma/2928/14?u=paulrberg). - -Instead of investing a lump sum and giving the money away to the project developers, funds are held in a smart contract which allocates money based on the passage of time. Project developers can withdraw funds as the stream stays active, while investors have the power to get back a significant percentage of their initial commitment if the project halts. - -## Specification - -### Structs - -The structure of a `stream` should be as follows: - -- `stream` - - `sender`: the `address` of the entity funding the stream - - `recipient`: the `address` where the money is being delivered to - - `tokenAddress`: the `address` of the ERC20 token used as payment asset - - `balance`: the total funds left in the stream - - `timeframe`: as defined below - - `rate`: as defined below - -```solidity - struct Stream { - address sender; - address recipient; - address tokenAddress; - uint256 balance; - Timeframe timeframe; - Rate rate; - } -``` - -- `timeframe` - - `start`: the starting block number of the stream - - `stop`: the stopping block number of the stream - -```solidity -struct Timeframe { - uint256 start; - uint256 stop; -} -``` - -- `rate` - - `payment`: how much money moves from `sender` to `recipient` - - `interval`: how often `payment` moves from `sender` to `recipient` - -```solidity -struct Rate { - uint256 payment; - uint256 interval; -} -``` - ---- - -### Methods - -#### balanceOf - -Returns available funds for the given stream id and address. - -```solidity -function balanceOf(uint256 _streamId, address _addr) -``` - -#### getStream - -Returns the full stream data, if the id points to a valid stream. - -```solidity -function getStream(uint256 _streamId) returns (address sender, address recipient, address tokenAddress, uint256 balance, uint256 startBlock, uint256 stopBlock, uint256 payment, uint256 interval) -``` - -#### create - -Creates a new stream between `msg.sender` and `_recipient`. - -MUST allow senders to create multiple streams in parallel. SHOULD not accept Ether and only use ERC20-compatible tokens. - -**Triggers Event**: [LogCreate](#logcreate) - -```solidity -function create(address _recipient, address _tokenAddress, uint256 _startBlock, uint256 _stopBlock, uint256 _payment, uint256 _interval) -``` - -#### withdraw - -Withdraws all or a fraction of the available funds. - -MUST allow only the recipient to perform this action. - -**Triggers Event**: [LogWithdraw](#logwithdraw) - -```solidity -function withdraw(uint256 _streamId, uint256 _funds) -``` - -#### redeem - -Redeems the stream by distributing the funds to the sender and the recipient. - -SHOULD allow any party to redeem the stream. - -**Triggers Event**: [LogRedeem](#logredeem) - -```solidity -function redeem(uint256 _streamId) -``` - -#### confirmUpdate - -Signals one party's willingness to update the stream - -SHOULD allow any party to do this but MUST NOT be executed without consent from all involved parties. - -**Triggers Event**: [LogConfirmUpdate](#logconfirmupdate) - -**Triggers Event**: [LogExecuteUpdate](#logexecuteupdate) when the last involved party calls this function - -```solidity -function update(uint256 _streamId, address _tokenAddress, uint256 _stopBlock, uint256 _payment, uint256 _interval) -``` - -#### revokeUpdate - -Revokes an update proposed by one of the involved parties. - -MUST allow any party to do this. - -**Triggers Event**: [LogRevokeUpdate](#logrevokeupdate) - -```solidity -function confirmUpdate(uint256 _streamId, address _tokenAddress, uint256 _stopBlock, uint256 _payment, uint256 _interval) -``` - ---- - -### Events - -#### LogCreate - -MUST be triggered when `create` is successfully called. - -```solidity -event LogCreate(uint256 indexed _streamId, address indexed _sender, address indexed _recipient, address _tokenAddress, uint256 _startBlock, uint256 _stopBlock, uint256 _payment, uint256 _interval) -``` - -#### LogWithdraw - -MUST be triggered when `withdraw` is successfully called. - -```solidity -event LogWithdraw(uint256 indexed _streamId, address indexed _recipient, uint256 _funds) -``` - -#### LogRedeem - -MUST be triggered when `redeem` is successfully called. - -```solidity -event LogRedeem(uint256 indexed _streamId, address indexed _sender, address indexed _recipient, uint256 _senderBalance, uint256 _recipientBalance) -``` - -#### LogConfirmUpdate - -MUST be triggered when `confirmUpdate` is successfully called. - -```solidity -event LogConfirmUpdate(uint256 indexed _streamId, address indexed _confirmer, address _newTokenAddress, uint256 _newStopBlock, uint256 _newPayment, uint256 _newInterval); -``` - -#### LogRevokeUpdate - -MUST be triggered when `revokeUpdate` is successfully called. - -```solidity -event LogRevokeUpdate(uint256 indexed _streamId, address indexed revoker, address _newTokenAddress, uint256 _newStopBlock, uint256 _newPayment, uint256 _newInterval) -``` - -#### LogExecuteUpdate - -MUST be triggered when an update is approved by all involved parties. - -```solidity -event LogExecuteUpdate(uint256 indexed _newStreamId, address indexed _sender, address indexed _recipient, address _newTokenAddress, uint256 _newStopBlock, uint256 _newPayment, uint256 _newInterval) -``` - -## Rationale - -This specification was designed to serve as an entry point to the quirky concept of money as a function of time and it is definitely not set in stone. Several other designs, including payment channels and Plasma chains were also considered, but they were eventually deemed dense in assumptions unnecessary for an initial version. - - - -Block times are a reasonable, trustless proxy for time on the blockchain. Between 2016 and 2018, the Ethereum block time average value [hovered](https://etherscan.io/chart/blocktime) around 14 seconds, excluding the last two quarters of 2017. Mathematically speaking, it would be ideal to have a standard deviation as close to 0 as possible, but that is not how things work in the real world. This has huge implications on the feasibility of this ERC which we shall investigate below. - -### GCD -When setting up a stream, a payer and a payee may want to make the total streaming duration a multiple of the "greatest common denominator" (GCD) of the chain they operate on; that is, the average block time. This is not imperative in the smart contracts per se, but there needs to be an off-chain process to map streams to real world time units in order to create a sound and fair payment mechanism. - -### Block Times -Because there is uncertainty regarding block times, streams may not be settled on the blockchain as initially planned. Let `$d` be the total streaming duration measured in seconds, `$t` the average block time before the stream started and `$t'` the actual average block time over `$d` after the stream started. We distinguish two undesirable scenarios: - -1. `$t` < `$t'`: the payee will get their funds *later* than expected - -2. `$t` > `$t'`: the payee will get their funds *sooner* than expected - -If the combined error delta is smaller than the payment rate (fifth parameter of the `create` method, measured in wei), there is no problem at all. Conversely, we stumble upon trust issues because real-world time frames do not correspond to the stream terms. For instance, if an employee is normally entitled to withdraw all the funds from the stream at the end of the month, but block times cause case 1 from above to occur, the employee is in a financial disadvantage because their continuous effort is not compensated as promised. - -Limiting the problem scope only to Ethereum, we propose two remedies: - -1. Consensus on calling the `update` function to correct the stream terms. This might sound preposterous, but in most cases the stakes are low and stream participants are involved in long-term financial commitments. There is a high disincentive to refuse to cooperate. - -2. Autonomously fix significant error deltas. In theory, we could achieve this using previous blocks' timestamps, "checkpointing" the stream once in a predefined number of blocks. This is still an area of active research because of potentially high overheads in gas costs. - -Nonetheless, it is important to note that this is still a major improvement on the traditional model where absolute trust is required. - -### Sidechains - -It could be more efficient to implement this standard on independent sidechains like [POA Network](https://poa.network) or [xDai](https://medium.com/poa-network/poa-network-partners-with-makerdao-on-xdai-chain-the-first-ever-usd-stable-blockchain-65a078c41e6a) - thanks to their rather predictable nature. Admittedly, security is traded for scalability, but proper cryptoeconomic stakes could alleviate potential problems. - -Furthermore, it is intriguing to explore the prospect of stream-specific sidechains. - -### Oracles - -The proposed specification uses block numbers to proxy time, but this need not be the only method. Albeit it would imply different trust assumptions, oracles could be used to provide a feed of timestamps. Coupled with the aforementioned idea of stream-specific sidechains, oracles could efficiently solve the problems outlined in [Block Times](#block-times). - -### Multi-Hop Streams - -Future or upgraded versions of this standard may describe "multi-hop" streams. If: - -1. There is a stream between A and B -2. There is another stream between B and C - -There could be a way to avoid running two different streams in parallel. That is, a fraction or all of the funds being streamed from A to B could be automatically wired to C. An interesting use case for this is taxes. Instead of manually moving money around, proactively calculating how much you owe and then transfer it, a stream could atomically perform those operations for you. - -## Implementation - -- [ChronosProtocol WIP implementation](https://github.com/ChronosProtocol/monorepo) - -## Additional References -- [Chronos Protocol Ethresear.ch Plasma Proposal](https://ethresear.ch/t/chronos-a-quirky-application-proposal-for-plasma/2928?u=paulrberg) -- [Chronos Protocol White Paper](http://chronosprotocol.org/chronos-white-paper.pdf) -- [Flipper: Streaming Salaries @ CryptoLife Hackathon](https://devpost.com/software/flipper-3gvl4b) -- [SICOs or Streamed ICOs](https://ethresear.ch/t/chronos-a-quirky-application-proposal-for-plasma/2928/14?u=paulrberg) -- [RICOs or Reversible ICOs](https://twitter.com/feindura/status/1058057076306518017) -- [Andreas Antonopoulos' Keynote on Bitcoin, Lightning and Money Streaming](https://www.youtube.com/watch?v=gF_ZQ_eijPs) - -## Final Notes - -Many thanks to @mmilton41 for countless brainstorming sessions. We have been doing research on the topic of money streaming for quite a while within the context of @ChronosProtocol. In August this year, we published the first version of our white paper describing a Plasma approach. However, in the meantime, we realised that it would be much more [fun](https://twitter.com/PaulRBerg/status/1056595919116910592) and easier to start small on Ethereum itself and sidechains like [xDai](https://blockscout.com/poa/dai). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1620.md diff --git a/EIPS/eip-1633.md b/EIPS/eip-1633.md index 17461d4dbddb55..385436f95f3eed 100644 --- a/EIPS/eip-1633.md +++ b/EIPS/eip-1633.md @@ -1,174 +1 @@ ---- -eip: 1633 -title: Re-Fungible Token Standard (RFT) -author: Billy Rennekamp (@okwme), Dan Long , Kiryl Yermakou , Nate van der Ende -discussions-to: https://github.com/ethereum/EIPs/issues/1634 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-11-18 -requires: 20, 165, 721 ---- - -## Simple Summary -[ERC-20](./eip-20.md) extension for proportional ownership of an [ERC-721](./eip-721.md) token. - -## Abstract -The intention of this proposal, the Re-Fungible Token Standard, is to extend the ERC-20 Token Standard and utilize ERC-165 Standard Interface Detection in order to represent the shared ownership of an ERC-721 Non-Fungible Token. The ERC-20 Token Standard was modified as little as possible in order to allow this new class of token to operate in all of the ways and locations which are familiar to assets that follow the original ERC-20 specification. While there are many possible variations of this specification that would enable many different capabilities and scenarios for shared ownership, this proposal is focused on the minimal commonalities to enable as much flexibility as possible for various further extensions. This proposal makes it possible to verify, from the contract level or from an external query, whether a fungible token represents a form of shared ownership of a non-fungible token. The inclusion of ERC-165 makes it possible to verify, from the contract level or from an external query, whether a non-fungible token is owned by ERC-20 token representing shared ownership. - -## Motivation -Shared ownership occurs across many industries and for many reasons. As more assets are registered, regulated and/or represented by the ERC-721 Non-Fungible Token Standard there will be more instances where the need for shared ownership of these assets will arise. For example, ARTBLX Inc. is working towards facilitating a protocol for collective ownership of physical, digital and conceptual artworks. The fungible tokens created from this process will have a value attached to the non-fungible tokens which they represent. This will be useful for price discovery of the underlying asset, liquidity for shared owners and as a new class of asset which can be used as collateral for loans or other financial instruments like stable coins. Providing an interface to this special class of fungible tokens is necessary to allow third parties to recognize them as a special class of fungible token and to recognize when a non-fungible token is collectively owned. This might be useful in the case of a wallet who would want to utilize the metadata of the underlying NFT to show additional info next to an RFT, or on an exchange who might want to make that sort of info similarly available, or an NFT marketplace who may want to direct customers to a relevant exchange who wish to purchase shares in a NFT which is owned by an RFT. Anywhere an ERC-20 is applicable it would be useful for a user to know whether that token represents a shared NFT, and what attributes that NFT may have. - -## Specification -At a minimum, third parties need two things: 1) to be able to distinguish re-fungible tokens from other token standards and 2) to determine when a non-fungible token is collectively owned. These two scenarios can be encountered from the perspective of initial contact with the non-fungible token or from the perspective of initial contact with the re-fungible token. - -#### Initial Contact with the Re-Fungible Token - -In order for a third party to confirm which non-fungible token is owned by the re-fungible token there needs to be a pointer from the RFT contract to the NFT contract and the relevant token id. This is possible with two public getters named `parentToken()` and `parentTokenId()`. The first getter returns a variable of type `address` and designates the contract address of the Non-Fungible Token contract. The second getter returns a variable of type `uint256` and designates the token ID of the Non-Fungible Token. With these getters, the identity of the Non-Fungible Token can be determined. Below is an example of the Re-Fungible Token Standard interface that includes these getter functions: - -```solidity -pragma solidity ^0.4.20; - -/// @dev Note: the ERC-165 identifier for this interface is 0x5755c3f2. -interface RFT /* is ERC20, ERC165 */ { - - function parentToken() external view returns(address _parentToken); - function parentTokenId() external view returns(uint256 _parentTokenId); - -} -``` - -The validity of this claim can be confirmed from another contract (on-chain) or from interacting with an RPC endpoint (off-chain). Below is an example of the on-chain scenario: - -```solidity -pragma solidity ^0.4.20; - -import './RFT.sol'; -import './ERC721.sol'; - -contract ConfirmRFT { - - function confirmRFT(address _RFT) external view returns(bool) { - address _NFT = RFT(_RFT).parentToken(); // returns address of NFT contract - uint256 _tokenId = RFT(_RFT).parentTokenId(); // returns id of ID of NFT - - return - NFT(_NFT).supportsInterface(0x80ac58cd) && // confirm it is ERC-721 - NFT(_NFT).ownerOf(_tokenId) == _RFT; // confirm the owner of the NFT is the RFT contract address - } - -} -``` - -Below is an off-chain example using an instance of web3.js in javascript: -```javascript -async function confirmRFT(web3) { - - const ERC721ABI = [...] // abi for ERC721 - const RFTABI = [...] // abi for RFT - const RFTAddress = '0x0123456789abcdef0123456789abcdef' // address for the deployed RFT - - const RFTContract = new web3.eth.Contract(RFTABI, RFTAddress) // deployed RFT contract instance - const ERC721Address = await RFTcontract.methods.parentToken().call() // returns address of NFT contract - const ERC721TokenId = await RFTcontract.methods.parentTokenId().call() // returns id of ID of NFT - - const ERC721Contract = new web3.eth.Contract(ERC721ABI, ERC721Address) // deployed ERC721 (as reported by RFT) - const isERC721 = await ERC721Contract.methods.supportsInterface('0x80ac58cd').call() // confirm it is ERC-721 - const ownerOfAddress = await ERC721Contract.methods.ownerOf(ERC721TokenId).call() // get the owner of the NFT - - return ERC721Response.toLowerCase() === RFTAddress.toLowerCase() // confirm the owner of the NFT is the RFT contract -} -``` - -#### Initial Contact with the Non-Fungible Token - -When checking the owner of a specific non-fungible token it's important to be able to determine whether owner is in fact a re-fungible token contract. This is possible by utilizing ERC-165 Standard Interface Detection. In order to comply with that standard a contract must include the following getter function which returns `true` when passed the `bytes4` parameter `0x01ffc9a7`: -``` -function supportsInterface(bytes4 interfaceID) external view returns (bool); -``` -After establishing support for this interface it becomes useful in determining whether the contract adheres to the Re-Fungible Token Standard. To do so the `supportsInterface(bytes4 interfaceID)` getter function must return `true` when passed the `bytes4` parameter `0x5755c3f2` which is the result of `bytes4(keccak256('parentToken()')) ^ bytes4(keccak256('parentTokenId()'))` or `parentToken.selector ^ parentTokenId.selector`. This could be achieved with the following code: -```solidity -pragma solidity ^0.4.20; - -import "./ERC20.sol"; - -/// @dev Note: the ERC-165 identifier for this interface is 0x5755c3f2. -interface RFT is ERC20 /*, ERC165 */ { - - function supportsInterface(bytes4 interfaceID) external view returns(bool) { - return - interfaceID == this.supportsInterface.selector || // ERC165 - interfaceID == this.parentToken.selector || // parentToken() - interfaceID == this.parentTokenId.selector || // parentTokenId() - interfaceID == this.parentToken.selector ^ this.parentTokenId.selector; // RFT - } - - function parentToken() external view returns(address _parentToken); - function parentTokenId() external view returns(uint256 _parentTokenId); - -} -``` -The flow of actually checking the status of a non-fungible token owner as a re-fungible token contract can be done from another contract (on-chain) as well as with an RPC endpoint (off-chain). Below is an example of the on-chain scenario: -```solidity -pragma solidity ^0.4.20; - -import './RFT.sol'; -import './ERC721.sol'; - -contract ConfirmRFT { - - function confirmRFT(address _NFT, uint256 _tokenId) external view returns(bool) { - address _RFT = ERC721(_NFT).ownerOf(_tokenId); // get the owner of the NFT - - return - RFT(_RFT).supportsInterface(0x01ffc9a7) && // confirm it supports ERC-165 - RFT(_RFT).supportsInterface(0x5755c3f2) // confirm it is RFT - } - -} -``` -Below is an off-chain example using web3.js in javascript: -```javascript -async function confirmRFT(web3) { - - const ERC721ABI = [...] // abi for ERC721 - const RFTABI = [...] // abi for RFT - const ERC721Address = '0x0123456789abcdef0123456789abcdef' // address for the deployed NFT - const ERC721TokenId = '7' // token Id of the NFT - - const ERC721Contract = new web3.eth.Contract(ERC721ABI, ERC721Address) // deployed ERC721 - const RFTAddress = await ERC721Contract.methods.ownerOf(ERC721TokenId).call() // owner address of the NFT - - - const RFTContract = new web3.eth.Contract(RFTABI, RFTAddress) // deployed RFT contract instance - const isERC165 = await RFTContract.methods.supportsInterface('0x01ffc9a7').call() // confirm it is ERC-165 - return isERC165 && await RFTContract.methods.supportsInterface('0x5755c3f2').call() // confirm it is RFT - -} -``` -## Rationale -Most of the decisions made around the design of this standard were done in the hopes of keeping it as flexible as possible for as many use cases as possible. This includes making the standard 100% backwards compatible with ERC-20 Token Standard and able to interact with any previously deployed or future ERC-721 non-fungible token. This allows for each project to determine their own system for minting, burning and governing their re-fungible tokens depending on their specific use case. - -## Backwards Compatibility -The Re-Fungible Token Standard is 100% backwards compatible with ERC-20 Token Standard. It is a small extension to the original specification and meant to be further extended for more specific use cases. Keeping the standard compatible with ERC-20 is important to allow for this token to benefit from the ecosystem that has grown around supporting the ubiquitous ERC-20 Token Standard. - -The Re-Fungible Token Standard is intended to interact with the ERC-721 Non-Fungible Token Standard. It is kept purposefully agnostic to extensions beyond the standard in order to allow specific projects to design their own token relationships such as governance over, rights to or permissions on each non-fungible token relative to the respective re-fungible token owners. - -## Implementation -```solidity -pragma solidity ^0.4.20; - -/// @dev Note: the ERC-165 identifier for this interface is 0x5755c3f2. -interface RFT /* is ERC20, ERC165 */ { - - function parentToken() external view returns(address _parentToken); - function parentTokenId() external view returns(uint256 _parentTokenId); - -} -``` - -## Security Considerations -TBD - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1633.md diff --git a/EIPS/eip-165.md b/EIPS/eip-165.md index 77e6cbcf3938d3..1b7b24d81c39fa 100644 --- a/EIPS/eip-165.md +++ b/EIPS/eip-165.md @@ -1,235 +1 @@ ---- -eip: 165 -title: Standard Interface Detection -author: Christian Reitwießner , Nick Johnson , Fabian Vogelsteller , Jordi Baylina , Konrad Feldmeier , William Entriken -type: Standards Track -category: ERC -status: Final -created: 2018-01-23 -requires: 214 ---- - -## Simple Summary - -Creates a standard method to publish and detect what interfaces a smart contract implements. - -## Abstract - -Herein, we standardize the following: - -1. How interfaces are identified -2. How a contract will publish the interfaces it implements -3. How to detect if a contract implements ERC-165 -4. How to detect if a contract implements any given interface - -## Motivation - -For some "standard interfaces" like [the ERC-20 token interface](./eip-20.md), it is sometimes useful to query whether a contract supports the interface and if yes, which version of the interface, in order to adapt the way in which the contract is to be interacted with. Specifically for ERC-20, a version identifier has already been proposed. This proposal standardizes the concept of interfaces and standardizes the identification (naming) of interfaces. - -## Specification - -### How Interfaces are Identified - -For this standard, an *interface* is a set of [function selectors as defined by the Ethereum ABI](https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector). This a subset of [Solidity's concept of interfaces](https://solidity.readthedocs.io/en/develop/abi-spec.html) and the `interface` keyword definition which also defines return types, mutability and events. - -We define the interface identifier as the XOR of all function selectors in the interface. This code example shows how to calculate an interface identifier: - -```solidity -pragma solidity ^0.4.20; - -interface Solidity101 { - function hello() external pure; - function world(int) external pure; -} - -contract Selector { - function calculateSelector() public pure returns (bytes4) { - Solidity101 i; - return i.hello.selector ^ i.world.selector; - } -} -``` - -Note: interfaces do not permit optional functions, therefore, the interface identity will not include them. - -### How a Contract will Publish the Interfaces it Implements - -A contract that is compliant with ERC-165 shall implement the following interface (referred as `ERC165.sol`): - -```solidity -pragma solidity ^0.4.20; - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -The interface identifier for this interface is `0x01ffc9a7`. You can calculate this by running `bytes4(keccak256('supportsInterface(bytes4)'));` or using the `Selector` contract above. - -Therefore the implementing contract will have a `supportsInterface` function that returns: - -- `true` when `interfaceID` is `0x01ffc9a7` (EIP165 interface) -- `false` when `interfaceID` is `0xffffffff` -- `true` for any other `interfaceID` this contract implements -- `false` for any other `interfaceID` - -This function must return a bool and use at most 30,000 gas. - -Implementation note, there are several logical ways to implement this function. Please see the example implementations and the discussion on gas usage. - -### How to Detect if a Contract Implements ERC-165 - -1. The source contract makes a `STATICCALL` to the destination address with input data: `0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000` and gas 30,000. This corresponds to `contract.supportsInterface(0x01ffc9a7)`. -2. If the call fails or return false, the destination contract does not implement ERC-165. -3. If the call returns true, a second call is made with input data `0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000`. -4. If the second call fails or returns true, the destination contract does not implement ERC-165. -5. Otherwise it implements ERC-165. - -### How to Detect if a Contract Implements any Given Interface - -1. If you are not sure if the contract implements ERC-165, use the above procedure to confirm. -2. If it does not implement ERC-165, then you will have to see what methods it uses the old-fashioned way. -3. If it implements ERC-165 then just call `supportsInterface(interfaceID)` to determine if it implements an interface you can use. - -## Rationale - -We tried to keep this specification as simple as possible. This implementation is also compatible with the current Solidity version. - -## Backwards Compatibility - -The mechanism described above (with `0xffffffff`) should work with most of the contracts previous to this standard to determine that they do not implement ERC-165. - -Also [the ENS](./eip-137.md) already implements this EIP. - -## Test Cases - -Following is a contract that detects which interfaces other contracts implement. From @fulldecent and @jbaylina. - -```solidity -pragma solidity ^0.4.20; - -contract ERC165Query { - bytes4 constant InvalidID = 0xffffffff; - bytes4 constant ERC165ID = 0x01ffc9a7; - - function doesContractImplementInterface(address _contract, bytes4 _interfaceId) external view returns (bool) { - uint256 success; - uint256 result; - - (success, result) = noThrowCall(_contract, ERC165ID); - if ((success==0)||(result==0)) { - return false; - } - - (success, result) = noThrowCall(_contract, InvalidID); - if ((success==0)||(result!=0)) { - return false; - } - - (success, result) = noThrowCall(_contract, _interfaceId); - if ((success==1)&&(result==1)) { - return true; - } - return false; - } - - function noThrowCall(address _contract, bytes4 _interfaceId) constant internal returns (uint256 success, uint256 result) { - bytes4 erc165ID = ERC165ID; - - assembly { - let x := mload(0x40) // Find empty storage location using "free memory pointer" - mstore(x, erc165ID) // Place signature at beginning of empty storage - mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature - - success := staticcall( - 30000, // 30k gas - _contract, // To addr - x, // Inputs are stored at location x - 0x24, // Inputs are 36 bytes long - x, // Store output over input (saves space) - 0x20) // Outputs are 32 bytes long - - result := mload(x) // Load the result - } - } -} -``` - -## Implementation - -This approach uses a `view` function implementation of `supportsInterface`. The execution cost is 586 gas for any input. But contract initialization requires storing each interface (`SSTORE` is 20,000 gas). The `ERC165MappingImplementation` contract is generic and reusable. - -```solidity -pragma solidity ^0.4.20; - -import "./ERC165.sol"; - -contract ERC165MappingImplementation is ERC165 { - /// @dev You must not set element 0xffffffff to true - mapping(bytes4 => bool) internal supportedInterfaces; - - function ERC165MappingImplementation() internal { - supportedInterfaces[this.supportsInterface.selector] = true; - } - - function supportsInterface(bytes4 interfaceID) external view returns (bool) { - return supportedInterfaces[interfaceID]; - } -} - -interface Simpson { - function is2D() external returns (bool); - function skinColor() external returns (string); -} - -contract Lisa is ERC165MappingImplementation, Simpson { - function Lisa() public { - supportedInterfaces[this.is2D.selector ^ this.skinColor.selector] = true; - } - - function is2D() external returns (bool){} - function skinColor() external returns (string){} -} -``` - -Following is a `pure` function implementation of `supportsInterface`. The worst-case execution cost is 236 gas, but increases linearly with a higher number of supported interfaces. - -```solidity -pragma solidity ^0.4.20; - -import "./ERC165.sol"; - -interface Simpson { - function is2D() external returns (bool); - function skinColor() external returns (string); -} - -contract Homer is ERC165, Simpson { - function supportsInterface(bytes4 interfaceID) external view returns (bool) { - return - interfaceID == this.supportsInterface.selector || // ERC165 - interfaceID == this.is2D.selector - ^ this.skinColor.selector; // Simpson - } - - function is2D() external returns (bool){} - function skinColor() external returns (string){} -} -``` - -With three or more supported interfaces (including ERC165 itself as a required supported interface), the mapping approach (in every case) costs less gas than the pure approach (at worst case). - -## Version history -* PR 1640, finalized 2019-01-23 -- This corrects the noThrowCall test case to use 36 bytes rather than the previous 32 bytes. The previous code was an error that still silently worked in Solidity 0.4.x but which was broken by new behavior introduced in Solidity 0.5.0. This change was discussed at [#1640](https://github.com/ethereum/EIPs/pull/1640). - -* EIP 165, finalized 2018-04-20 -- Original published version. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-165.md diff --git a/EIPS/eip-1710.md b/EIPS/eip-1710.md index 91503f8d78d9d1..0f9bc9f691a4be 100644 --- a/EIPS/eip-1710.md +++ b/EIPS/eip-1710.md @@ -1,59 +1 @@ ---- -eip: 1710 -title: URL Format for Web3 Browsers -author: Bruno Barbieri (@brunobar79) -discussions-to: https://ethereum-magicians.org/t/standarize-url-format-for-web3-browsers/2422 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-01-13 -requires: 155 ---- - -## Simple Summary - -A standard way of representing web3 browser URLs for decentralized applications. - -## Abstract - -Since most normal web browsers (specifically on mobile devices) can not run decentralized applications correctly because of the lack of web3 support, it is necessary to differentiate them from normal urls, so they can be opened in web3 browsers if available. - -## Motivation - -Lots of dApps that are trying to improve their mobile experience are currently (deep)linking to specific mobile web3 browsers which are currently using their own url scheme. - -In order to make the experience more seamless, dApps should still be able to recommend a specific mobile web3 browser via [deferred deeplinking](https://en.wikipedia.org/wiki/Deferred_deep_linking) but by having a standard url format, if the user already has a web3 browser installed that implements this standard, it will be automatically linked to it. - -There is also a compatibility problem with the current `ethereum:` url scheme described in [EIP-831](./eip-831.md) where any ethereum related app (wallets, identity management, etc) already registered it and because of iOS unpredictable behavior for multiple apps handling a single url scheme, users can end up opening an `ethereum:` link in an app that doesn not include a web3 browser and will not be able to handle the deeplink correctly. - -## Specification - -### Syntax - -Web3 browser URLs contain "dapp" in their schema (protocol) part and are constructed as follows: - - request = "dapp" ":" [chain_id "@"] dapp_url - chain_id = 1*DIGIT - dapp_url = URI - -### Semantics - -`chain_id` is optional and it is a parameter for the browser to automatically select the corresponding chain ID as specified in [EIP-155](./eip-155.md) before opening the dApp. - -`dapp_url` is a valid [RFC3986](https://www.ietf.org/rfc/rfc3986.txt) URI - -This a complete example url: - -`dapp:1@peepeth.com/brunobar79?utm_source=github` - -which will open the web3 browser, select `mainnet` (chain_id = 1) and then navigate to: - -`https://peepeth.com/brunobar79?utm_source=github` - -## Rationale - -The proposed format attempts to solve the problem of vendor specific protocols for web3 browsers, avoiding conflicts with the existing 'ethereum:' URL scheme while also adding an extra feature: `chain_id` which will help dApps to be accessed with the right network preselected, optionally extracting away that complexity from end users. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1710.md diff --git a/EIPS/eip-173.md b/EIPS/eip-173.md index e79033eb5262d3..5bd02359d4a3c6 100644 --- a/EIPS/eip-173.md +++ b/EIPS/eip-173.md @@ -1,97 +1 @@ ---- -eip: 173 -title: Contract Ownership Standard -description: A standard interface for ownership of contracts -author: Nick Mudge (@mudgen), Dan Finlay -discussions-to: https://github.com/ethereum/EIPs/issues/173 -type: Standards Track -category: ERC -status: Final -created: 2018-06-07 ---- - -## Abstract - -This specification defines standard functions for owning or controlling a contract. - -An implementation allows reading the current owner (`owner() returns (address)`) and transferring ownership (`transferOwnership(address newOwner)`) along with a standardized event for when ownership is changed (`OwnershipTransferred(address indexed previousOwner, address indexed newOwner)`). - -## Motivation - -Many smart contracts require that they be owned or controlled in some way. For example to withdraw funds or perform administrative actions. It is so common that the contract interface used to handle contract ownership should be standardized to allow compatibility with user interfaces and contracts that manage contracts. - -Here are some examples of kinds of contracts and applications that can benefit from this standard: -1. Exchanges that buy/sell/auction ethereum contracts. This is only widely possible if there is a standard for getting the owner of a contract and transferring ownership. -2. Contract wallets that hold the ownership of contracts and that can transfer the ownership of contracts. -3. Contract registries. It makes sense for some registries to only allow the owners of contracts to add/remove their contracts. A standard must exist for these contract registries to verify that a contract is being submitted by the owner of it before accepting it. -4. User interfaces that show and transfer ownership of contracts. - -## Specification - -Every ERC-173 compliant contract must implement the `ERC173` interface. Contracts should also implement `ERC165` for the ERC-173 interface. - -```solidity - -/// @title ERC-173 Contract Ownership Standard -/// Note: the ERC-165 identifier for this interface is 0x7f5828d0 -interface ERC173 /* is ERC165 */ { - /// @dev This emits when ownership of a contract changes. - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /// @notice Get the address of the owner - /// @return The address of the owner. - function owner() view external returns(address); - - /// @notice Set the address of the new owner of the contract - /// @dev Set _newOwner to address(0) to renounce any ownership. - /// @param _newOwner The address of the new owner of the contract - function transferOwnership(address _newOwner) external; -} - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -The `owner()` function may be implemented as `pure` or `view`. - -The `transferOwnership(address _newOwner)` function may be implemented as `public` or `external`. - -To renounce any ownership of a contract set `_newOwner` to the zero address: `transferOwnership(address(0))`. If this is done then a contract is no longer owned by anybody. - -The OwnershipTransferred event should be emitted when a contract is created. - -## Rationale - -Key factors influencing the standard: -- Keeping the number of functions in the interface to a minimum to prevent contract bloat. -- Backwards compatibility with existing contracts. -- Simplicity -- Gas efficient - -Several ownership schemes were considered. The scheme chosen in this standard was chosen because of its simplicity, low gas cost and backwards compatibility with existing contracts. - -Here are other schemes that were considered: -1. **Associating an Ethereum Name Service (ENS) domain name with a contract.** A contract's `owner()` function could look up the owner address of a particular ENS name and use that as the owning address of the contract. Using this scheme a contract could be transferred by transferring the ownership of the ENS domain name to a different address. Short comings to this approach are that it is not backwards compatible with existing contracts and requires gas to make external calls to ENS related contracts to get the owner address. -2. **Associating an ERC721-based non-fungible token (NFT) with a contract.** Ownership of a contract could be tied to the ownership of an NFT. The benefit of this approach is that the existing ERC721-based infrastructure could be used to sell/buy/auction contracts. Short comings to this approach are additional complexity and infrastructure required. A contract could be associated with a particular NFT but the NFT would not track that it had ownership of a contract unless it was programmed to track contracts. In addition handling ownership of contracts this way is not backwards compatible. - -This standard does not exclude the above ownership schemes or other schemes from also being implemented in the same contract. For example a contract could implement this standard and also implement the other schemes so that ownership could be managed and transferred in multiple ways. This standard does provide a simple ownership scheme that is backwards compatible, is light-weight and simple to implement, and can be widely adopted and depended on. - -This standard can be (and has been) extended by other standards to add additional ownership functionality. - -## Security Considerations - -If the address returned by `owner()` is an externally owned account then its private key must not be lost or compromised. - -## Backwards Compatibility - -Many existing contracts already implement this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-173.md diff --git a/EIPS/eip-1753.md b/EIPS/eip-1753.md index 09cb027eebd047..9d78845149ce98 100644 --- a/EIPS/eip-1753.md +++ b/EIPS/eip-1753.md @@ -1,246 +1 @@ ---- -eip: 1753 -title: Smart Contract Interface for Licences -author: Lucas Cullen (@BitcoinBrisbane), Kai Yeung (@CivicKai), Anna Crowley , Caroline Marshall , Katrina Donaghy -status: Stagnant -type: Standards Track -category: ERC -created: 2019-02-06 ---- - -## Abstract - -This Ethereum Improvement Proposal (EIP) proposes an Ethereum standard for the issuance of licences, permits and grants (Licences). - -A Licence is a limited and temporary authority, granted to a natural (e.g. you) or legal person (e.g. a corporation), to do something that would otherwise be unlawful pursuant to a legal framework. A public Licence is granted by the government, directly (e.g. by the New South Wales Department of Primary Industries, Australia) or indirectly (e.g. by an agent operating under the government’s authority), and derives its authority from legislation, though this is often practically achieved via delegated legislation such as regulations. This can be contrasted to a private licence – for example, the licence you grant to a visitor who comes onto your property. - -A Licence has the following properties: - -* granted personally to the licencee (Licencee), though it may be transferrable to another person or company; -* conferring a temporary right to the Licencee to own, use or do something that would otherwise be prohibited, without conferring any property interest in the underlying thing. For example, you may be granted a licence to visit a national park without acquiring any ownership in or over the park itself; -* allowing the government authority responsible for the Licence to amend, revoke, renew, suspend or deny the issuance of the Licence, or to impose conditions or penalties for non-compliance; and -* usually issued only after the payment of a fee or the meeting of some criteria. - -Additionally, a Licence may be granted in respect of certain information. For example, a Licence may be issued in respect of a vehicle registration number and attaching to that specific registered vehicle. - -## Motivation - -Governments are responsible for the issuance and management of Licences. However, maintaining and sharing this data can be complicated and inefficient. The granting of Licences usually requires the filing of paper-based application forms, manual oversight of applicable legislation and data entry into registries, as well as the issuance of paper based Licences. If individuals wish to sight information on Licence registries, they often need to be present at the government office and complete further paper-based enquiry forms in order to access that data (if available publicly). - -This EIP seeks to define a standard that will allow for the granting and/or management of Licences via Ethereum smart contracts. The motivation is, in essence, to address the inefficiencies inherent in current licencing systems. - -## Specification - -### Methods - -**NOTES**: - - The following specifications use syntax from Solidity `0.4.17` (or above) - - Callers MUST handle `false` from `returns (bool success)`. Callers MUST NOT assume that `false` is never returned! - - -#### name - -Returns the name of the permit - e.g. `"MyPermit"`. - -``` js -function name() public view returns (string); -``` - -#### totalSupply - -Returns the total permit supply. - -``` js -function totalSupply() public view returns (uint256); -``` - -#### grantAuthority - -Adds an ethereum address to a white list of addresses that have authority to modify a permit. - -``` js -function grantAuthority(address who) public; -``` - -#### revokeAuthority - -Removes an ethereum address from a white list of addresses that have authority to modify a permit. - -``` js -function revokeAuthority(address who) public; -``` - -#### hasAuthority - -Checks to see if the address has authority to grant or revoke permits. - -``` js -function hasAuthority(address who) public view; -``` - -#### issue - -Issues an ethereum address a permit between the specified date range. - -``` js -function issue(address who, uint256 validFrom, uint256 validTo) public; -``` - -#### revoke - -Revokes a permit from an ethereum address. - -``` js -function revoke(address who) public; -``` - -#### hasValid - -Checks to see if an ethereum address has a valid permit. - -``` js -function hasValid(address who) external view returns (bool); -``` - -#### purchase - -Allows a user to self procure a licence. - -``` js -function purchase(uint256 validFrom, uint256 validTo) external payable; -``` - -## Rationale - -The use of smart contracts to apply for, renew, suspend and revoke Licences will free up much needed government resources and allow for the more efficient management of Licences. The EIP also seeks to improve the end user experience of the Licence system. In an era of open government, there is also an increased expectation that individuals will be able to easily access Licence registries, and that the process will be transparent and fair. - -By creating an EIP, we hope to increase the use of Ethereum based and issued Licences, which will address these issues. - -The Ethereum blockchain is adaptable to various Licences and government authorities. It will also be easily translatable into other languages and can be used by other governmental authorities across the world. Moreover, a blockchain will more effectively protect the privacy of Licence-holders’ data, particularly at a time of an ever-increasing volume of government data breaches. - -The EIP has been developed following the review of a number of licensing regulations at the national and state level in Australia. The review allowed the identification of the common licence requirements and criteria for incorporation into the EIP. We have included these in the proposed standard but seek feedback on whether these criteria are sufficient and universal. - -## Test Cases - -A real world example of a Licence is a permit required to camp in a national park in Australia (e.g. Kakadu national park in the Northern Territory of Australia) under the Environment Protection and Biodiversity Conservation Regulations 2000 (Cth) (EPBC Act) and the Environment Protection and Biodiversity Conservation Regulations 2000 (the Regulations). Pursuant to the EPBC Act and the Regulations, the Director of National Parks oversees a camping permit system, which is intended to help regulate certain activities in National Parks. Permits allowing access to National Parks can be issued to legal or natural persons if the applicant has met certain conditions. - -The current digital portal and application form to camp at Kakadu National Park (the Application) can be accessed at: https://www.environment.gov.au/system/files/resources/b3481ed3-164b-4e72-a9f8-91fc987d90e7/files/kakadu-camping-permit-form-19jan2015-pdf.pdf - -The user must provide the following details when making an Application: - -* The full name and contact details of each person to whom the permit is to be issued; - -* If the applicant is a company or other incorporated body: - -o the name, business address and postal address of the company or incorporated body; - -o if the applicant is a company— - -* the full name of each of the directors of the company; - -* the full name and contact details of the person completing the application form; - -* the ACN or ABN of the company or other incorporated body (if applicable); - -* Details of the proposed camping purpose (e.g. private camping, school group, etc.); - -* A start date and duration for the camping (up to the maximum duration allowed by law); - -* Number of campers (up to the maximum allowed by law); - -* All other required information not essential to the issuance of the Licence (e.g. any particular medical needs of the campers); and - -* Fees payable depending on the site, duration and number of campers. - -The Regulations also set out a number of conditions that must be met by licensees when the permit has been issued. The Regulations allow the Director of National Parks to cancel, renew or transfer the licence. The above workflow could be better performed by way of a smart contract. - -The key criteria required as part of this process form part of the proposed Ethereum standard. We have checked this approach by also considering the issuance of a Commercial Fishing Licence under Part 8 “Licensing and other commercial fisheries management” of the Fisheries Management (General) Regulation 2010 (NSW) (Fisheries Regulations) made pursuant to the Fisheries Management Act 1994 (NSW) (Fisheries Act). - -## Implementation - -The issuance and ownership of a Licence can be digitally represented on the Ethereum blockchain. - -Smart contracts can be used to embed regulatory requirements with respect to the relevant Licence in the blockchain. The Licence would be available electronically in the form of a token. This might be practically represented by a QR code, for example, displaying the current Licence information. The digital representation of the Licence would be stored in a digital wallet, typically an application on a smartphone or tablet computer. The proposed standard allows issuing authorities or regulators to amend, revoke or deny Licences from time to time, with the result of their determinations reflected in the Licence token in near real-time. Licence holders will therefore be notified almost instantly of any amendments, revocations or issues involving their Licence. - -## Interface - -### Solidity Example -```solidity -interface EIP1753 { - - function grantAuthority(address who) external; - function revokeAuthority(address who) external; - function hasAuthority(address who) external view returns (bool); - - function issue(address who, uint256 from, uint256 to) external; - function revoke(address who) external; - - function hasValid(address who) external view returns (bool); - function purchase(uint256 validFrom, uint256 validTo) external payable; -} - -pragma solidity ^0.5.3; - -contract EIP is EIP1753 { - - string public name = "Kakadu National Park Camping Permit"; - uint256 public totalSupply; - - address private _owner; - mapping(address => bool) private _authorities; - mapping(address => Permit) private _holders; - - struct Permit { - address issuer; - uint256 validFrom; - uint256 validTo; - } - - constructor() public { - _owner = msg.sender; - } - - function grantAuthority(address who) public onlyOwner() { - _authorities[who] = true; - } - - function revokeAuthority(address who) public onlyOwner() { - delete _authorities[who]; - } - - function hasAuthority(address who) public view returns (bool) { - return _authorities[who] == true; - } - - function issue(address who, uint256 start, uint256 end) public onlyAuthority() { - _holders[who] = Permit(_owner, start, end); - totalSupply += 1; - } - - function revoke(address who) public onlyAuthority() { - delete _holders[who]; - } - - function hasValid(address who) external view returns (bool) { - return _holders[who].validFrom > now && _holders[who].validTo < now; - } - - function purchase(uint256 validFrom, uint256 validTo) external payable { - require(msg.value == 1 ether, "Incorrect fee"); - issue(msg.sender, validFrom, validTo); - } - - modifier onlyOwner() { - require(msg.sender == _owner, "Only owner can perform this function"); - _; - } - - modifier onlyAuthority() { - require(hasAuthority(msg.sender), "Only an authority can perform this function"); - _; - } -} -``` - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1753.md diff --git a/EIPS/eip-1761.md b/EIPS/eip-1761.md index 141836829d66a0..d89f3fd97d6263 100644 --- a/EIPS/eip-1761.md +++ b/EIPS/eip-1761.md @@ -1,175 +1 @@ ---- -eip: 1761 -title: Scoped Approval Interface -author: Witek Radomski , Andrew Cooke , James Therien , Eric Binet -type: Standards Track -category: ERC -status: Stagnant -created: 2019-02-18 -discussions-to: https://github.com/ethereum/EIPs/issues/1761 -requires: 165 ---- - -## Simple Summary - -A standard interface to permit restricted approval in token contracts by defining "scopes" of one or more Token IDs. - -## Abstract - -This interface is designed for use with token contracts that have an "ID" domain, such as ERC-1155 or ERC-721. This enables restricted approval of one or more Token IDs to a specific "scope". When considering a smart contract managing tokens from multiple different domains, it makes sense to limit approvals to those domains. Scoped approval is a generalization of this idea. Implementors can define scopes as needed. - -Sample use cases for scopes: - -* A company may represent its fleet of vehicles on the blockchain and it could create a scope for each regional office. -* Game developers could share an [ERC-1155](./eip-1155.md) contract where each developer manages tokens under a specified scope. -* Tokens of different value could be split into separate scopes. High-value tokens could be kept in smaller separate scopes while low-value tokens might be kept in a shared scope. Users would approve the entire low-value token scope to a third-party smart contract, exchange, or other application without concern about losing their high-value tokens in the event of a problem. - -## Motivation - -It may be desired to restrict approval in some applications. Restricted approval can prevent losses in cases where users do not audit the contracts they're approving. No standard API is supplied to manage scopes as this is implementation specific. Some implementations may opt to offer a fixed number of scopes, or assign a specific set of scopes to certain types. Other implementations may open up scope configuration to its users and offer methods to create scopes and assign IDs to them. - -# Specification - -```solidity -pragma solidity ^0.5.2; - -/** - Note: The ERC-165 identifier for this interface is 0x30168307. -*/ -interface ScopedApproval { - /** - @dev MUST emit when approval changes for scope. - */ - event ApprovalForScope(address indexed _owner, address indexed _operator, bytes32 indexed _scope, bool _approved); - - /** - @dev MUST emit when the token IDs are added to the scope. - By default, IDs are in no scope. - The range is inclusive: _idStart, _idEnd, and all IDs in between have been added to the scope. - _idStart must be lower than or equal to _idEnd. - */ - event IdsAddedToScope(uint256 indexed _idStart, uint256 indexed _idEnd, bytes32 indexed _scope); - - /** - @dev MUST emit when the token IDs are removed from the scope. - The range is inclusive: _idStart, _idEnd, and all IDs in between have been removed from the scope. - _idStart must be lower than or equal to _idEnd. - */ - event IdsRemovedFromScope(uint256 indexed _idStart, uint256 indexed _idEnd, bytes32 indexed _scope); - - /** @dev MUST emit when a scope URI is set or changes. - URIs are defined in RFC 3986. - The URI MUST point a JSON file that conforms to the "Scope Metadata JSON Schema". - */ - event ScopeURI(string _value, bytes32 indexed _scope); - - /** - @notice Returns the number of scopes that contain _id. - @param _id The token ID - @return The number of scopes containing the ID - */ - function scopeCountForId(uint256 _id) public view returns (uint32); - - /** - @notice Returns a scope that contains _id. - @param _id The token ID - @param _scopeIndex The scope index to query (valid values are 0 to scopeCountForId(_id)-1) - @return The Nth scope containing the ID - */ - function scopeForId(uint256 _id, uint32 _scopeIndex) public view returns (bytes32); - - /** - @notice Returns a URI that can be queried to get scope metadata. This URI should return a JSON document containing, at least the scope name and description. Although supplying a URI for every scope is recommended, returning an empty string "" is accepted for scopes without a URI. - @param _scope The queried scope - @return The URI describing this scope. - */ - function scopeUri(bytes32 _scope) public view returns (string memory); - - /** - @notice Enable or disable approval for a third party ("operator") to manage the caller's tokens in the specified scope. - @dev MUST emit the ApprovalForScope event on success. - @param _operator Address to add to the set of authorized operators - @param _scope Approval scope (can be identified by calling scopeForId) - @param _approved True if the operator is approved, false to revoke approval - */ - function setApprovalForScope(address _operator, bytes32 _scope, bool _approved) external; - - /** - @notice Queries the approval status of an operator for a given owner, within the specified scope. - @param _owner The owner of the Tokens - @param _operator Address of authorized operator - @param _scope Scope to test for approval (can be identified by calling scopeForId) - @return True if the operator is approved, false otherwise - */ - function isApprovedForScope(address _owner, address _operator, bytes32 _scope) public view returns (bool); -} -``` - -## Scope Metadata JSON Schema - -This schema allows for localization. `{id}` and `{locale}` should be replaced with the appropriate values by clients. - -```json -{ - "title": "Scope Metadata", - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "description": "Identifies the scope in a human-readable way.", - }, - "description": { - "type": "string", - "description": "Describes the scope to allow users to make informed approval decisions.", - }, - "localization": { - "type": "object", - "required": ["uri", "default", "locales"], - "properties": { - "uri": { - "type": "string", - "description": "The URI pattern to fetch localized data from. This URI should contain the substring `{locale}` which will be replaced with the appropriate locale value before sending the request." - }, - "default": { - "type": "string", - "description": "The locale of the default data within the base JSON" - }, - "locales": { - "type": "array", - "description": "The list of locales for which data is available. These locales should conform to those defined in the Unicode Common Locale Data Repository (http://cldr.unicode.org/)." - } - } - } - } -} -``` - -### Localization - -Metadata localization should be standardized to increase presentation uniformity across all languages. As such, a simple overlay method is proposed to enable localization. If the metadata JSON file contains a `localization` attribute, its content may be used to provide localized values for fields that need it. The `localization` attribute should be a sub-object with three attributes: `uri`, `default` and `locales`. If the string `{locale}` exists in any URI, it MUST be replaced with the chosen locale by all client software. - -## Rationale - -The initial design was proposed as an extension to ERC-1155: [Discussion Thread - Comment 1](https://github.com/ethereum/EIPs/issues/1155#issuecomment-459505728). After some discussion: [Comment 2](https://github.com/ethereum/EIPs/issues/1155#issuecomment-460603439) and suggestions by the community to implement this approval mechanism in an external contract [Comment 3](https://github.com/ethereum/EIPs/issues/1155#issuecomment-461758755), it was decided that as an interface standard, this design would allow many different token standards such as ERC-721 and ERC-1155 to implement scoped approvals without forcing the system into all implementations of the tokens. - -### Metadata JSON - -The Scope Metadata JSON Schema was added in order to support human-readable scope names and descriptions in more than one language. - -## References - -**Standards** -- [ERC-1155 Multi Token Standard](./eip-1155.md) -- [ERC-165 Standard Interface Detection](./eip-165.md) -- [JSON Schema](https://json-schema.org/) - -**Implementations** -- [Enjin Coin](https://enjincoin.io) ([github](https://github.com/enjin)) - -**Articles & Discussions** -- [GitHub - Original Discussion Thread](https://github.com/ethereum/EIPs/issues/1761) -- [GitHub - ERC-1155 Discussion Thread](https://github.com/ethereum/EIPs/issues/1155) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1761.md diff --git a/EIPS/eip-1775.md b/EIPS/eip-1775.md index 3ee41df430a617..aafded62b18c78 100644 --- a/EIPS/eip-1775.md +++ b/EIPS/eip-1775.md @@ -1,196 +1 @@ ---- -eip: 1775 -title: App Keys, application specific wallet accounts -author: Vincent Eli (@Bunjin), Dan Finlay (@DanFinlay) -discussions-to: https://ethereum-magicians.org/t/eip-erc-app-keys-application-specific-wallet-accounts/2742 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-02-20 ---- - -## Simple Summary - -Among others cryptographic applications, scalability and privacy solutions for ethereum blockchain require that an user performs a significant amount of signing operations. It may also require her to watch some state and be ready to sign data automatically (e.g. sign a state or contest a withdraw). The way wallets currently implement accounts poses several obstacles to the development of a complete web3.0 experience both in terms of UX, security and privacy. - -This proposal describes a standard and api for a new type of wallet accounts that are derived specifically for a each given application. We propose to call them `app keys`. They allow to isolate the accounts used for each application, thus potentially increasing privacy. They also allow to give more control to the applications developers over account management and signing delegation. For these app keys, wallets can have a more permissive level of security (e.g. not requesting user's confirmation) while keeping main accounts secure. Finally wallets can also implement a different behavior such as allowing to sign transactions without broadcasting them. - -This new accounts type can allow to significantly improve UX and permit new designs for applications of the crypto permissionned web. - -## Abstract -In a wallet, an user often holds most of her funds in her main accounts. These accounts require a significant level of security and should not be delegated in any way, this significantly impacts the design of cryptographic applications if a user has to manually confirm every action. Also often an user uses the same accounts across apps, which is a privacy and potentially also a security issue. - -We introduce here a new account type, app keys, which permits signing delegation and accounts isolation across applications for privacy and security. - -In this EIP, we provide a proposal on how to uniquely identify and authenticate each application, how to derive a master account (or app key) unique for the domain from an user private key (her root private key or any other private key of an account derived or not from her root one). This EIP aims at becoming a standard on how to derive keys specific to each application that can be regenerated from scratch without further input from the user if she restores her wallet and uses again the application for which this key was derived. -These app keys can then be endowed a different set of permissions (through the requestPermission model introduced in [EIP-2255](./eip-2255.md)). This will potentially allow an user to partly trust some apps to perform some crypto operations on their behalf without compromising any security with respect to her main accounts. - -## Motivation -Wallets developers have agreed on an HD derivation path for ethereum accounts using BIP32, BIP44, SLIP44, [(see the discussion here)](https://github.com/ethereum/EIPs/issues/84). Web3 wallets have implemented in a roughly similar way the rpc eth api. [EIP-1102](./eip-1102.md) introduced privacy through non automatic opt-in of a wallet account into an app increasing privacy. - -However several limitations remain in order to allow for proper design and UX for crypto permissioned apps. - -Most of GUI based current wallets don't allow to: -* being able to automatically and effortlessly use different keys / accounts for each apps, -* being able to sign some app's action without prompting the user with the same level of security as sending funds from their main accounts, -* being able to use throwable keys to improve anonymity, -* effortlessly signing transactions for an app without broadcasting these while still being able to perform other transaction signing as usual from their main accounts, -* All this while being fully restorable using the user's mnemonic or hardware wallet and the HD Path determined uniquely by the app's ens name. - -We try to overcome these limitations by introducing a new account's type, app keys, made to be used along side the existing main accounts. - -These new app keys can permit to give more power and flexibility to the crypto apps developers. This can allow to improve a lot the UX of crypto dapps and to create new designs that were not possible before leveraging the ability to create and handle many accounts, to presign messages and broadcast them later. These features were not compatible with the level of security we were requesting for main accounts that hold most of an user's funds. - - -## Specification - -### Applications - -An app is a website (or other) that would like to request from a wallet to access a cryptographic key specifically derived for this usage. It can be any form of cryptography/identity relying application, Ethereum based but not only. - -Once connected to a wallet, an application can request to access an account derived exclusively for that application using the following algorithm. - -### Private App Key generation algorithm - -We now propose an algorithm to generate application keys that: -- are uniquely defined, with respect to the account that the user selected to generate these keys, -- and thus can be isolated when changing the user account, allowing persona management (see next section), -- are specific to each application, -- can be fully restored from the user master seed mnemonic and the applications' names. - -#### Using different accounts as personas - -We allow the user to span a different set of application keys by changing the account selected to generate each key. Thus from the same master seed mnemonic, an user can use each of her account index to generate an alternative set of application keys. One can describe this as using different personas. -This would allow potentially an user to fully isolate her interaction with a given app across personas. One can use this for instance to create a personal and business profile for a given's domain both backup up from the same mnemonic, using 2 different accounts to generate these. The app or domain, will not be aware that it is the same person and mnemonic behind both. -If an application interacts with several main accounts of an user, one of these accounts, a master account can be used as persona and the others as auxiliary accounts. - -This EIP is agnostic about the way one generates the private keys used to span different app keys spaces. However for compatibility purposes and for clean disambiguation between personas and cryptocurrency accounts, a new EIP, distinct from this one but to be used alongside, will be proposed soon introducing clean persona generation and management. - -#### Applications' Unique Identifiers - -Each application is uniquely defined and authenticated by its origin, a domain string. It can be a Domain Name Service (DNS) name or, in the future, an Ethereum Name Service (ENS) name or IPFS hash. - -For Ipfs or swam origins, but we could probably use the ipfs or swarm addresses as origin or we could require those to be pointed at through an ENS entry and use the ENS address as origin, although this would mean that the content it refers to could change. It would thus allow for different security and updatibility models. - -We will probably require for protocol prefixes when using an ENS domain to point to an IPFS address: -`ens://ipfs.snap.eth` - - -#### Private App Key generation algorithm - -Using the domain name of an application, we generate a private key for each application (and per main account) : - -`const appKeyPrivKey = keccak256(privKey + originString)` - -where `+` is concatenation, `privKey` is the private key of the user's account selected to span the application key and `originString` represents the origin url from which the permission call to access the application key is originated from. - -This is exposed as an RPC method to allow any domain to request its own app key associated with the current requested account (if available): - -``` -const appKey = await provider.send({ - method: 'wallet_getAppKeyForAccount', - params: [address1] -}); -``` - -See here for an implementation: -https://github.com/MetaMask/eth-simple-keyring/blob/master/index.js#L169 - -#### App keys and Hierarchical Deterministic keys - -The app keys generated using the algorithm described in the previous section will not be BIP32 compliant. Therefore apps will not be able to create several app keys or use non-hardening and extended public keys techniques directly. They get a single private key (per origin, per persona). -Yet they can use this as initial entropy to span a new HD tree and generate addresses that can be either hardened or not. Thus we should not be losing use cases. - -## Rationale - -### Sharing application keys across domains: -While this does not explicit cover cases of sharing these app keys between pages on its own, this need can be met by composition: - -Since a domain would get a unique key per persona, and because domains can intercommunicate, one domain (app) could request another domain (signer) to perform its cryptographic operation on some data, with its appKey as a seed, potentially allowing new signing strategies to be added as easily as new websites. - -This could also pass it to domains that are loading specific signing strategies. This may sound dangerous at first, but if a domain represents a static hash of a trusted cryptographic function implementation, it could be as safe as calling any audited internal dependency. - -### Privacy and the funding trail - -If all an application needs to do with its keys is to sign messages and it does not require funding, then this EIP allows for privacy through the use of distinct keys for each application with a simple deterministic standard compatible across wallets. - -However if these application keys require funding, there can be trail and the use of app keys would not fully solve the privacy problem there. - -Mixers or anonymous ways of funding an ethereum address (ring signatures) along with this proposal would guarantee privacy. - -Even if privacy is not solved fully without this anonymous funding method, we still need a way to easily create and restore different accounts/addresses for each application - -## Backwards Compatibility -From a wallet point of view, there does not seem to be compatibility issues since these are separate accounts from those that were used previously by wallets and they are supposed to be used along-side in synergy. - -However, for applications that associated in some way their users to their main accounts may want to reflect on if and how they would like to leverage the power offered by `app keys` to migrate to them and leverage on the new app designs they permit. - -## Implementation - -Here is an early implementation of app keys for standard (non HW) MetaMask accounts. -https://github.com/MetaMask/eth-simple-keyring/blob/6d12bd9d73adcccbe0b0c7e32a99d279085e2934/index.js#L139-L152 - -See here for a fork of MetaMask that implements app keys along side plugins: -https://github.com/MetaMask/metamask-snaps-beta -https://github.com/MetaMask/metamask-snaps-beta/wiki/Plugin-API - -## Example use cases - -* signing transactions without broadcasting them -https://github.com/MetaMask/metamask-extension/issues/3475 - -* token contract -https://github.com/ethereum/EIPs/issues/85 - -* default account for dapps -https://ethereum-magicians.org/t/default-accounts-for-dapps/904 - -* non wallet/crypto accounts -[EIP1581: Non-wallet usage of keys derived from BIP32 trees](./eip-1581.md) - -* state channel application - -* privacy solution - -* non custodian cross cryptocurrency exchange... - -## Acknowledgements -MetaMask team, Christian Lundkvist, Counterfactual team, Liam Horne, Erik Bryn, Richard Moore, Jeff Coleman. - - -## References - -### HD and mnemonics -#### BIPs -* [BIP32: Hierarchical Deterministic Wallets:](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) - -* [BIP39: Mnemonic code for generating deterministic keys:](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) - -* [SLIP44: Registered coin types for BIP44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) - - -#### Derivation path for eth -* [Issue 84](https://github.com/ethereum/EIPs/issues/84) - -* [Issue 85](https://github.com/ethereum/EIPs/issues/85) - -* [EIP600 Ethereum purpose allocation for Deterministic Wallets](./eip-600.md) - - -* [EIP601 Ethereum hierarchy for deterministic wallets](./eip-601.md) - - -### Previous proposals and discussions related to app keys -* [Meta: we should value privacy more](https://ethereum-magicians.org/t/meta-we-should-value-privacy-more/2475) - -* [EIP1102: Opt-in account exposure](./eip-1102.md) - -* [EIP1581: Non-wallet usage of keys derived from BIP-32 trees](./eip-1581.md) - -* [EIP1581: discussion](https://ethereum-magicians.org/t/non-wallet-usage-of-keys-derived-from-bip-32-trees/1817/4) - -* [SLIP13: Authentication using deterministic hierarchy](https://github.com/satoshilabs/slips/blob/master/slip-0013.md) - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1775.md diff --git a/EIPS/eip-181.md b/EIPS/eip-181.md index 3c7a2311de824c..efdddc842b9cf3 100644 --- a/EIPS/eip-181.md +++ b/EIPS/eip-181.md @@ -1,209 +1 @@ ---- -eip: 181 -title: ENS support for reverse resolution of Ethereum addresses -author: Nick Johnson -status: Final -type: Standards Track -category: ERC -created: 2016-12-01 ---- - -# Abstract -This EIP specifies a TLD, registrar, and resolver interface for reverse resolution of Ethereum addresses using ENS. This permits associating a human-readable name with any Ethereum blockchain address. Resolvers can be certain that the reverse record was published by the owner of the Ethereum address in question. - -# Motivation -While name services are mostly used for forward resolution - going from human-readable identifiers to machine-readable ones - there are many use-cases in which reverse resolution is useful as well: - - - Applications that allow users to monitor accounts benefit from showing the name of an account instead of its address, even if it was originally added by address. - - Attaching metadata such as descriptive information to an address allows retrieving this information regardless of how the address was originally discovered. - - Anyone can configure a name to resolve to an address, regardless of ownership of that address. Reverse records allow the owner of an address to claim a name as authoritative for that address. - -# Specification -Reverse ENS records are stored in the ENS hierarchy in the same fashion as regular records, under a reserved domain, `addr.reverse`. To generate the ENS name for a given account's reverse records, convert the account to hexadecimal representation in lower-case, and append `addr.reverse`. For instance, the ENS registry's address at `0x112234455c3a32fd11230c42e7bccd4a84e02010` has any reverse records stored at `112234455c3a32fd11230c42e7bccd4a84e02010.addr.reverse`. - -Note that this means that contracts wanting to do dynamic reverse resolution of addresses will need to perform hex encoding in the contract. - -## Registrar -The owner of the `addr.reverse` domain will be a registrar that permits the caller to take ownership of -the reverse record for their own address. It provides the following methods: - -### function claim(address owner) returns (bytes32 node) - -When called by account `x`, instructs the ENS registry to transfer ownership of the name `hex(x) + '.addr.reverse'` to the provided address, and return the namehash of the ENS record thus transferred. - -Allowing the caller to specify an owner other than themselves for the relevant node facilitates contracts that need accurate reverse ENS entries delegating this to their creators with a minimum of code inside their constructor: - - reverseRegistrar.claim(msg.sender) - -### function claimWithResolver(address owner, address resolver) returns (bytes32 node) - -When called by account `x`, instructs the ENS registry to set the resolver of the name `hex(x) + '.addr.reverse'` to the specified resolver, then transfer ownership of the name to the provided address, and return the namehash of the ENS record thus transferred. This method facilitates setting up a custom resolver and owner in fewer transactions than would be required if calling `claim`. - -### function setName(string name) returns (bytes32 node) - -When called by account `x`, sets the resolver for the name `hex(x) + '.addr.reverse'` to a default resolver, and sets the name record on that name to the specified name. This method facilitates setting up simple reverse records for users in a single transaction. - -## Resolver interface -A new resolver interface is defined, consisting of the following method: - - function name(bytes32 node) constant returns (string); - -Resolvers that implement this interface must return a valid ENS name for the requested node, or the empty string if no name is defined for the requested node. - -The interface ID of this interface is 0x691f3431. - -Future EIPs may specify more record types appropriate to reverse ENS records. - -# Appendix 1: Registrar implementation - -This registrar, written in Solidity, implements the specifications outlined above. - - pragma solidity ^0.4.10; - - import "./AbstractENS.sol"; - - contract Resolver { - function setName(bytes32 node, string name) public; - } - - /** - * @dev Provides a default implementation of a resolver for reverse records, - * which permits only the owner to update it. - */ - contract DefaultReverseResolver is Resolver { - AbstractENS public ens; - mapping(bytes32=>string) public name; - - /** - * @dev Constructor - * @param ensAddr The address of the ENS registry. - */ - function DefaultReverseResolver(AbstractENS ensAddr) { - ens = ensAddr; - } - - /** - * @dev Only permits calls by the reverse registrar. - * @param node The node permission is required for. - */ - modifier owner_only(bytes32 node) { - require(msg.sender == ens.owner(node)); - _; - } - - /** - * @dev Sets the name for a node. - * @param node The node to update. - * @param _name The name to set. - */ - function setName(bytes32 node, string _name) public owner_only(node) { - name[node] = _name; - } - } - - contract ReverseRegistrar { - // namehash('addr.reverse') - bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; - - AbstractENS public ens; - Resolver public defaultResolver; - - /** - * @dev Constructor - * @param ensAddr The address of the ENS registry. - * @param resolverAddr The address of the default reverse resolver. - */ - function ReverseRegistrar(AbstractENS ensAddr, Resolver resolverAddr) { - ens = ensAddr; - defaultResolver = resolverAddr; - } - - /** - * @dev Transfers ownership of the reverse ENS record associated with the - * calling account. - * @param owner The address to set as the owner of the reverse record in ENS. - * @return The ENS node hash of the reverse record. - */ - function claim(address owner) returns (bytes32 node) { - return claimWithResolver(owner, 0); - } - - /** - * @dev Transfers ownership of the reverse ENS record associated with the - * calling account. - * @param owner The address to set as the owner of the reverse record in ENS. - * @param resolver The address of the resolver to set; 0 to leave unchanged. - * @return The ENS node hash of the reverse record. - */ - function claimWithResolver(address owner, address resolver) returns (bytes32 node) { - var label = sha3HexAddress(msg.sender); - node = sha3(ADDR_REVERSE_NODE, label); - var currentOwner = ens.owner(node); - - // Update the resolver if required - if(resolver != 0 && resolver != ens.resolver(node)) { - // Transfer the name to us first if it's not already - if(currentOwner != address(this)) { - ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, this); - currentOwner = address(this); - } - ens.setResolver(node, resolver); - } - - // Update the owner if required - if(currentOwner != owner) { - ens.setSubnodeOwner(ADDR_REVERSE_NODE, label, owner); - } - - return node; - } - - /** - * @dev Sets the `name()` record for the reverse ENS record associated with - * the calling account. First updates the resolver to the default reverse - * resolver if necessary. - * @param name The name to set for this address. - * @return The ENS node hash of the reverse record. - */ - function setName(string name) returns (bytes32 node) { - node = claimWithResolver(this, defaultResolver); - defaultResolver.setName(node, name); - return node; - } - - /** - * @dev Returns the node hash for a given account's reverse records. - * @param addr The address to hash - * @return The ENS node hash. - */ - function node(address addr) constant returns (bytes32 ret) { - return sha3(ADDR_REVERSE_NODE, sha3HexAddress(addr)); - } - - /** - * @dev An optimised function to compute the sha3 of the lower-case - * hexadecimal representation of an Ethereum address. - * @param addr The address to hash - * @return The SHA3 hash of the lower-case hexadecimal encoding of the - * input address. - */ - function sha3HexAddress(address addr) private returns (bytes32 ret) { - addr; ret; // Stop warning us about unused variables - assembly { - let lookup := 0x3031323334353637383961626364656600000000000000000000000000000000 - let i := 40 - loop: - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - jumpi(loop, i) - ret := sha3(0, 40) - } - } - } - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-181.md diff --git a/EIPS/eip-1812.md b/EIPS/eip-1812.md index 8ff5f0759d7e79..7c8de6bcb7f2cf 100644 --- a/EIPS/eip-1812.md +++ b/EIPS/eip-1812.md @@ -1,442 +1 @@ ---- -eip: 1812 -title: Ethereum Verifiable Claims -author: Pelle Braendgaard (@pelle) -discussions-to: https://ethereum-magicians.org/t/erc-1812-ethereum-verifiable-claims/2814 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-03-03 -requires: 712 ---- - -# Ethereum Verifiable Claims - -## Simple Summary - -Reusable Verifiable Claims using [EIP 712 Signed Typed Data](./eip-712.md). - -## Abstract -A new method for Off-Chain Verifiable Claims built on [EIP-712](./eip-712.md). These Claims can be issued by any user with a EIP 712 compatible web3 provider. Claims can be stored off chain and verified on-chain by Solidity Smart Contracts, State Channel Implementations or off-chain libraries. - -## Motivation -Reusable Off-Chain Verifiable Claims provide an important piece of integrating smart contracts with real world organizational requirements such as meeting regulatory requirements such as KYC, GDPR, Accredited Investor rules etc. - -[ERC-735](https://github.com/ethereum/EIPs/issues/735) and [ERC-780](https://github.com/ethereum/EIPs/issues/780) provide methods of making claims that live on chain. This is useful for some particular use cases, where some claim about an address must be verified on chain. - -In most cases though it is both dangerous and in some cases illegal (according to EU GDPR rules for example) to record Identity Claims containing Personal Identifying Information (PII) on an immutable public database such as the Ethereum blockchain. - -The W3C [Verifiable Claims Data Model and Representations](https://www.w3.org/TR/verifiable-claims-data-model/) as well as uPorts [Verification Message Spec](https://developer.uport.me/messages/verification) are proposed off-chain solutions. - -While built on industry standards such as [JSON-LD](https://json-ld.org) and [JWT](https://jwt.io) neither of them are easy to integrate with the Ethereum ecosystem. - -[EIP-712](./eip-712.md) introduces a new method of signing off chain Identity data. This provides both a data format based on Solidity ABI encoding that can easily be parsed on-chain an a new JSON-RPC call that is easily supported by existing Ethereum wallets and Web3 clients. - -This format allows reusable off-chain Verifiable Claims to be cheaply issued to users, who can present them when needed. - -## Prior Art -Verified Identity Claims such as those proposed by [uPort](https://developer.uport.me/messages/verification) and [W3C Verifiable Claims Working Group](https://www.w3.org/2017/vc/WG/) form an important part of building up reusable identity claims. - -[ERC-735](https://github.com/ethereum/EIPs/issues/735) and [ERC-780](https://github.com/ethereum/EIPs/issues/780) provide on-chain storage and lookups of Verifiable Claims. - -## Specification -### Claims -Claims can be generalized like this: - -> Issuer makes the claim that Subject is something or has some attribute and value. - -Claims should be deterministic, in that the same claim signed multiple times by the same signer. - -### Claims data structure -Each claim should be typed based on its specific use case, which EIP 712 lets us do effortlessly. But there are 3 minimal attributes required of the claims structure. - -* `subject` the subject of the claim as an `address` (who the claim is about) -* `validFrom` the time in seconds encoded as a `uint256` of start of validity of claim. In most cases this would be the time of issuance, but some claims may be valid in the future or past. -* `validTo` the time in seconds encoded as a `uint256` of when the validity of the claim expires. If you intend for the claim not to expire use `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`. - -The basic minimal claim data structure as a Solidity struct: - -```solidity -struct [CLAIM TYPE] { - address subject; - uint256 validFrom; - uint256 validTo; -} -``` - -The CLAIM TYPE is the actual name of the claim. While not required, in most cases use the taxonomy developed by [schema.org](https://schema.org/docs/full.html) which is also commonly used in other Verifiable Claims formats. - -Example claim that issuer knows a subject: - -```solidity -struct Know { - address subject; - uint256 validFrom; - uint256 validTo; -} -``` - -### Presenting a Verifiable Claim -#### Verifying Contract -When defining Verifiable Claims formats a Verifying Contract should be created with a public `verify()` view function. This makes it very easy for other smart contracts to verify a claim correctly. - -It also provides a convenient interface for web3 and state channel apps to verify claims securely. - -```solidity -function verifyIssuer(Know memory claim, uint8 v, bytes32 r, bytes32 s) public returns (address) { - bytes32 digest = keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - hash(claim) - ) - ); - require( - (claim.validFrom >= block.timestamp) && (block.timestamp < claim.validTo) -, "invalid issuance timestamps"); - return ecrecover(digest, v, r, s); -} -``` - -#### Calling a SmartContract function -Verifiable Claims can be presented to a solidity function call as it’s struct together with the `v`, `r` and `s` signature components. - -```solidity -function vouch(Know memory claim, uint8 v, bytes32 r, bytes32 s) public returns (bool) { - address issuer = verifier.verifyIssuer(claim, v, r, s); - require(issuer !== '0x0'); - knows[issuer][claim.subject] = block.number; - return true; -} -``` - -#### Embedding a Verifiable Claim in another Signed Typed Data structure -The Claim struct should be embedded in another struct together with the `v`, `r` and `s` signature parameters. - -```solidity -struct Know { - address subject; - uint256 validFrom; - uint256 validTo; -} - -struct VerifiableReference { - Know delegate; - uint8 v; - bytes32 r; - bytes32 s; -} - -struct Introduction { - address recipient; - VerifiableReference issuer; -} -``` - -Each Verifiable Claim should be individually verified together with the parent Signed Typed Data structure. - -Verifiable Claims issued to different EIP 712 Domains can be embedded within each other. - -#### State Channels -This proposal will not show how to use Eth Verifiable Claims as part of a specific State Channel method. - -Any State Channel based on EIP712 should be able to include the embeddable Verifiable Claims as part of its protocol. This could be useful for exchanging private Identity Claims between the parties for regulatory reasons, while ultimately not posting them to the blockchain on conclusion of a channel. - -### Key Delegation -In most simple cases the issuer of a Claim is the signer of the data. There are cases however where signing should be delegated to an intermediary key. - -KeyDelegation can be used to implement off chain signing for smart contract based addresses, server side key rotation as well as employee permissions in complex business use cases. - -#### ERC1056 Signing Delegation - -[ERC-1056](./eip-1056.md) provides a method for addresses to assign delegate signers. One of the primary use cases for this is that a smart contract can allow a key pair to sign on its behalf for a certain period. It also allows server based issuance tools to institute key rotation. - -To support this an additional `issuer` attribute can be added to the Claim Type struct. In this case the verification code should lookup the EthereumDIDRegistry to see if the signer of the data is an allowed signing delegate for the `issuer` - -The following is the minimal struct for a Claim containing an issuer: - -```solidity -struct [CLAIM TYPE] { - address subject; - address issuer; - uint256 validFrom; - uint256 validTo; -} -``` - -If the `issuer` is specified in the struct In addition to performing the standard ERC712 verification the verification code MUST also verify that the signing address is a valid `veriKey` delegate for the address specified in the issuer. - -```solidity -registry.validDelegate(issuer, 'veriKey', recoveredAddress) -``` - - -#### Embedded Delegation Proof -There may be applications, in particularly where organizations want to allow delegates to issue claims about specific domains and types. - -For this purpose instead of the `issuer` we allow a special claim to be embedded following this same format: - -```solidity -struct Delegate { - address issuer; - address subject; - uint256 validFrom; - uint256 validTo; -} - -struct VerifiableDelegate { - Delegate delegate; - uint8 v; - bytes32 r; - bytes32 s; -} - - -struct [CLAIM TYPE] { - address subject; - VerifiedDelegate issuer; - uint256 validFrom; - uint256 validTo; -} -``` - -Delegates should be created for specific EIP 712 Domains and not be reused across Domains. - -Implementers of new EIP 712 Domains can add further data to the `Delegate` struct to allow finer grained application specific rules to it. - -### Claim Types -#### Binary Claims -A Binary claim is something that doesn’t have a particular value. It either is issued or not. - -Examples: -* subject is a Person -* subject is my owner (eg. Linking an ethereum account to an owner identity) - -Example: - -```solidity -struct Person { - address issuer; - address subject; - uint256 validFrom; - uint256 validTo; -} -``` - -This is exactly the same as the minimal claim above with the CLAIM TYPE set to [Person](https://schema.org/Person). - -### Value Claims -Value claims can be used to make a claim about the subject containing a specific readable value. - -**WARNING**: Be very careful about using Value Claims as part of Smart Contract transactions. Identity Claims containing values could be a GDPR violation for the business or developer encouraging a user to post it to a public blockchain. - -Examples: -* subject’s name is Alice -* subjects average account balance is 1234555 - -Each value should use the `value` field to indicate the value. - -A Name Claim - -```solidity -struct Name { - address issuer; - address subject; - string name; - uint256 validFrom; - uint256 validTo; -} -``` - -Average Balance - -```solidity -struct AverageBalance { - address issuer; - address subject; - uint256 value; - uint256 validFrom; - uint256 validTo; -} -``` - -### Hashed Claims -Hashed claims can be used to make a claim about the subject containing the hash of a claim value. Hashes should use ethereum standard `keccak256` hashing function. - -**WARNING**: Be very careful about using Hashed Claims as part of Smart Contract transactions. Identity Claims containing hashes of known values could be a GDPR violation for the business or developer encouraging a user to post it to a public blockchain. - -Examples: -- [ ] hash of subject’s name is `keccak256(“Alice Torres”)` -- [ ] hash of subject’s email is `keccak256(“alice@example.com”)` - -Each value should use the `keccak256 ` field to indicate the hashed value. Question. The choice of using this name is that we can easily add support for future algorithms as well as maybe zkSnark proofs. - -A Name Claim - -```solidity -struct Name { - address issuer; - address subject; - bytes32 keccak256; - uint256 validFrom; - uint256 validTo; -} -``` - -Email Claim - -```solidity -struct Email { - address issuer; - address subject; - bytes32 keccak256; - uint256 validFrom; - uint256 validTo; -} -``` - -### EIP 712 Domain -The EIP 712 Domain specifies what kind of message that is to be signed and is used to differentiate between signed data types. The content MUST contain the following: - -```solidity -{ - name: "EIP1???Claim", - version: 1, - chainId: 1, // for mainnet - verifyingContract: 0x // TBD - salt: ... -} -``` - -#### Full Combined format for EIP 712 signing: - -Following the EIP 712 standard we can combine the Claim Type with the EIP 712 Domain and the claim itself (in the `message`) attribute. - -Eg: -```solidity - { - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Email": [ - { - "name": "subject", - "type": "address" - }, - { - "name": "keccak256", - "type": "bytes32" - }, - { - "name": "validFrom", - "type": "uint256" - }, - { - "name": "validTo", - "type": "uint256" - } - ] - }, - "primaryType": "Email", - "domain": { - "name": "EIP1??? Claim", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" - }, - "message": { - "subject": "0x5792e817336f41de1d8f54feab4bc200624a1d9d", - "value": "9c8465d9ae0b0bc167dee7f62880034f59313100a638dcc86a901956ea52e280", - "validFrom": "0x0000000000000000000000000000000000000000000000000001644b74c2a0", - "validTo": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - } - } -``` - - -### Revocation -Both Issuers and Subjects should be allowed to revoke Verifiable Claims. Revocations can be handled through a simple on-chain registry. - -The ultimate rules of who should be able to revoke a claim is determined by the Verifying contract. - -The `digest` used for revocation is the EIP712 Signed Typed Data digest. - -```solidity -contract RevocationRegistry { - mapping (bytes32 => mapping (address => uint)) public revocations; - - function revoke(bytes32 digest) public returns (bool) { - revocations[digest][msg.sender] = block.number; - return true; - } - - function revoked(address party, bytes32 digest) public view returns (bool) { - return revocations[digest][party] > 0; - } -} -``` - -A verifying contract can query the Revocation Registry as such: - -```solidity -bytes32 digest = keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - hash(claim) - ) -); -require(valid(claim.validFrom, claim.validTo), "invalid issuance timestamps"); -address issuer = ecrecover(digest, v, r, s); -require(!revocations.revoked(issuer, digest), "claim was revoked by issuer"); -require(!revocations.revoked(claim.subject, digest), "claim was revoked by subject"); -``` - -### Creation of Verifiable Claims Domains - -Creating specific is Verifiable Claims Domains is out of the scope of this EIP. The Example Code has a few examples. - -EIP’s or another process could be used to standardize specific important Domains that are universally useful across the Ethereum world. - -## Rationale -Signed Typed Data provides a strong foundation for Verifiable Claims that can be used in many different kinds of applications built on both Layer 1 and Layer 2 of Ethereum. - -### Rationale for using not using a single EIP 712 Domain -EIP712 supports complex types and domains in itself, that we believe are perfect building blocks for building Verifiable Claims for specific purposes. - -The Type and Domain of a Claim is itself an important part of a claim and ensures that Verifiable Claims are used for the specific purposes required and not misused. - -EIP712 Domains also allow rapid experimentation, allowing taxonomies to be built up by the community. - -## Test Cases -There is a repo with a few example verifiers and consuming smart contracts written in Solidity: - -**Example Verifiers** -* [Verifier for very simple IdVerification Verifiable Claims containing minimal Personal Data](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/IdentityClaimsVerifier.sol) -* [Verifier for OwnershipProofs signed by a users wallet](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/OwnershipProofVerifier.sol) - -**Example Smart Contracts** -* [KYCCoin.sol](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/KYCCoin.sol) - Example Token allows reusable IdVerification claims issued by trusted verifiers and users to whitelist their own addresses using OwnershipProofs -* [ConsortiumAgreement.sol](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/ConsortiumAgreements.sol) - Example Consortium Agreement smart contract. Consortium Members can issue Delegated Claims to employees or servers to interact on their behalf. - -**Shared Registries** -* [RevocationRegistry.sol](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/RevocationRegistry.sol) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1812.md diff --git a/EIPS/eip-1820.md b/EIPS/eip-1820.md index 31240f50b43df4..7e0456a41683bd 100644 --- a/EIPS/eip-1820.md +++ b/EIPS/eip-1820.md @@ -1,928 +1 @@ ---- -eip: 1820 -title: Pseudo-introspection Registry Contract -author: Jordi Baylina , Jacques Dafflon -discussions-to: https://github.com/ethereum/EIPs/pull/1820 -status: Final -type: Standards Track -category: ERC -requires: 165, 214 -created: 2019-03-04 ---- - -> :information_source: **[ERC-1820] has superseded [ERC-820].** :information_source: -> [ERC-1820] fixes the incompatibility in the [ERC-165] logic which was introduced by the Solidity 0.5 update. -> Have a look at the [official announcement][erc1820-annoucement], and the comments about the [bug][erc820-bug] and the [fix][erc820-fix]. -> Apart from this fix, [ERC-1820] is functionally equivalent to [ERC-820]. -> -> :warning: [ERC-1820] MUST be used in lieu of [ERC-820]. :warning: - -## Simple Summary - -This standard defines a universal registry smart contract where any address (contract or regular account) can register which interface it supports and which smart contract is responsible for its implementation. - -This standard keeps backward compatibility with [ERC-165]. - -## Abstract - -This standard defines a registry where smart contracts and regular accounts can publish which functionality they implement---either directly or through a proxy contract. - -Anyone can query this registry to ask if a specific address implements a given interface and which smart contract handles its implementation. - -This registry MAY be deployed on any chain and shares the same address on all chains. - -Interfaces with zeroes (`0`) as the last 28 bytes are considered [ERC-165] interfaces, -and this registry SHALL forward the call to the contract to see if it implements the interface. - -This contract also acts as an [ERC-165] cache to reduce gas consumption. - -## Motivation - -There have been different approaches to define pseudo-introspection in Ethereum. -The first is [ERC-165] which has the limitation that it cannot be used by regular accounts. -The second attempt is [ERC-672] which uses reverse [ENS]. Using reverse [ENS] has two issues. -First, it is unnecessarily complicated, and second, [ENS] is still a centralized contract controlled by a multisig. -This multisig theoretically would be able to modify the system. - -This standard is much simpler than [ERC-672], and it is *fully* decentralized. - -This standard also provides a *unique* address for all chains. -Thus solving the problem of resolving the correct registry address for different chains. - -## Specification - -### [ERC-1820] Registry Smart Contract - -> This is an exact copy of the code of the [ERC1820 registry smart contract]. - -``` solidity -/* ERC1820 Pseudo-introspection Registry Contract - * This standard defines a universal registry smart contract where any address (contract or regular account) can - * register which interface it supports and which smart contract is responsible for its implementation. - * - * Written in 2019 by Jordi Baylina and Jacques Dafflon - * - * To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to - * this software to the public domain worldwide. This software is distributed without any warranty. - * - * You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see - * . - * - * ███████╗██████╗ ██████╗ ██╗ █████╗ ██████╗ ██████╗ - * ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗ - * █████╗ ██████╔╝██║ ╚██║╚█████╔╝ █████╔╝██║██╔██║ - * ██╔══╝ ██╔══██╗██║ ██║██╔══██╗██╔═══╝ ████╔╝██║ - * ███████╗██║ ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝ - * ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝ - * - * ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗ - * ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝ - * ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝ - * ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝ - * ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║ - * ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ - * - */ -pragma solidity 0.5.3; -// IV is value needed to have a vanity address starting with '0x1820'. -// IV: 53759 - -/// @dev The interface a contract MUST implement if it is the implementer of -/// some (other) interface for any address other than itself. -interface ERC1820ImplementerInterface { - /// @notice Indicates whether the contract implements the interface 'interfaceHash' for the address 'addr' or not. - /// @param interfaceHash keccak256 hash of the name of the interface - /// @param addr Address for which the contract will implement the interface - /// @return ERC1820_ACCEPT_MAGIC only if the contract implements 'interfaceHash' for the address 'addr'. - function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32); -} - - -/// @title ERC1820 Pseudo-introspection Registry Contract -/// @author Jordi Baylina and Jacques Dafflon -/// @notice This contract is the official implementation of the ERC1820 Registry. -/// @notice For more details, see https://eips.ethereum.org/EIPS/eip-1820 -contract ERC1820Registry { - /// @notice ERC165 Invalid ID. - bytes4 constant internal INVALID_ID = 0xffffffff; - /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`). - bytes4 constant internal ERC165ID = 0x01ffc9a7; - /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address. - bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")); - - /// @notice mapping from addresses and interface hashes to their implementers. - mapping(address => mapping(bytes32 => address)) internal interfaces; - /// @notice mapping from addresses to their manager. - mapping(address => address) internal managers; - /// @notice flag for each address and erc165 interface to indicate if it is cached. - mapping(address => mapping(bytes4 => bool)) internal erc165Cached; - - /// @notice Indicates a contract is the 'implementer' of 'interfaceHash' for 'addr'. - event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer); - /// @notice Indicates 'newManager' is the address of the new manager for 'addr'. - event ManagerChanged(address indexed addr, address indexed newManager); - - /// @notice Query if an address implements an interface and through which contract. - /// @param _addr Address being queried for the implementer of an interface. - /// (If '_addr' is the zero address then 'msg.sender' is assumed.) - /// @param _interfaceHash Keccak256 hash of the name of the interface as a string. - /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface. - /// @return The address of the contract which implements the interface '_interfaceHash' for '_addr' - /// or '0' if '_addr' did not register an implementer for this interface. - function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) { - address addr = _addr == address(0) ? msg.sender : _addr; - if (isERC165Interface(_interfaceHash)) { - bytes4 erc165InterfaceHash = bytes4(_interfaceHash); - return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0); - } - return interfaces[addr][_interfaceHash]; - } - - /// @notice Sets the contract which implements a specific interface for an address. - /// Only the manager defined for that address can set it. - /// (Each address is the manager for itself until it sets a new manager.) - /// @param _addr Address for which to set the interface. - /// (If '_addr' is the zero address then 'msg.sender' is assumed.) - /// @param _interfaceHash Keccak256 hash of the name of the interface as a string. - /// E.g., 'web3.utils.keccak256("ERC777TokensRecipient")' for the 'ERC777TokensRecipient' interface. - /// @param _implementer Contract address implementing '_interfaceHash' for '_addr'. - function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external { - address addr = _addr == address(0) ? msg.sender : _addr; - require(getManager(addr) == msg.sender, "Not the manager"); - - require(!isERC165Interface(_interfaceHash), "Must not be an ERC165 hash"); - if (_implementer != address(0) && _implementer != msg.sender) { - require( - ERC1820ImplementerInterface(_implementer) - .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC, - "Does not implement the interface" - ); - } - interfaces[addr][_interfaceHash] = _implementer; - emit InterfaceImplementerSet(addr, _interfaceHash, _implementer); - } - - /// @notice Sets '_newManager' as manager for '_addr'. - /// The new manager will be able to call 'setInterfaceImplementer' for '_addr'. - /// @param _addr Address for which to set the new manager. - /// @param _newManager Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.) - function setManager(address _addr, address _newManager) external { - require(getManager(_addr) == msg.sender, "Not the manager"); - managers[_addr] = _newManager == _addr ? address(0) : _newManager; - emit ManagerChanged(_addr, _newManager); - } - - /// @notice Get the manager of an address. - /// @param _addr Address for which to return the manager. - /// @return Address of the manager for a given address. - function getManager(address _addr) public view returns(address) { - // By default the manager of an address is the same address - if (managers[_addr] == address(0)) { - return _addr; - } else { - return managers[_addr]; - } - } - - /// @notice Compute the keccak256 hash of an interface given its name. - /// @param _interfaceName Name of the interface. - /// @return The keccak256 hash of an interface name. - function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) { - return keccak256(abi.encodePacked(_interfaceName)); - } - - /* --- ERC165 Related Functions --- */ - /* --- Developed in collaboration with William Entriken. --- */ - - /// @notice Updates the cache with whether the contract implements an ERC165 interface or not. - /// @param _contract Address of the contract for which to update the cache. - /// @param _interfaceId ERC165 interface for which to update the cache. - function updateERC165Cache(address _contract, bytes4 _interfaceId) external { - interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache( - _contract, _interfaceId) ? _contract : address(0); - erc165Cached[_contract][_interfaceId] = true; - } - - /// @notice Checks whether a contract implements an ERC165 interface or not. - // If the result is not cached a direct lookup on the contract address is performed. - // If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling - // 'updateERC165Cache' with the contract address. - /// @param _contract Address of the contract to check. - /// @param _interfaceId ERC165 interface to check. - /// @return True if '_contract' implements '_interfaceId', false otherwise. - function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) { - if (!erc165Cached[_contract][_interfaceId]) { - return implementsERC165InterfaceNoCache(_contract, _interfaceId); - } - return interfaces[_contract][_interfaceId] == _contract; - } - - /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. - /// @param _contract Address of the contract to check. - /// @param _interfaceId ERC165 interface to check. - /// @return True if '_contract' implements '_interfaceId', false otherwise. - function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) { - uint256 success; - uint256 result; - - (success, result) = noThrowCall(_contract, ERC165ID); - if (success == 0 || result == 0) { - return false; - } - - (success, result) = noThrowCall(_contract, INVALID_ID); - if (success == 0 || result != 0) { - return false; - } - - (success, result) = noThrowCall(_contract, _interfaceId); - if (success == 1 && result == 1) { - return true; - } - return false; - } - - /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not. - /// @param _interfaceHash The hash to check. - /// @return True if '_interfaceHash' is an ERC165 interface (ending with 28 zeroes), false otherwise. - function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) { - return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0; - } - - /// @dev Make a call on a contract without throwing if the function does not exist. - function noThrowCall(address _contract, bytes4 _interfaceId) - internal view returns (uint256 success, uint256 result) - { - bytes4 erc165ID = ERC165ID; - - assembly { - let x := mload(0x40) // Find empty storage location using "free memory pointer" - mstore(x, erc165ID) // Place signature at beginning of empty storage - mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature - - success := staticcall( - 30000, // 30k gas - _contract, // To addr - x, // Inputs are stored at location x - 0x24, // Inputs are 36 (4 + 32) bytes long - x, // Store output over input (saves space) - 0x20 // Outputs are 32 bytes long - ) - - result := mload(x) // Load the result - } - } -} - -``` - -### Deployment Transaction - -Below is the raw transaction which MUST be used to deploy the smart contract on any chain. - -``` -0xf90a388085174876e800830c35008080b909e5608060405234801561001057600080fd5b506109c5806100206000396000f3fe608060405234801561001057600080fd5b50600436106100a5576000357c010000000000000000000000000000000000000000000000000000000090048063a41e7d5111610078578063a41e7d51146101d4578063aabbb8ca1461020a578063b705676514610236578063f712f3e814610280576100a5565b806329965a1d146100aa5780633d584063146100e25780635df8122f1461012457806365ba36c114610152575b600080fd5b6100e0600480360360608110156100c057600080fd5b50600160a060020a038135811691602081013591604090910135166102b6565b005b610108600480360360208110156100f857600080fd5b5035600160a060020a0316610570565b60408051600160a060020a039092168252519081900360200190f35b6100e06004803603604081101561013a57600080fd5b50600160a060020a03813581169160200135166105bc565b6101c26004803603602081101561016857600080fd5b81019060208101813564010000000081111561018357600080fd5b82018360208201111561019557600080fd5b803590602001918460018302840111640100000000831117156101b757600080fd5b5090925090506106b3565b60408051918252519081900360200190f35b6100e0600480360360408110156101ea57600080fd5b508035600160a060020a03169060200135600160e060020a0319166106ee565b6101086004803603604081101561022057600080fd5b50600160a060020a038135169060200135610778565b61026c6004803603604081101561024c57600080fd5b508035600160a060020a03169060200135600160e060020a0319166107ef565b604080519115158252519081900360200190f35b61026c6004803603604081101561029657600080fd5b508035600160a060020a03169060200135600160e060020a0319166108aa565b6000600160a060020a038416156102cd57836102cf565b335b9050336102db82610570565b600160a060020a031614610339576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6103428361092a565b15610397576040805160e560020a62461bcd02815260206004820152601a60248201527f4d757374206e6f7420626520616e204552433136352068617368000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103b85750600160a060020a0382163314155b156104ff5760405160200180807f455243313832305f4143434550545f4d4147494300000000000000000000000081525060140190506040516020818303038152906040528051906020012082600160a060020a031663249cb3fa85846040518363ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018083815260200182600160a060020a0316600160a060020a031681526020019250505060206040518083038186803b15801561047e57600080fd5b505afa158015610492573d6000803e3d6000fd5b505050506040513d60208110156104a857600080fd5b5051146104ff576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03818116600090815260016020526040812054909116151561059a5750806105b7565b50600160a060020a03808216600090815260016020526040902054165b919050565b336105c683610570565b600160a060020a031614610624576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146106435780610646565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b600082826040516020018083838082843780830192505050925050506040516020818303038152906040528051906020012090505b92915050565b6106f882826107ef565b610703576000610705565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b600080600160a060020a038416156107905783610792565b335b905061079d8361092a565b156107c357826107ad82826108aa565b6107b85760006107ba565b815b925050506106e8565b600160a060020a0390811660009081526020818152604080832086845290915290205416905092915050565b6000808061081d857f01ffc9a70000000000000000000000000000000000000000000000000000000061094c565b909250905081158061082d575080155b1561083d576000925050506106e8565b61084f85600160e060020a031961094c565b909250905081158061086057508015155b15610870576000925050506106e8565b61087a858561094c565b909250905060018214801561088f5750806001145b1561089f576001925050506106e8565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108f2576108eb83836107ef565b90506106e8565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160248189617530fa90519096909550935050505056fea165627a7a72305820377f4a2d4301ede9949f163f319021a6e9c687c292a5e2b2c4734c126b524e6c00291ba01820182018201820182018201820182018201820182018201820182018201820a01820182018201820182018201820182018201820182018201820182018201820 -``` - -The strings of `1820`'s at the end of the transaction are the `r` and `s` of the signature. -From this deterministic pattern (generated by a human), anyone can deduce that no one knows the private key for the deployment account. - -### Deployment Method - -This contract is going to be deployed using the keyless deployment method---also known as [Nick]'s method---which relies on a single-use address. -(See [Nick's article] for more details). This method works as follows: - -1. Generate a transaction which deploys the contract from a new random account. - - This transaction MUST NOT use [EIP-155] in order to work on any chain. - - This transaction MUST have a relatively high gas price to be deployed on any chain. In this case, it is going to be 100 Gwei. - -2. Set the `v`, `r`, `s` of the transaction signature to the following values: - - ``` - v: 27, - r: 0x1820182018201820182018201820182018201820182018201820182018201820' - s: 0x1820182018201820182018201820182018201820182018201820182018201820' - ``` - - Those `r` and `s` values---made of a repeating pattern of `1820`'s---are predictable "random numbers" generated deterministically by a human. - -3. We recover the sender of this transaction, i.e., the single-use deployment account. - - > Thus we obtain an account that can broadcast that transaction, but we also have the warranty that nobody knows the private key of that account. - -4. Send exactly 0.08 ether to this single-use deployment account. - -5. Broadcast the deployment transaction. - -This operation can be done on any chain, guaranteeing that the contract address is always the same and nobody can use that address with a different contract. - - -### Single-use Registry Deployment Account - -``` -0xa990077c3205cbDf861e17Fa532eeB069cE9fF96 -``` - -This account is generated by reverse engineering it from its signature for the transaction. -This way no one knows the private key, but it is known that it is the valid signer of the deployment transaction. - -> To deploy the registry, 0.08 ether MUST be sent to this account *first*. - -### Registry Contract Address - -``` -0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 -``` - -The contract has the address above for every chain on which it is deployed. - -
-Raw metadata of ./contracts/ERC1820Registry.sol - -```json -{ - "compiler": { - "version": "0.5.3+commit.10d17f24" - }, - "language": "Solidity", - "output": { - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_addr", - "type": "address" - }, - { - "name": "_interfaceHash", - "type": "bytes32" - }, - { - "name": "_implementer", - "type": "address" - } - ], - "name": "setInterfaceImplementer", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_addr", - "type": "address" - } - ], - "name": "getManager", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_addr", - "type": "address" - }, - { - "name": "_newManager", - "type": "address" - } - ], - "name": "setManager", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_interfaceName", - "type": "string" - } - ], - "name": "interfaceHash", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_contract", - "type": "address" - }, - { - "name": "_interfaceId", - "type": "bytes4" - } - ], - "name": "updateERC165Cache", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_addr", - "type": "address" - }, - { - "name": "_interfaceHash", - "type": "bytes32" - } - ], - "name": "getInterfaceImplementer", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_contract", - "type": "address" - }, - { - "name": "_interfaceId", - "type": "bytes4" - } - ], - "name": "implementsERC165InterfaceNoCache", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_contract", - "type": "address" - }, - { - "name": "_interfaceId", - "type": "bytes4" - } - ], - "name": "implementsERC165Interface", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "addr", - "type": "address" - }, - { - "indexed": true, - "name": "interfaceHash", - "type": "bytes32" - }, - { - "indexed": true, - "name": "implementer", - "type": "address" - } - ], - "name": "InterfaceImplementerSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "addr", - "type": "address" - }, - { - "indexed": true, - "name": "newManager", - "type": "address" - } - ], - "name": "ManagerChanged", - "type": "event" - } - ], - "devdoc": { - "author": "Jordi Baylina and Jacques Dafflon", - "methods": { - "getInterfaceImplementer(address,bytes32)": { - "params": { - "_addr": "Address being queried for the implementer of an interface. (If '_addr' is the zero address then 'msg.sender' is assumed.)", - "_interfaceHash": "Keccak256 hash of the name of the interface as a string. E.g., 'web3.utils.keccak256(\"ERC777TokensRecipient\")' for the 'ERC777TokensRecipient' interface." - }, - "return": "The address of the contract which implements the interface '_interfaceHash' for '_addr' or '0' if '_addr' did not register an implementer for this interface." - }, - "getManager(address)": { - "params": { - "_addr": "Address for which to return the manager." - }, - "return": "Address of the manager for a given address." - }, - "implementsERC165Interface(address,bytes4)": { - "params": { - "_contract": "Address of the contract to check.", - "_interfaceId": "ERC165 interface to check." - }, - "return": "True if '_contract' implements '_interfaceId', false otherwise." - }, - "implementsERC165InterfaceNoCache(address,bytes4)": { - "params": { - "_contract": "Address of the contract to check.", - "_interfaceId": "ERC165 interface to check." - }, - "return": "True if '_contract' implements '_interfaceId', false otherwise." - }, - "interfaceHash(string)": { - "params": { - "_interfaceName": "Name of the interface." - }, - "return": "The keccak256 hash of an interface name." - }, - "setInterfaceImplementer(address,bytes32,address)": { - "params": { - "_addr": "Address for which to set the interface. (If '_addr' is the zero address then 'msg.sender' is assumed.)", - "_implementer": "Contract address implementing '_interfaceHash' for '_addr'.", - "_interfaceHash": "Keccak256 hash of the name of the interface as a string. E.g., 'web3.utils.keccak256(\"ERC777TokensRecipient\")' for the 'ERC777TokensRecipient' interface." - } - }, - "setManager(address,address)": { - "params": { - "_addr": "Address for which to set the new manager.", - "_newManager": "Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.)" - } - }, - "updateERC165Cache(address,bytes4)": { - "params": { - "_contract": "Address of the contract for which to update the cache.", - "_interfaceId": "ERC165 interface for which to update the cache." - } - } - }, - "title": "ERC1820 Pseudo-introspection Registry Contract" - }, - "userdoc": { - "methods": { - "getInterfaceImplementer(address,bytes32)": { - "notice": "Query if an address implements an interface and through which contract." - }, - "getManager(address)": { - "notice": "Get the manager of an address." - }, - "implementsERC165InterfaceNoCache(address,bytes4)": { - "notice": "Checks whether a contract implements an ERC165 interface or not without using nor updating the cache." - }, - "interfaceHash(string)": { - "notice": "Compute the keccak256 hash of an interface given its name." - }, - "setInterfaceImplementer(address,bytes32,address)": { - "notice": "Sets the contract which implements a specific interface for an address. Only the manager defined for that address can set it. (Each address is the manager for itself until it sets a new manager.)" - }, - "setManager(address,address)": { - "notice": "Sets '_newManager' as manager for '_addr'. The new manager will be able to call 'setInterfaceImplementer' for '_addr'." - }, - "updateERC165Cache(address,bytes4)": { - "notice": "Updates the cache with whether the contract implements an ERC165 interface or not." - } - }, - "notice": "This contract is the official implementation of the ERC1820 Registry.For more details, see https://eips.ethereum.org/EIPS/eip-1820" - } - }, - "settings": { - "compilationTarget": { - "./contracts/ERC1820Registry.sol": "ERC1820Registry" - }, - "evmVersion": "byzantium", - "libraries": {}, - "optimizer": { - "enabled": true, - "runs": 200 - }, - "remappings": [] - }, - "sources": { - "./contracts/ERC1820Registry.sol": { - "content": "/* ERC1820 Pseudo-introspection Registry Contract\n * This standard defines a universal registry smart contract where any address (contract or regular account) can\n * register which interface it supports and which smart contract is responsible for its implementation.\n *\n * Written in 2019 by Jordi Baylina and Jacques Dafflon\n *\n * To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to\n * this software to the public domain worldwide. This software is distributed without any warranty.\n *\n * You should have received a copy of the CC0 Public Domain Dedication along with this software. If not, see\n * .\n *\n * ███████╗██████╗ ██████╗ ██╗ █████╗ ██████╗ ██████╗\n * ██╔════╝██╔══██╗██╔════╝███║██╔══██╗╚════██╗██╔═████╗\n * █████╗ ██████╔╝██║ ╚██║╚█████╔╝ █████╔╝██║██╔██║\n * ██╔══╝ ██╔══██╗██║ ██║██╔══██╗██╔═══╝ ████╔╝██║\n * ███████╗██║ ██║╚██████╗ ██║╚█████╔╝███████╗╚██████╔╝\n * ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚════╝ ╚══════╝ ╚═════╝\n *\n * ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗\n * ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝\n * ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝\n * ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝\n * ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║\n * ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝\n *\n */\npragma solidity 0.5.3;\n// IV is value needed to have a vanity address starting with '0x1820'.\n// IV: 53759\n\n/// @dev The interface a contract MUST implement if it is the implementer of\n/// some (other) interface for any address other than itself.\ninterface ERC1820ImplementerInterface {\n /// @notice Indicates whether the contract implements the interface 'interfaceHash' for the address 'addr' or not.\n /// @param interfaceHash keccak256 hash of the name of the interface\n /// @param addr Address for which the contract will implement the interface\n /// @return ERC1820_ACCEPT_MAGIC only if the contract implements 'interfaceHash' for the address 'addr'.\n function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);\n}\n\n\n/// @title ERC1820 Pseudo-introspection Registry Contract\n/// @author Jordi Baylina and Jacques Dafflon\n/// @notice This contract is the official implementation of the ERC1820 Registry.\n/// @notice For more details, see https://eips.ethereum.org/EIPS/eip-1820\ncontract ERC1820Registry {\n /// @notice ERC165 Invalid ID.\n bytes4 constant internal INVALID_ID = 0xffffffff;\n /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`).\n bytes4 constant internal ERC165ID = 0x01ffc9a7;\n /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address.\n bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked(\"ERC1820_ACCEPT_MAGIC\"));\n\n /// @notice mapping from addresses and interface hashes to their implementers.\n mapping(address => mapping(bytes32 => address)) internal interfaces;\n /// @notice mapping from addresses to their manager.\n mapping(address => address) internal managers;\n /// @notice flag for each address and erc165 interface to indicate if it is cached.\n mapping(address => mapping(bytes4 => bool)) internal erc165Cached;\n\n /// @notice Indicates a contract is the 'implementer' of 'interfaceHash' for 'addr'.\n event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);\n /// @notice Indicates 'newManager' is the address of the new manager for 'addr'.\n event ManagerChanged(address indexed addr, address indexed newManager);\n\n /// @notice Query if an address implements an interface and through which contract.\n /// @param _addr Address being queried for the implementer of an interface.\n /// (If '_addr' is the zero address then 'msg.sender' is assumed.)\n /// @param _interfaceHash Keccak256 hash of the name of the interface as a string.\n /// E.g., 'web3.utils.keccak256(\"ERC777TokensRecipient\")' for the 'ERC777TokensRecipient' interface.\n /// @return The address of the contract which implements the interface '_interfaceHash' for '_addr'\n /// or '0' if '_addr' did not register an implementer for this interface.\n function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {\n address addr = _addr == address(0) ? msg.sender : _addr;\n if (isERC165Interface(_interfaceHash)) {\n bytes4 erc165InterfaceHash = bytes4(_interfaceHash);\n return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : address(0);\n }\n return interfaces[addr][_interfaceHash];\n }\n\n /// @notice Sets the contract which implements a specific interface for an address.\n /// Only the manager defined for that address can set it.\n /// (Each address is the manager for itself until it sets a new manager.)\n /// @param _addr Address for which to set the interface.\n /// (If '_addr' is the zero address then 'msg.sender' is assumed.)\n /// @param _interfaceHash Keccak256 hash of the name of the interface as a string.\n /// E.g., 'web3.utils.keccak256(\"ERC777TokensRecipient\")' for the 'ERC777TokensRecipient' interface.\n /// @param _implementer Contract address implementing '_interfaceHash' for '_addr'.\n function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {\n address addr = _addr == address(0) ? msg.sender : _addr;\n require(getManager(addr) == msg.sender, \"Not the manager\");\n\n require(!isERC165Interface(_interfaceHash), \"Must not be an ERC165 hash\");\n if (_implementer != address(0) && _implementer != msg.sender) {\n require(\n ERC1820ImplementerInterface(_implementer)\n .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC1820_ACCEPT_MAGIC,\n \"Does not implement the interface\"\n );\n }\n interfaces[addr][_interfaceHash] = _implementer;\n emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);\n }\n\n /// @notice Sets '_newManager' as manager for '_addr'.\n /// The new manager will be able to call 'setInterfaceImplementer' for '_addr'.\n /// @param _addr Address for which to set the new manager.\n /// @param _newManager Address of the new manager for 'addr'. (Pass '0x0' to reset the manager to '_addr'.)\n function setManager(address _addr, address _newManager) external {\n require(getManager(_addr) == msg.sender, \"Not the manager\");\n managers[_addr] = _newManager == _addr ? address(0) : _newManager;\n emit ManagerChanged(_addr, _newManager);\n }\n\n /// @notice Get the manager of an address.\n /// @param _addr Address for which to return the manager.\n /// @return Address of the manager for a given address.\n function getManager(address _addr) public view returns(address) {\n // By default the manager of an address is the same address\n if (managers[_addr] == address(0)) {\n return _addr;\n } else {\n return managers[_addr];\n }\n }\n\n /// @notice Compute the keccak256 hash of an interface given its name.\n /// @param _interfaceName Name of the interface.\n /// @return The keccak256 hash of an interface name.\n function interfaceHash(string calldata _interfaceName) external pure returns(bytes32) {\n return keccak256(abi.encodePacked(_interfaceName));\n }\n\n /* --- ERC165 Related Functions --- */\n /* --- Developed in collaboration with William Entriken. --- */\n\n /// @notice Updates the cache with whether the contract implements an ERC165 interface or not.\n /// @param _contract Address of the contract for which to update the cache.\n /// @param _interfaceId ERC165 interface for which to update the cache.\n function updateERC165Cache(address _contract, bytes4 _interfaceId) external {\n interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(\n _contract, _interfaceId) ? _contract : address(0);\n erc165Cached[_contract][_interfaceId] = true;\n }\n\n /// @notice Checks whether a contract implements an ERC165 interface or not.\n // If the result is not cached a direct lookup on the contract address is performed.\n // If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling\n // 'updateERC165Cache' with the contract address.\n /// @param _contract Address of the contract to check.\n /// @param _interfaceId ERC165 interface to check.\n /// @return True if '_contract' implements '_interfaceId', false otherwise.\n function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {\n if (!erc165Cached[_contract][_interfaceId]) {\n return implementsERC165InterfaceNoCache(_contract, _interfaceId);\n }\n return interfaces[_contract][_interfaceId] == _contract;\n }\n\n /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.\n /// @param _contract Address of the contract to check.\n /// @param _interfaceId ERC165 interface to check.\n /// @return True if '_contract' implements '_interfaceId', false otherwise.\n function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {\n uint256 success;\n uint256 result;\n\n (success, result) = noThrowCall(_contract, ERC165ID);\n if (success == 0 || result == 0) {\n return false;\n }\n\n (success, result) = noThrowCall(_contract, INVALID_ID);\n if (success == 0 || result != 0) {\n return false;\n }\n\n (success, result) = noThrowCall(_contract, _interfaceId);\n if (success == 1 && result == 1) {\n return true;\n }\n return false;\n }\n\n /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not.\n /// @param _interfaceHash The hash to check.\n /// @return True if '_interfaceHash' is an ERC165 interface (ending with 28 zeroes), false otherwise.\n function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {\n return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;\n }\n\n /// @dev Make a call on a contract without throwing if the function does not exist.\n function noThrowCall(address _contract, bytes4 _interfaceId)\n internal view returns (uint256 success, uint256 result)\n {\n bytes4 erc165ID = ERC165ID;\n\n assembly {\n let x := mload(0x40) // Find empty storage location using \"free memory pointer\"\n mstore(x, erc165ID) // Place signature at beginning of empty storage\n mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature\n\n success := staticcall(\n 30000, // 30k gas\n _contract, // To addr\n x, // Inputs are stored at location x\n 0x24, // Inputs are 36 (4 + 32) bytes long\n x, // Store output over input (saves space)\n 0x20 // Outputs are 32 bytes long\n )\n\n result := mload(x) // Load the result\n }\n }\n}\n", - "keccak256": "0x64025ecebddb6e126a5075c1fd6c01de2840492668e2909cef7157040a9d1945" - } - }, - "version": 1 - } -``` - -
- -### Interface Name - -Any interface name is hashed using `keccak256` and sent to `getInterfaceImplementer()`. - -If the interface is part of a standard, it is best practice to explicitly state the interface name and link to this published [ERC-1820] such that other people don't have to come here to look up these rules. - -For convenience, the registry provides a function to compute the hash on-chain: - -``` solidity -function interfaceHash(string _interfaceName) public pure returns(bytes32) -``` - -Compute the keccak256 hash of an interface given its name. - -> **identifier:** `65ba36c1` -> **parameters** -> `_interfaceName`: Name of the interface. -> **returns:** The `keccak256` hash of an interface name. - -#### **Approved ERCs** - -If the interface is part of an approved ERC, it MUST be named `ERC###XXXXX` where `###` is the number of the ERC and XXXXX should be the name of the interface in CamelCase. -The meaning of this interface SHOULD be defined in the specified ERC. - -Examples: - -- `keccak256("ERC20Token")` -- `keccak256("ERC777Token")` -- `keccak256("ERC777TokensSender")` -- `keccak256("ERC777TokensRecipient")` - -#### **[ERC-165] Compatible Interfaces** - -> The compatibility with [ERC-165], including the [ERC165 Cache], has been designed and developed with [William Entriken]. - -Any interface where the last 28 bytes are zeroes (`0`) SHALL be considered an [ERC-165] interface. - -**[ERC-165] Lookup** - -Anyone can explicitly check if a contract implements an [ERC-165] interface using the registry by calling one of the two functions below: - -``` solidity -function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) -``` - -Checks whether a contract implements an [ERC-165] interface or not. - -If the result is not cached a direct lookup on the contract address is performed. - -*NOTE*: If the result is not cached or the cached value is out-of-date, the cache MUST be updated manually by calling `updateERC165Cache` with the contract address. -(See [ERC165 Cache] for more details.) - -> **identifier:** `f712f3e8` -> **parameters** -> `_contract`: Address of the contract to check. -> `_interfaceId`: [ERC-165] interface to check. -> **returns:** `true` if `_contract` implements `_interfaceId`, `false` otherwise. - -``` solidity -function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) -``` - -Checks whether a contract implements an [ERC-165] interface or not without using nor updating the cache. - -> **identifier:** `b7056765` -> **parameters** -> `_contract`: Address of the contract to check. -> `_interfaceId`: [ERC-165] interface to check. -> **returns:** `true` if `_contract` implements `_interfaceId`, false otherwise. - -**[ERC-165] Cache** - -Whether a contract implements an [ERC-165] interface or not can be cached manually to save gas. - -If a contract dynamically changes its interface and relies on the [ERC-165] cache of the [ERC-1820] registry, the cache MUST be updated manually---there is no automatic cache invalidation or cache update. -Ideally the contract SHOULD automatically update the cache when changing its interface. -However anyone MAY update the cache on the contract's behalf. - -The cache update MUST be done using the `updateERC165Cache` function: - -``` solidity -function updateERC165Cache(address _contract, bytes4 _interfaceId) external -``` - -> **identifier:** `a41e7d51` -> **parameters** -> `_contract`: Address of the contract for which to update the cache. -> `_interfaceId`: [ERC-165] interface for which to update the cache. - -#### **Private User-defined Interfaces** - -This scheme is extensible. -You MAY make up your own interface name and raise awareness to get other people to implement it and then check for those implementations. -Have fun but please, you MUST not conflict with the reserved designations above. - -### Set An Interface For An Address - -For any address to set a contract as the interface implementation, it must call the following function of the [ERC-1820] registry: - -``` solidity -function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external -``` - -Sets the contract which implements a specific interface for an address. - -Only the `manager` defined for that address can set it. -(Each address is the manager for itself, see the [manager] section for more details.) - -*NOTE*: If `_addr` and `_implementer` are two different addresses, then: - -- The `_implementer` MUST implement the `ERC1820ImplementerInterface` (detailed below). -- Calling `canImplementInterfaceForAddress` on `_implementer` with the given `_addr` and `_interfaceHash` MUST return the `ERC1820_ACCEPT_MAGIC` value. - -*NOTE*: The `_interfaceHash` MUST NOT be an [ERC-165] interface---it MUST NOT end with 28 zeroes (`0`). - -*NOTE*: The `_addr` MAY be `0`, then `msg.sender` is assumed. -This default value simplifies interactions via multisigs where the data of the transaction to sign is constant regardless of the address of the multisig instance. - -> **identifier:** `29965a1d` -> **parameters** -> `_addr`: Address for which to set the interface. (If `_addr` is the zero address then `msg.sender` is assumed.) -> `_interfaceHash`: Keccak256 hash of the name of the interface as a string, for example `web3.utils.keccak256('ERC777TokensRecipient')` for the ERC777TokensRecipient interface. -> `_implementer`: Contract implementing `_interfaceHash` for `_addr`. - -### Get An Implementation Of An Interface For An Address - -Anyone MAY query the [ERC-1820] Registry to obtain the address of a contract implementing an interface on behalf of some address using the `getInterfaceImplementer` function. - -``` solidity -function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) -``` - -Query if an address implements an interface and through which contract. - -*NOTE*: If the last 28 bytes of the `_interfaceHash` are zeroes (`0`), then the first 4 bytes are considered an [ERC-165] interface and the registry SHALL forward the call to the contract at `_addr` to see if it implements the [ERC-165] interface (the first 4 bytes of `_interfaceHash`). -The registry SHALL also cache [ERC-165] queries to reduce gas consumption. Anyone MAY call the `erc165UpdateCache` function to update whether a contract implements an interface or not. - -*NOTE*: The `_addr` MAY be `0`, then `msg.sender` is assumed. -This default value is consistent with the behavior of the `setInterfaceImplementer` function and simplifies interactions via multisigs where the data of the transaction to sign is constant regardless of the address of the multisig instance. - -> **identifier:** `aabbb8ca` -> **parameters** -> `_addr`: Address being queried for the implementer of an interface. (If `_addr` is the zero address then `msg.sender` is assumed.) -> `_interfaceHash`: keccak256 hash of the name of the interface as a string. E.g. `web3.utils.keccak256('ERC777Token')` -> **returns:** The address of the contract which implements the interface `_interfaceHash` for `_addr` or `0` if `_addr` did not register an implementer for this interface. - - -### Interface Implementation (`ERC1820ImplementerInterface`) - -``` solidity -interface ERC1820ImplementerInterface { - /// @notice Indicates whether the contract implements the interface `interfaceHash` for the address `addr` or not. - /// @param interfaceHash keccak256 hash of the name of the interface - /// @param addr Address for which the contract will implement the interface - /// @return ERC1820_ACCEPT_MAGIC only if the contract implements `interfaceHash` for the address `addr`. - function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32); -} -``` - -Any contract being registered as the implementation of an interface for a given address MUST implement said interface. -In addition if it implements an interface on behalf of a different address, the contract MUST implement the `ERC1820ImplementerInterface` shown above. - -``` solidity -function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32) -``` - -Indicates whether a contract implements an interface (`interfaceHash`) for a given address (`addr`). - -If a contract implements the interface (`interfaceHash`) for a given address (`addr`), it MUST return `ERC1820_ACCEPT_MAGIC` when called with the `addr` and the `interfaceHash`. -If it does not implement the `interfaceHash` for a given address (`addr`), it MUST NOT return `ERC1820_ACCEPT_MAGIC`. - -> **identifier:** `f0083250` -> **parameters** -> `interfaceHash`: Hash of the interface which is implemented -> `addr`: Address for which the interface is implemented -> **returns:** `ERC1820_ACCEPT_MAGIC` only if the contract implements `ìnterfaceHash` for the address `addr`. - -The special value `ERC1820_ACCEPT_MAGIC` is defined as the `keccka256` hash of the string `"ERC1820_ACCEPT_MAGIC"`. - -``` solidity -bytes32 constant internal ERC1820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC1820_ACCEPT_MAGIC")); -``` - -> The reason to return `ERC1820_ACCEPT_MAGIC` instead of a boolean is to prevent cases where a contract fails to implement the `canImplementInterfaceForAddress` but implements a fallback function which does not throw. In this case, since `canImplementInterfaceForAddress` does not exist, the fallback function is called instead, executed without throwing and returns `1`. Thus making it appear as if `canImplementInterfaceForAddress` returned `true`. - -### Manager - -The manager of an address (regular account or a contract) is the only entity allowed to register implementations of interfaces for the address. -By default, any address is its own manager. - -The manager can transfer its role to another address by calling `setManager` on the registry contract with the address for which to transfer the manager and the address of the new manager. - -**`setManager` Function** - -``` solidity -function setManager(address _addr, address _newManager) external -``` - -Sets `_newManager` as manager for `_addr`. - -The new manager will be able to call `setInterfaceImplementer` for `_addr`. - -If `_newManager` is `0x0`, the manager is reset to `_addr` itself as the manager. - -> **identifier:** `5df8122f` -> **parameters** -> `_addr`: Address for which to set the new manager. -> `_newManager`: The address of the new manager for `_addr`. (Pass `0x0` to reset the manager to `_addr`.) - -**`getManager` Function** - -``` solidity -function getManager(address _addr) public view returns(address) -``` - -Get the manager of an address. - -> **identifier:** `3d584063` -> **parameters** -> `_addr`: Address for which to return the manager. -> **returns:** Address of the manager for a given address. - -## Rationale - -This standards offers a way for any type of address (externally owned and contracts) to implement an interface and potentially delegate the implementation of the interface to a proxy contract. -This delegation to a proxy contract is necessary for externally owned accounts and useful to avoid redeploying existing contracts such as multisigs and DAOs. - -The registry can also act as a [ERC-165] cache in order to save gas when looking up if a contract implements a specific [ERC-165] interface. -This cache is intentionally kept simple, without automatic cache update or invalidation. -Anyone can easily and safely update the cache for any interface and any contract by calling the `updateERC165Cache` function. - -The registry is deployed using a keyless deployment method relying on a single-use deployment address to ensure no one controls the registry, thereby ensuring trust. - -## Backward Compatibility - -This standard is backward compatible with [ERC-165], as both methods MAY be implemented without conflicting with each other. - -## Test Cases - -Please check the [0xjac/ERC1820] repository for the full test suite. - -## Implementation - -The implementation is available in the repo: [0xjac/ERC1820]. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[EIP-155]: ./eip-155.md -[ERC-165]: ./eip-165.md -[ERC-672]: https://github.com/ethereum/EIPs/issues/672 -[ERC-820]: ./eip-820.md -[ERC-1820]: ./eip-1820.md -[ERC1820 registry smart contract]: https://github.com/0xjac/ERC1820/blob/master/contracts/ERC1820Registry.sol -[erc1820-annoucement]: https://github.com/ethereum/EIPs/issues/820#issuecomment-464109166 -[erc820-bug]: https://github.com/ethereum/EIPs/issues/820#issuecomment-452465748 -[erc820-fix]: https://github.com/ethereum/EIPs/issues/820#issuecomment-454021564 -[manager]: #manager -[lookup]: #get-an-implementation-of-an-interface-for-an-address -[ERC165 Cache]: #erc165-cache -[Nick's article]: https://medium.com/@weka/how-to-send-ether-to-11-440-people-187e332566b7 -[0xjac/ERC1820]: https://github.com/0xjac/ERC1820 -[Nick]: https://github.com/Arachnid/ -[William Entriken]: https://github.com/fulldecent -[ENS]: https://ens.domains/ +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1820.md diff --git a/EIPS/eip-1822.md b/EIPS/eip-1822.md index 8280b196ad5c8d..beaed30dc660e2 100644 --- a/EIPS/eip-1822.md +++ b/EIPS/eip-1822.md @@ -1,319 +1 @@ ---- -eip: 1822 -title: Universal Upgradeable Proxy Standard (UUPS) -author: Gabriel Barros , Patrick Gallagher -discussions-to: https://ethereum-magicians.org/t/eip-1822-universal-upgradeable-proxy-standard-uups -status: Stagnant -type: Standards Track -category: ERC -created: 2019-03-04 ---- - -## Simple Summary - -Standard upgradeable proxy contract. - -## Abstract - -The following describes a standard for proxy contracts which is universally compatible with all contracts, and does not create incompatibility between the proxy and business-logic contracts. This is achieved by utilizing a unique storage position in the proxy contract to store the Logic Contract's address. A compatibility check ensures successful upgrades. Upgrading can be performed unlimited times, or as determined by custom logic. In addition, a method for selecting from multiple constructors is provided, which does not inhibit the ability to verify bytecode. - -## Motivation - -- Improve upon existing proxy implementations to improve developer experience for deploying and maintaining Proxy and Logic Contracts. - -- Standardize and improve the methods for verifying the bytecode used by the Proxy Contract. - -## Terminology - -- `delegatecall()` - Function in contract **A** which allows an external contract **B** (delegating) to modify **A**'s storage (see diagram below, [Solidity docs](https://solidity.readthedocs.io/en/v0.5.3/introduction-to-smart-contracts.html#delegatecall-callcode-and-libraries)) -- **Proxy Contract** - The contract **A** which stores data, but uses the logic of external contract **B** by way of `delegatecall()`. -- **Logic Contract** - The contract **B** which contains the logic used by Proxy Contract **A** -- **Proxiable Contract** - Inherited in Logic Contract **B** to provide the upgrade functionality - -![](../assets/eip-1822/proxy-diagram.png) - -## Specification - -The Proxy Contract proposed here should be deployed _as is_, and used as a drop-in replacement for any existing methods of lifecycle management of contracts. In addition to the Proxy Contract, we propose the Proxiable Contract interface/base which establishes a pattern for the upgrade which does not interfere with existing business rules. The logic for allowing upgrades can be implemented as needed. - -### Proxy Contract - -#### Functions - -##### `fallback` - -The proposed fallback function follows the common pattern seen in other Proxy Contract implementations such as [Zeppelin][1] or [Gnosis][2]. - -However, rather than forcing use of a variable, the address of the Logic Contract is stored at the defined storage position `keccak256("PROXIABLE")`. This eliminates the possibility of collision between variables in the Proxy and Logic Contracts, thus providing "universal" compatibility with any Logic Contract. - -```javascript -function() external payable { - assembly { // solium-disable-line - let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) - calldatacopy(0x0, 0x0, calldatasize) - let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0) - let retSz := returndatasize - returndatacopy(0, 0, retSz) - switch success - case 0 { - revert(0, retSz) - } - default { - return(0, retSz) - } - } -} -``` - -#### `constructor` - -The proposed constructor accepts any number of arguments of any type, and thus is compatible with any Logic Contract constructor function. - -In addition, the arbitrary nature of the Proxy Contract's constructor provides the ability to select from one or more constructor functions available in the Logic Contract source code (e.g., `constructor1`, `constructor2`, ... etc. ). Note that if multiple constructors are included in the Logic Contract, a check should be included to prohibit calling a constructor again post-initialization. - -It's worth noting that the added functionality of supporting multiple constructors does not inhibit verification of the Proxy Contract's bytecode, since the initialization tx call data (input) can be decoded by first using the Proxy Contract ABI, and then using the Logic Contract ABI. - -The contract below shows the proposed implementation of the Proxy Contract. - -```javascript -contract Proxy { - // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" - constructor(bytes memory constructData, address contractLogic) public { - // save the code address - assembly { // solium-disable-line - sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic) - } - (bool success, bytes memory _ ) = contractLogic.delegatecall(constructData); // solium-disable-line - require(success, "Construction failed"); - } - - function() external payable { - assembly { // solium-disable-line - let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) - calldatacopy(0x0, 0x0, calldatasize) - let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0) - let retSz := returndatasize - returndatacopy(0, 0, retSz) - switch success - case 0 { - revert(0, retSz) - } - default { - return(0, retSz) - } - } - } -} -``` - -### Proxiable Contract - -The Proxiable Contract is included in the Logic Contract, and provides the functions needed to perform an upgrade. The compatibility check `proxiable` prevents irreparable updates during an upgrade. - -> :warning: Warning: `updateCodeAddress` and `proxiable` must be present in the Logic Contract. Failure to include these may prevent upgrades, and could allow the Proxy Contract to become entirely unusable. See below [Restricting dangerous functions](#restricting-dangerous-functions) - -#### Functions - -##### `proxiable` - -Compatibility check to ensure the new Logic Contract implements the Universal Upgradeable Proxy Standard. Note that in order to support future implementations, the `bytes32` comparison could be changed e.g., `keccak256("PROXIABLE-ERC1822-v1")`. - -##### `updateCodeAddress` - -Stores the Logic Contract's address at storage `keccak256("PROXIABLE")` in the Proxy Contract. - -The contract below shows the proposed implementation of the Proxiable Contract. - -```javascript -contract Proxiable { - // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" - - function updateCodeAddress(address newAddress) internal { - require( - bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(), - "Not compatible" - ); - assembly { // solium-disable-line - sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, newAddress) - } - } - function proxiableUUID() public pure returns (bytes32) { - return 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; - } -} -``` - -## Pitfalls when using a proxy - -The following common best practices should be employed for all Logic Contracts when using a proxy contract. - -### Separating Variables from Logic - -Careful consideration should be made when designing a new Logic Contract to prevent incompatibility with the existing storage of the Proxy Contract after an upgrade. Specifically, the order in which variables are instantiated in the new contract should not be modified, and any new variables should be added after all existing variables from the previous Logic Contract - -To facilitate this practice, we recommend utilizing a single "base" contract which holds all variables, and which is inherited in subsequent logic contract(s). This practice greatly reduces the chances of accidentally reordering variables or overwriting them in storage. - -### Restricting dangerous functions - -The compatibility check in the Proxiable Contract is a safety mechanism to prevent upgrading to a Logic Contract which does not implement the Universal Upgradeable Proxy Standard. However, as occurred in the parity wallet hack, it is still possible to perform irreparable damage to the Logic Contract itself. - -In order to prevent damage to the Logic Contract, we recommend restricting permissions for any potentially damaging functions to `onlyOwner`, and giving away ownership of the Logic Contract immediately upon deployment to a null address (e.g., address(1)). Potentially damaging functions include native functions such as `SELFDESTRUCT`, as well functions whose code may originate externally such as `CALLCODE`, and `delegatecall()`. In the [ERC-20 Token](#erc-20-token) example below, a `LibraryLock` contract is used to prevent destruction of the logic contract. - -## Examples - -### Owned - -In this example, we show the standard ownership example, and restrict the `updateCodeAddress` to only the owner. - -```javascript -contract Owned is Proxiable { - // ensures no one can manipulate this contract once it is deployed - address public owner = address(1); - - function constructor1() public{ - // ensures this can be called only once per *proxy* contract deployed - require(owner == address(0)); - owner = msg.sender; - } - - function updateCode(address newCode) onlyOwner public { - updateCodeAddress(newCode); - } - - modifier onlyOwner() { - require(msg.sender == owner, "Only owner is allowed to perform this action"); - _; - } -} -``` - -### ERC-20 Token - -#### Proxy Contract - -```javascript -pragma solidity ^0.5.1; - -contract Proxy { - // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" - constructor(bytes memory constructData, address contractLogic) public { - // save the code address - assembly { // solium-disable-line - sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic) - } - (bool success, bytes memory _ ) = contractLogic.delegatecall(constructData); // solium-disable-line - require(success, "Construction failed"); - } - - function() external payable { - assembly { // solium-disable-line - let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) - calldatacopy(0x0, 0x0, calldatasize) - let success := delegatecall(sub(gas, 10000), contractLogic, 0x0, calldatasize, 0, 0) - let retSz := returndatasize - returndatacopy(0, 0, retSz) - switch success - case 0 { - revert(0, retSz) - } - default { - return(0, retSz) - } - } - } -} -``` - -#### Token Logic Contract - -``` javascript - -contract Proxiable { - // Code position in storage is keccak256("PROXIABLE") = "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7" - - function updateCodeAddress(address newAddress) internal { - require( - bytes32(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) == Proxiable(newAddress).proxiableUUID(), - "Not compatible" - ); - assembly { // solium-disable-line - sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, newAddress) - } - } - function proxiableUUID() public pure returns (bytes32) { - return 0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7; - } -} - - -contract Owned { - - address owner; - - function setOwner(address _owner) internal { - owner = _owner; - } - modifier onlyOwner() { - require(msg.sender == owner, "Only owner is allowed to perform this action"); - _; - } -} - -contract LibraryLockDataLayout { - bool public initialized = false; -} - -contract LibraryLock is LibraryLockDataLayout { - // Ensures no one can manipulate the Logic Contract once it is deployed. - // PARITY WALLET HACK PREVENTION - - modifier delegatedOnly() { - require(initialized == true, "The library is locked. No direct 'call' is allowed"); - _; - } - function initialize() internal { - initialized = true; - } -} - -contract ERC20DataLayout is LibraryLockDataLayout { - uint256 public totalSupply; - mapping(address=>uint256) public tokens; -} - -contract ERC20 { - // ... - function transfer(address to, uint256 amount) public { - require(tokens[msg.sender] >= amount, "Not enough funds for transfer"); - tokens[to] += amount; - tokens[msg.sender] -= amount; - } -} - -contract MyToken is ERC20DataLayout, ERC20, Owned, Proxiable, LibraryLock { - - function constructor1(uint256 _initialSupply) public { - totalSupply = _initialSupply; - tokens[msg.sender] = _initialSupply; - initialize(); - setOwner(msg.sender); - } - function updateCode(address newCode) public onlyOwner delegatedOnly { - updateCodeAddress(newCode); - } - function transfer(address to, uint256 amount) public delegatedOnly { - ERC20.transfer(to, amount); - } -} -``` - -## References - -- ["Escape-hatch" proxy Medium Post](https://medium.com/terminaldotco/escape-hatch-proxy-efb681de108d) - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - -[1]: https://github.com/maraoz/solidity-proxy/blob/master/contracts/Dispatcher.sol -[2]: https://blog.gnosis.pm/solidity-delegateproxy-contracts-e09957d0f201 +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1822.md diff --git a/EIPS/eip-1844.md b/EIPS/eip-1844.md index a80e696c7b20cb..a5e75dcb3711ac 100644 --- a/EIPS/eip-1844.md +++ b/EIPS/eip-1844.md @@ -1,64 +1 @@ ---- -eip: 1844 -title: ENS Interface Discovery -author: Nick Johnson (@arachnid) -discussions-to: https://ethereum-magicians.org/t/ens-interface-discovery/2924 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-03-15 -requires: 137, 165 ---- - -## Simple Summary -Defines a method of associating contract interfaces with ENS names and addresses, and of discovering those interfaces. - -## Abstract -This EIP specifies a method for exposing interfaces associated with an ENS name or an address (typically a contract address) and allowing applications to discover those interfaces and interact with them. Interfaces can be implemented either by the target contract (if any) or by any other contract. - -## Motivation -EIP 165 supports interface discovery - determining if the contract at a given address supports a requested interface. However, in many cases it's useful to be able to discover functionality associated with a name or an address that is implemented by other contracts. - -For example, a token contract may not itself provide any kind of 'atomic swap' functionality, but there may be associated contracts that do. With ENS interface discovery, the token contract can expose this metadata, informing applications where they can find that functionality. - -## Specification -A new profile for ENS resolvers is defined, consisting of the following method: - -```solidity -function interfaceImplementer(bytes32 node, bytes4 interfaceID) external view returns (address); -``` - -The EIP-165 interface ID of this interface is `0xb8f2bbb4`. - -Given an ENS name hash `node` and an EIP-165 `interfaceID`, this function returns the address of an appropriate implementer of that interface. If there is no interface matching that interface ID for that node, 0 is returned. - -The address returned by `interfaceImplementer` MUST refer to a smart contract. - -The smart contract at the returned address SHOULD implement EIP-165. - -Resolvers implementing this interface MAY utilise a fallback strategy: If no matching interface was explicitly provided by the user, query the contract returned by `addr()`, returning its address if the requested interface is supported by that contract, and 0 otherwise. If they do this, they MUST ensure they return 0, rather than reverting, if the target contract reverts. - -This field may be used with both forward resolution and reverse resolution. - -## Rationale - -A naive approach to this problem would involve adding this method directly to the target contract. However, doing this has several shortcomings: - - 1. Each contract must maintain its own list of interface implementations. - 2. Modifying this list requires access controls, which the contract may not have previously required. - 3. Support for this must be designed in when the contract is written, and cannot be retrofitted afterwards. - 4. Only one canonical list of interfaces can be supported. - -Using ENS resolvers instead mitigates these shortcomings, making it possible for anyone to associate interfaces with a name, even for contracts not previously built with this in mind. - -## Backwards Compatibility -There are no backwards compatibility issues. - -## Test Cases -TBD - -## Implementation -The PublicResolver in the [ensdomains/resolvers](https://github.com/ensdomains/resolvers/) repository implements this interface. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1844.md diff --git a/EIPS/eip-190.md b/EIPS/eip-190.md index bf909700db3f26..15c9754e15e42e 100644 --- a/EIPS/eip-190.md +++ b/EIPS/eip-190.md @@ -1,96 +1 @@ ---- -eip: 190 -title: Ethereum Smart Contract Packaging Standard -author: Piper Merriam (@pipermerriam), Tim Coulter (@tcoulter), Denis Erfurt (@mhhf), RJ Catalano (@VoR0220), Iuri Matias (@iurimatias) -status: Final -type: Standards Track -category: ERC -created: 2017-01-10 ---- - -# Abstract - -This ERC proposes a specification for Ethereum smart contract packages. - -The specification was collaboratively developed by the following Ethereum development framework maintainers. - -* Tim Coulter (Truffle) -* Denis Erfurt (Dapple) -* Piper Merriam (Populus) -* RJ Catalano (Eris PM) -* Iuri Matias (Embark) - -# Motivation - -Packaging is a core piece of modern software development which is missing from the Ethereum ecosystem. The lack of packaging limits the ability for developers to reuse code which negatively affects productivity and security. - -A key example of this is the ERC20 standard. There are a few well audited reusable token contracts available but most developers end up writing their own because of the difficulty in finding and reusing existing code. - -A packaging standard should have the following positive effects on the ecosystem: - -* Greater overall productivity caused by the ability to reuse existing code. -* Increased security caused by the ability to reuse existing well audited implementations of common patterns (ERC20, crowdfunding, etc). - -Smart contract packaging should also have a direct positive effect on the end user. Wallet software will be able to consume a released package and generate an interface for interacting with any deployed contracts included within that package. With the advent of [ENS](./eip-137.md) all of the pieces will be in place for a wallet to take a human readable name and present the user with an interface for interacting with the underlying application. - - -# Specification - -The full specification for this standard is maintained separately in the repository [epm/epm-spec](https://github.com/ethpm/epm-spec). - -This EIP refers to the `1.0.0` version of the specification: [https://github.com/ethpm/epm-spec/tree/v1.0.0](https://github.com/ethpm/epm-spec/tree/v1.0.0) - -The specification contains details for a single document referred to as a *"Release Lockfile"*. - -* Release Lockfile Specification: [https://github.com/ethpm/epm-spec/blob/v1.0.0/release-lockfile.spec.md](https://github.com/ethpm/epm-spec/blob/v1.0.0/release-lockfile.spec.md). -* JSON Schema for Release Lockfile: [https://github.com/ethpm/epm-spec/blob/v1.0.0/spec/release-lockfile.spec.json](https://github.com/ethpm/epm-spec/blob/v1.0.0/spec/release-lockfile.spec.json) - -> These documents have not been inlined into this ERC to ensure that there is a single source of truth for the specification. - - -# Use Cases - -This specification covers the following types of smart contract packages. - -1. Packages with contracts intended to be used as base contract such as the common `owned` pattern. -2. Packages with contracts that are ready to use as-is such as an ERC20 token contract. -3. Packages with deployed contracts such as libraries or services. - -Full explanations and examples of these use cases can be found in the [`README.md`](https://github.com/ethpm/epm-spec/blob/v1.0.0/README.md#use-cases) from the `epm/epm-spec` repository. - - -# Package Managers - -The *Release Lockfile* is intended for consumption by package management software. Specific care was made to ensure that all of the following functionality can be implemented by package managers. - - -## Deterministic builds - -Ensures that a package will always resolve to the same set of dependencies and source files. Both source files and dependencies are content addressed to ensure that the referenced resources cannot change. - - -## Bytecode verification - -Contains the appropriate information for a package manager to inspect a deployed contract and verify that its bytecode matches the bytecode that results from compilation and linking of the package source code. - - -## Multi-chain deploys - -Supports deployments across multiple chains, allowing a package to define addresses on both the public mainnet and testnet. - - -## Trusted packages - -Allows for packages which exclude source code or other elements which would be needed for verification of the contract bytecode. This allows for minimalistic packages to be created for special situations where the package manager will not be performing verification. - - -# Framework support and integration - -Support for ERC190 is either implemented or in progress for the following: - -* [Truffle](https://truffleframework.com/) -* [Populus](https://populus.readthedocs.io/en/latest/) -* [Dapple](https://dapple.readthedocs.io/en/master/) -* [Eris PM](https://github.com/eris-ltd/eris-cli) -* [Embark](https://github.com/iurimatias/embark-framework) -* [Browser Solidity](https://github.com/ethereum/remix-ide/issues/386) +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-190.md diff --git a/EIPS/eip-1900.md b/EIPS/eip-1900.md index a11b794dbc3a40..a6783aa550db79 100644 --- a/EIPS/eip-1900.md +++ b/EIPS/eip-1900.md @@ -1,276 +1 @@ ---- -eip: 1900 -title: dType - Decentralized Type System for EVM -author: Loredana Cirstea (@loredanacirstea), Christian Tzurcanu (@ctzurcanu) -discussions-to: https://github.com/ethereum/EIPs/issues/1882 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-03-28 ---- - -## Simple Summary - -The EVM and related languages such as Solidity need consensus on an extensible Type System in order to further evolve into the Singleton Operating System (The World Computer). - -## Abstract - -We are proposing a decentralized Type System for Ethereum, to introduce data definition (and therefore ABI) consistency. This ERC focuses on defining an on-chain Type Registry (named `dType`) and a common interface for creating types, based on `struct`s. - - -## Motivation - -In order to build a network of interoperable protocols on Ethereum, we need data standardization, to ensure a smooth flow of on-chain information. Off-chain, the Type Registry will allow a better analysis of blockchain data (e.g. for blockchain explorers) and creation of smart contract development tools for easily using existing types in a new smart contract. - -However, this is only the first phase. As defined in this document and in the future proposals that will be based on this one, we are proposing something more: a decentralized Type System with Data Storage - [ERC-2158](https://github.com/ethereum/EIPs/pull/2158). In addition, developers can create libraries of `pure` functions that know how to interact and modify the data entries - [dType Functions Extension](https://github.com/ethereum/EIPs/issues/1921). This will effectively create the base for a general functional programming system on Ethereum, where developers can use previously created building blocks. - -To summarize: - -* We would like to have a good decentralized medium for integrating all Ethereum data, and relationships between the different types of data. Also, a way to address the behavior related to each data type. -* Functional programming becomes easier. Functions like `map`, `reduce`, `filter`, are implemented by each type library. -* Solidity development tools could be transparently extended to include the created types (For example in IDEs like Remix). At a later point, the EVM itself can have precompiled support for these types. -* The system can be easily extended to types pertaining to other languages. (With type definitions in the source (Swarm stored source code in the respective language)) -* The dType database should be part of the System Registry for the Operating System of The World Computer - - -## Specification - -The Type Registry can have a governance protocol for its CRUD operations. However, this, and other permission guards are not covered in this proposal. - -### Type Definition and Metadata - -The dType registry should support the registration of Solidity's elementary and complex types. In addition, it should also support contract events definitions. In this EIP, the focus will be on describing the minimal on-chain type definition and metadata needed for registering Solidity user-defined types. - -#### Type Definition: TypeLibrary - -A type definition consists of a type library containing: -- the nominal `struct` used to define the type -- additional functions: - - `isInstanceOf`: checks whether a given variable is an instance of the defined type. Additional rules can be defined for each type fields, e.g. having a specific range for a `uint16 amount`. - - provide HOFs such as `map`, `filter`, `reduce` - - `structureBytes` and `destructureBytes`: provide type structuring and destructuring. This can be useful for low-level calls or assembly code, when importing contract interfaces is not an efficient option. It can also be used for type checking. - -A simple example is: - -```solidity -pragma solidity ^0.5.0; -pragma experimental ABIEncoderV2; - -library myBalanceLib { - - struct myBalance { - string accountName; - uint256 amount; - } - - function structureBytes(bytes memory data) pure public returns(myBalance memory balance) - - function destructureBytes(myBalance memory balance) pure public returns(bytes memory data) - - function isInstanceOf(myBalance memory balance) pure public returns(bool isInstance) - - function map( - address callbackAddr, - bytes4 callbackSig, - myBalance[] memory balanceArr - ) - view - internal - returns (myBalance[] memory result) -} -``` - -Types can also use existing types in their composition. However, this will always result in a directed acyclic graph. - -```solidity -library myTokenLib { - using myBalanceLib for myBalanceLib.myBalance; - - struct myToken { - address token; - myBalanceLib.myBalance; - } -} -``` - -#### Type Metadata: dType Registry - -Type metadata will be registered on-chain, in the dType registry contract. This consists of: -- `name` - the type's name, as it would be used in Solidity; it can be stored as a `string` or encoded as `bytes`. The name can have a human-readable part and a version number. -- `typeChoice` - used for storing additional ABI data that differentiate how types are handled on and off chain. It is defined as an `enum` with the following options: `BaseType`, `PayableFunction`, `StateFunction`, `ViewFunction`, `PureFunction`, `Event` -- `contractAddress` - the Ethereum `address` of the `TypeRootContract`. For this proposal, we can consider the Type Library address as the `TypeRootContract`. Future EIPs will make it more flexible and propose additional TypeStorage contracts that will modify the scope of `contractAddress` - [ERC-2158](https://github.com/ethereum/EIPs/pull/2158). -- `source` - a `bytes32` Swarm hash where the source code of the type library and contracts can be found; in future EIPs, where dType will be extended to support other languages (e.g. JavaScript, Rust), the file identified by the Swarm hash will contain the type definitions in that language. -- `types` - metadata for subtypes: the first depth level internal components. This is an array of objects (`structs`), with the following fields: - - `name` - the subtype name, of type `string`, similar to the above `name` definition - - `label` - the subtype label - - `dimensions` - `string[]` used for storing array dimensions. E.g.: - - `[]` -> `TypeA` - - `[""]` -> `TypeA[]` - - `["2"]` -> `TypeA[2]` - - `["",""]` -> `TypeA[][]` - - `["2","3"]` -> `TypeA[2][3]` - -Examples of metadata, for simple, value types: -```javascript -{ - "contractAddress": "0x0000000000000000000000000000000000000000", - "typeChoice": 0, - "source": "0x0000000000000000000000000000000000000000000000000000000000000000", - "name": "uint256", - "types": [] -} - -{ - "contractAddress": "0x0000000000000000000000000000000000000000", - "typeChoice": 0, - "source": "0x0000000000000000000000000000000000000000000000000000000000000000", - "name": "string", - "types": [] -} -``` - -Composed types can be defined as: -```javascript -{ - "contractAddress": "0x105631C6CdDBa84D12Fa916f0045B1F97eC9C268", - "typeChoice": 0, - "source": , - "name": "myBalance", - "types": [ - {"name": "string", "label": "accountName", dimensions: []}, - {"name": "uint256", "label": "amount", dimensions: []} - ] -} -``` - -Composed types can be further composed: -```javascript -{ - "contractAddress": "0x91E3737f15e9b182EdD44D45d943cF248b3a3BF9", - "typeChoice": 0, - "source": , - "name": "myToken", - "types": [ - {"name": "address", "label": "token", dimensions: []}, - {"name": "myBalance", "label": "balance", dimensions: []} - ] -} -``` - -`myToken` type will have the final data format: `(address,(string,uint256))` and a labeled format: `(address token, (string accountName, uint256 amount))`. - -##### dType Registry Data Structures and Interface - -To store this metadata, the dType registry will have the following data structures: - -```solidity -enum TypeChoices { - BaseType, - PayableFunction, - StateFunction, - ViewFunction, - PureFunction, - Event -} - -struct dTypes { - string name; - string label; - string[] dimensions; -} - -struct dType { - TypeChoices typeChoice; - address contractAddress; - bytes32 source; - string name; - dTypes[] types; -} - -``` - -For storage, we propose a pattern which isolates the type metadata from additional storage-specific data and allows CRUD operations on records. - -```solidity -// key: identifier -mapping(bytes32 => Type) public typeStruct; - -// array of identifiers -bytes32[] public typeIndex; - -struct Type { - dType data; - uint256 index; -} -``` - -Note that we are proposing to define the type's primary identifier, `identifier`, as `keccak256(abi.encodePacked(name))`. If the system is extended to other programming languages, we can define `identifier` as `keccak256(abi.encodePacked(language, name))`. -Initially, single word English names can be disallowed, avoiding name squatting. - - -The dType registry interface is: - -```solidity -import './dTypeLib.sol'; -interface dType { - event LogNew(bytes32 indexed identifier, uint256 indexed index); - event LogUpdate(bytes32 indexed identifier, uint256 indexed index); - event LogRemove(bytes32 indexed identifier, uint256 indexed index); - - function insert(dTypeLib.dType calldata data) external returns (bytes32 identifier); - - function remove(bytes32 identifier) external returns(uint256 index); - - function count() external view returns(uint256 counter); - - function getTypeIdentifier(string memory name) pure external returns (bytes32 identifier); - - function getByIdentifier(bytes32 identifier) view external returns(dTypeLib.dType memory dtype); - - function get(string memory name) view external returns(dTypeLib.dType memory dtype); - - function isRegistered(bytes32 identifier) view external returns(bool registered); -} -``` - -**Notes:** - -To ensure backward compatibility, we suggest that updating types should not be supported. - -The `remove` function can also be removed from the interface, to ensure immutability. One reason for keeping it would be clearing up storage for types that are not in use or have been made obsolete. However, this can have undesired effects and should be accompanied by a solid permissions system, testing and governance process. This part will be updated when enough feedback has been received. - -## Rationale - -The Type Registry must store the minimum amount of information for rebuilding the type ABI definition. This allows us to: -* support on-chain interoperability -* decode blockchain side effects off-chain (useful for block explorers) -* allow off-chain tools to cache and search through the collection (e.g. editor plugin for writing typed smart contracts) - -There is one advantage that has become clear with the emergence of global operating systems, like Ethereum: we can have a global type system through which the system’s parts can interoperate. Projects should agree on standardizing types and a type registry, continuously working on improving them, instead of creating encapsulated projects, each with their own types. - -The effort of having consensus on new types being added or removing unused ones is left to the governance system. - -After the basis of such a system is specified, we can move forward to building a static type checking system at compile time, based on the type definitions and rules stored in the dType registry. - -The Type Library must express the behavior strictly pertinent to its defined type. Additional behavior, required by various project's business logic can be added later, through libraries containing functions that handle the respective type. These can also be registered in dType, but will be detailed in a future ERC. - -This is an approach that will separate definitions from stored data and behavior, allowing for easier and more secure fine-grained upgrades. - -## Backwards Compatibility - -This proposal does not affect extant Ethereum standards or implementations. It uses the present experimental version of ABIEncoderV2. - -## Test Cases - -Will be added. - -## Implementation - -An in-work implementation can be found at https://github.com/pipeos-one/dType/tree/master/contracts/contracts. -This proposal will be updated with an appropriate implementation when consensus is reached on the specifications. - -A video demo of the current implementation (a more extended version of this proposal) can be seen at https://youtu.be/pcqi4yWBDuQ. - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1900.md diff --git a/EIPS/eip-191.md b/EIPS/eip-191.md index 9a166d62af91f7..b8e196ffbef022 100644 --- a/EIPS/eip-191.md +++ b/EIPS/eip-191.md @@ -1,108 +1 @@ ---- -eip: 191 -title: Signed Data Standard -author: Martin Holst Swende (@holiman), Nick Johnson -discussions-to: https://github.com/ethereum/EIPs/issues/191 -status: Final -type: Standards Track -category: ERC -created: 2016-01-20 ---- - -# Abstract - -This ERC proposes a specification about how to handle signed data in Ethereum contracts. - -# Motivation - -Several multisignature wallet implementations have been created which accepts `presigned` transactions. A `presigned` transaction is a chunk of binary `signed_data`, along with signature (`r`, `s` and `v`). The interpretation of the `signed_data` has not been specified, leading to several problems: - -* Standard Ethereum transactions can be submitted as `signed_data`. An Ethereum transaction can be unpacked, into the following components: `RLP` (hereby called `RLPdata`), `r`, `s` and `v`. If there are no syntactical constraints on `signed_data`, this means that `RLPdata` can be used as a syntactically valid `presigned` transaction. -* Multisignature wallets have also had the problem that a `presigned` transaction has not been tied to a particular `validator`, i.e a specific wallet. Example: - 1. Users `A`, `B` and `C` have the `2/3`-wallet `X` - 2. Users `A`, `B` and `D` have the `2/3`-wallet `Y` - 3. User `A` and `B` submit `presigned` transactions to `X`. - 4. Attacker can now reuse their presigned transactions to `X`, and submit to `Y`. - -## Specification - -We propose the following format for `signed_data` - -``` -0x19 <1 byte version> . -``` - -The initial `0x19` byte is intended to ensure that the `signed_data` is not valid RLP. - -> For a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. - -That means that any `signed_data` cannot be one RLP-structure, but a 1-byte `RLP` payload followed by something else. Thus, any EIP-191 `signed_data` can never be an Ethereum transaction. - -Additionally, `0x19` has been chosen because since ethereum/go-ethereum#2940 , the following is prepended before hashing in personal_sign: - -``` -"\x19Ethereum Signed Message:\n" + len(message). -``` - -Using `0x19` thus makes it possible to extend the scheme by defining a version `0x45` (`E`) to handle these kinds of signatures. - -### Registry of version bytes - -| Version byte | EIP | Description -| ------------ | -------------- | ----------- -| `0x00` | [191][eip-191] | Data with intended validator -| `0x01` | [712][eip-712] | Structured data -| `0x45` | [191][eip-191] | `personal_sign` messages - -#### Version `0x00` - -``` -0x19 <0x00> -``` - -The version `0x00` has `` for the version specific data. In the case of a Multisig wallet that perform an execution based on a passed signature, the validator address is the address of the Multisig itself. The data to sign could be any arbitrary data. - -#### Version `0x01` - -The version `0x01` is for structured data as defined in [EIP-712] - -#### Version `0x45` (E) - -``` -0x19 <0x45 (E)> -``` - -The version `0x45` (E) has `` for the version-specific data. The data to sign can be any arbitrary data. - -> NB: The `E` in `Ethereum Signed Message` refers to the version byte 0x45. The character `E` is `0x45` in hexadecimal which makes the remainder, `thereum Signed Message:\n + len(message)`, the version-specific data. - -[EIP-191]: ./eip-191.md -[EIP-712]: ./eip-712.md - -### Example - -The following snippets has been written in Solidity 0.8.0. - -#### Version `0x00` - -```solidity -function signatureBasedExecution(address target, uint256 nonce, bytes memory payload, uint8 v, bytes32 r, bytes32 s) public payable { - - // Arguments when calculating hash to validate - // 1: byte(0x19) - the initial 0x19 byte - // 2: byte(0) - the version byte - // 3: address(this) - the validator address - // 4-6 : Application specific data - - bytes32 hash = keccak256(abi.encodePacked(byte(0x19), byte(0), address(this), msg.value, nonce, payload)); - - // recovering the signer from the hash and the signature - addressRecovered = ecrecover(hash, v, r, s); - - // logic of the wallet - // if (addressRecovered == owner) executeOnTarget(target, payload); -} -``` -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-191.md diff --git a/EIPS/eip-1921.md b/EIPS/eip-1921.md index 9a671313f46a92..2baf06d2865feb 100644 --- a/EIPS/eip-1921.md +++ b/EIPS/eip-1921.md @@ -1,141 +1 @@ ---- -eip: 1921 -title: dType Functions Extension -author: Loredana Cirstea (@loredanacirstea), Christian Tzurcanu (@ctzurcanu) -discussions-to: https://github.com/ethereum/EIPs/issues/1921 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-04-06 -requires: 1900 ---- - -## Simple Summary -In the context of dType, the Decentralized Type System described in [EIP-1900](./eip-1900.md), we are proposing to add support for registering functions (with a preference for `pure` and `view`) in the dType Registry. - -## Abstract - -This proposal is part of a series of EIPs focused on expanding the concept of a Decentralized Type System, as explained in [EIP-1900](./eip-1900.md). -The current EIP specifies the data definitions and interfaces needed to support registering individual smart contract functions, as entries in the dType Registry. - -## Motivation - -In order to evolve the EVM into a Singleton Operating System, we need a way to register, find and address contract functions that we want to run in an automated way. -This implies having access to all the data needed to run the function inside the EVM. - -Aside from the above motivation, there are also near future benefits for this proposal. Having a globally available, non-custodial functions registry, will democratize the development of tools, such as those targeting: blockchain data analysis (e.g. block explorers), smart contract IDEs, security analysis of smart contracts. - -Registering new smart contract functions can be done through the same consensus mechanism as [EIP-1900](./eip-1900.md) mentions, in order to avoid burdening the chain state with redundant or improper records. - - -## Specification - -This specification targets `pure` and `view` functions. - -For each function, we can store: -* `name` - type `string` unique function name, as defined in EIP-1900; required -* `types` - the type data and label of each input, as defined in EIP-1900; required -* `outputs` - the type data and label of each output; required -* `contractAddress` - type `address` - smart contract where the function resides, as defined in EIP-1900; optional for interfaces -* `source` - type `bytes32` - reference to an external file containing the function source code, as defined in EIP-1900; optional - -Therefore, this proposal adds `outputs` to the EIP-1900 type registration definition. - -An example of a function registration object for the dType registry is: - -``` -{ - "name": "setStaked", - "types": [ - {"name": "TypeA", "label": "typeA", "relation":0, "dimensions":[]} - ], - "typeChoice": 4, - "contractAddress":
, - "source": , - "outputs": [ - {"name": "TypeB", "label": "typeB", "relation":0, "dimensions":[]} - ] -} -``` - -The above object will be passed to `.insert({...})` - -An additional `setOutputs` function is proposed for the dType registry: - -``` -function setOutputs( - bytes32 identifier, - dTypes[] memory outputs -) - public -``` - -- `identifier` - type `bytes32`, the type's identifier, as defined in EIP-1900 -- `outputs` - type `dTypes`, as defined in EIP-1900 - -### Implementation Suggestions - - -In the dType registry implementation, `outputs` can be stored in a `mapping`: - -``` -mapping(bytes32 => dTypes[]) public outputs; -``` - -## Rationale - - -The suggestion to treat each `pure` or `view` function as a separate entity instead of having a contract-based approach allows us to: -* have a global context of readily available functions -* scale designs through functional programming patterns rather than contract-encapsulated logic (which can be successfully used to scale development efforts independently) -* bidirectionally connect functions with the types they use, making automation easier -* cherry-pick functions from already deployed contracts if the other contract functions do not pass community consensus -* have scope-restricted improvements - instead of redeploying entire contracts, we can just redeploy the new function versions that we want to be added to the registry -* enable fine-grained auditing of individual functions, for the common good -* enable testing directly on a production chain, without state side-effects - -The proposal to store the minimum ABI information on-chain, for each function, allows us to: -* enable on-chain automation (e.g. function chaining and composition) -* be backward compatible in case the function signature format changes (e.g. from `bytes4` to `bytes32`): multiple signature calculation functions can be registered with dType. Examples: - -``` -function getSignatureBytes4(bytes32 identifier) - view - public - returns (bytes4 signature) - -function getSignatureBytes32(bytes32 identifier) - view - public - returns (bytes32 signature) -``` - -- `identifier` - the type's identifier, as defined in EIP-1900 -- `signature` - the function's signature - - -Concerns about this design might be: -* redundancy of storing `contractAddress` for each function that is part of the same contract - -We think that state/storage cost will be compensated through DRYness across the chain, due to reusing types and functions that have already been registered and are now easy to find. Other state/storage cost calculations will be added once the specification and implementation are closer to be finalized. - - -Note that the input and output types are based on types that have already been registered. This lowers the amount of ABI information needed to be stored for each function and enables developers to aggregate and find functions that use the same types for their I/O. This can be a powerful tool for interoperability and smart contract composition. - - -## Backwards Compatibility - -This proposal does not affect extant Ethereum standards or implementations. Registering functions for existing contract deployments should be fully supported. - -## Test Cases - -Will be added. - - -## Implementation - -In-work implementation examples can be found at https://github.com/pipeos-one/dType. -This proposal will be updated with an appropriate implementation when consensus is reached on the specifications. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1921.md diff --git a/EIPS/eip-1922.md b/EIPS/eip-1922.md index 2ab2ed7c8a05b1..354c6ac35b93eb 100644 --- a/EIPS/eip-1922.md +++ b/EIPS/eip-1922.md @@ -1,207 +1 @@ ---- -eip: 1922 -title: zk-SNARK Verifier Standard -author: Michael Connor , Chaitanya Konda , Duncan Westland -discussions-to: https://github.com/ethereum/EIPs/issues/1922 -type: Standards Track -category: ERC -status: Stagnant -created: 2018-09-14 -requires: 165, 196, 197 ---- - -## Simple Summary - -A standard interface for "Verifier" contracts which verify zk-SNARKs. - -## Abstract -The following standard allows for the implementation of a standard contract API for the verification of zk-SNARKs ("Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge"), also known as "proofs", "arguments", or "commitments". - -This standard provides basic functionality to load all necessary parameters for the verification of any zk-SNARK into a verifier contract, so that the proof may ultimately return a `true` or `false` response; corresponding to whether it has been verified or not verified. - -## Motivation -zk-SNARKs are a promising area of interest for the Ethereum community. Key applications of zk-SNARKs include: -- Private transactions -- Private computations -- Improved transaction scaling through proofs of "bundled" transactions - -A standard interface for verifying all zk-SNARKs will allow applications to more easily implement private transactions, private contracts, and scaling solutions; and to extract and interpret the limited information which gets emitted during zk-SNARK verifications. - -This standard was initially proposed by EY, and was inspired in particular by the requirements of businesses wishing to keep their agreements, transactions, and supply chain activities confidential—all whilst still benefiting from the commonly cited strengths of blockchains and smart contracts. - -:warning: TODO: Explain the benefits to and perspective of a consumer of information. I.e. the thing that interfaces with the standard verifier. - -## Specification -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -Terminology in this specification is used consistently with libsnark, as provided in that project's README. - -* Adhering Contract — A Verifier contract which adheres to this specification. -* Arithmetic circuit: An abstraction of logical statements into addition and multiplication gates. -* Public Inputs: often denoted as a vector 'x' in zk-SNARKs literature, and denoted `inputs` in this interface. An arithmetic circuit can be thought of as taking two parameters; the Public Inputs, 'x', and a secret 'witness', 'w'. This interface standardises functions which can load the `inputs` into an Adhering Contract. -* Proof: A 'prover' who wants to 'prove' knowledge of some secret witness 'w' (which satisfies an arithmetic circuit), generates a `proof` from: the circuit's Proving Key; their secret witness 'w'; and its corresponding Public Inputs 'x'. Together, a pair `(proof, inputs)` of satisfying `inputs` and their corresponding `proof` forms a zk-SNARK. -* Verification Key: A 'trusted setup' calculation creates both a public 'Proving Key' and a public 'Verification Key' from an arithmetic circuit. This interface does not provide a method for loading a Verification Key onto the blockchain. An Adhering Contract SHALL be able to accept arguments of knowledge (`(proof, inputs)` pairs) for at least one Verification Key. We shall call such Verification Keys 'in-scope' Verification Keys. An Adhering Contract MUST be able to interpret unambiguously a unique `verificationKeyId` for each of its 'in-scope' Verification Keys. - -**Every ERC-XXXX compliant verifier contract must implement the `ERCXXXX` and `ERC165` interfaces** (subject to "caveats" below): - - -```solidity -pragma solidity ^0.5.6; - -/// @title EIP-XXXX zk-SNARK Verifier Standard -/// @dev See https://github.com/EYBlockchain/zksnark-verifier-standard -/// Note: the ERC-165 identifier for this interface is 0xXXXXXXXX. -/// ⚠️ TODO: Calculate interface identifier -interface EIPXXXX /* is ERC165 */ { - /// @notice Checks the arguments of Proof, through elliptic curve - /// pairing functions. - /// @dev - /// MUST return `true` if Proof passes all checks (i.e. the Proof is - /// valid). - /// MUST return `false` if the Proof does not pass all checks (i.e. if the - /// Proof is invalid). - /// @param proof A zk-SNARK. - /// @param inputs Public inputs which accompany Proof. - /// @param verificationKeyId A unique identifier (known to this verifier - /// contract) for the Verification Key to which Proof corresponds. - /// @return result The result of the verification calculation. True - /// if Proof is valid; false otherwise. - function verify(uint256[] calldata proof, uint256[] calldata inputs, bytes32 verificationKeyId) external returns (bool result); -} -``` -### Interface -``` solidity -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -## Rationale - -### Taxonomy - -⚠️ TODO: Add a specific reference to libsnark here, explaining the choice of variable names. - -:warning: TODO: Explain how _C_ may not necessarily be a satisfiable arithmetic circuit of logical statements. As current, this is a limitation to certain kinds of SNARKS. Whereas the source references also mention polynomials, and other applications. - -_C_ — A satisfiable arithmetic circuit abstraction of logical statements. - -_lambda​_ - A random number, generated at the 'setup' phase - commonly referred to as 'toxic waste', because knowledge of _lambda​_ would allow an untrustworthy party to create 'false' proofs which would verify as 'true'. _lambda​_ must be destroyed. - -_pk​_ - The proving key for a particular circuit _C​_. - -_vk_ - The verification key for a particular circuit _C_. - -Both _pk​_ and _vk​_ are generated as a pair by some function _G​_: -_(pk, vk) = G(lambda, C)​_ - -Note: _C_ can be represented unambiguously by either of _pk_ or _vk_. In zk-SNARK constructions, _vk_ is much smaller in size than _pk_, so as to enable succinct verification on-chain. Hence, _vk_ is the representative of _C_ that is 'known' to the contract. Therefore, we can identify each circuit uniquely through some `verificationKeyId`, where `verificationKeyId` serves as a more succinct mapping to _vk_. - -_w_ - A 'private witness' string. A private argument to the circuit _C_ known only to the prover, which, when combined with the `inputs` argument _x_, comprises an argument of knowledge which satisfies the circuit _C_. - -_x_ or `inputs` - A vector of 'Public Inputs'. A public argument to the circuit _C_ which, when combined with the private witness string _w_, comprises an argument of knowledge which satisfies the circuit _C_. - -_pi_ or `proof` - an encoded vector of values which represents the 'prover's' 'argument of knowledge' of values _w_ and _x_ which satisfy the circuit _C_. -_pi = P(pk, x, w)_. - -The ultimate purpose of a Verifier contract, as specified in this EIP, is to verify a proof (of the form _pi​_) through some verification function _V​_. - -_V(vk, x, pi) = 1_, if there exists a _w_ s.t. _C(x,w)=1_. -_V(vk, x, pi) = 0_, otherwise. - -The `verify()` function of this specification serves the purpose of _V​_; returning either `true` (the proof has been verified to satisfy the arithmetic circuit) or `false` (the proof has not been verified). - -### Functions - -#### `verify` -The `verify` function forms the crux this standard. The parameters are intended to be as generic as possible, to allow for verification of any zk-SNARK: - -- `proof` - Specified as `uint256[]`. - `uint256` is the most appropriate type for elliptic curve operations over a finite field. Indeed, this type is used in the predominant 'Pairing library' implementation of zk-SNARKs by Christian Reitweissner. - A one-dimensional dynamic array has been chosen for several reasons: - - Dynamic: There are several possible methods for producing a zk-SNARK proof, including PGHR13, G16, GM17, and future methods might be developed in future. Although each method may produce differently sized proof objects, a dynamic array allows for these differing sizes. - - Array: An array has been chosen over a 'struct' object, because it is currently easier to pass dynamic arrays between functions in Solidity. Any proof 'struct' can be 'flattened' to an array and passed to the `verify` function. Interpretation of that flattened array is the responsibility of the implemented body of the function. Example implementations demonstrate that this can be achieved. - - One-dimensional: A one-dimensional array has been chosen over multi-dimensional array, because it is currently easier to work with one-dimensional arrays in Solidity. Any proof can be 'flattened' to a one-dimensional array and passed to the `verify` function. Interpretation of that flattened array is the responsibility of the implemented body of the Adhering Contract. Example implementations demonstrate that this can be achieved. - -- `inputs` - Specified as `uint256[]`. - `uint256` is the most appropriate type for elliptic curve operations over a finite field. Indeed, this type is used in the predominant 'Pairing library' implementation of zk-SNARKs by Christian Reitweissner. - The number of inputs will vary in size, depending on the number of 'public inputs' of the arithmetic circuit being verified against. In a similar vein to the `proof` parameter, a one-dimensional dynamic array is general enough to cope with any set of inputs to a zk-SNARK. - -- `verificationKeyId` - A verification key (referencing a particular arithmetic circuit) only needs to be stored on-chain once. Any proof (relating to the underlying arithmetic circuit) can then be verified against that verification key. Given this, it would be unnecessary (from a 'gas cost' point of view) to pass a duplicate of the full verification key to the `verify` function every time a new `(proof, inputs)` pair is passed in. We do however need to tell the Adhering Verifier Contract which verification key corresponds to the `(proof, inputs)` pair being passed in. A `verificationKeyId` serves this purpose - it uniquely represents a verification key as a `bytes32` id. A method for uniquely assigning a `verificationKeyId` to a verification key is the responsibility of the implemented body of the Adhering Contract. - - -## Backwards Compatibility -- At the time this EIP was first proposed, there was one implementation on the Ethereum main net - deployed by [EY](https://www.ey.com). This was compiled with Solidity 0.4.24 for compatibility with [Truffle](https://github.com/trufflesuite/truffle) but otherwise compatible with this standard, which is presented at the latest current version of Solidity. -- Dr Christian Reitwiessner's excellent [example](https://gist.github.com/chriseth/f9be9d9391efc5beb9704255a8e2989d) of a Verifier contract and elliptic curve pairing library has been instrumental in the Ethereum community's experimentation and development of zk-SNARK protocols. Many of the naming conventions of this EIP have been kept consistent with his example. -- Existing zk-SNARK compilers such as [ZoKrates](https://github.com/Zokrates/ZoKrates), which produce 'Verifier.sol' contracts, do not currently produce Verifier contracts which adhere to this EIP specification. - - :warning: TODO: Provide a converter contract or technique which allows ZoKrates verifier.sol contracts to adhere with this EIP. - - -## Test Cases - -Truffle tests of example implementations are included in the test case repository. - -⚠️ TODO: Reference specific test cases because there are many currently in the repository. - - -## Implementations -Detailed example implementations and Truffle tests of these example implementations are included in this repository. - -:warning: TODO: Update referenced verifier implementations so that they are ready-to-deploy or reference deployed versions of those implementations. At current, the referenced code specifically states "DO NOT USE THIS IN PRODUCTION". - -:warning: TODO: Provide reference to an implementation which interrogates a standard verifier contract that implements this standard. - - -## References - -:warning: TODO: Update references and confirm that each reference is cited (parenthetical documentation not necessary) in the text. - -**Standards** - -1. ERC-20 Token Standard. ./eip-20.md - -1. ERC-165 Standard Interface Detection. ./eip-165.md -1. ERC-173 Contract Ownership Standard (DRAFT). ./eip-173.md -1. ERC-196 Precompiled contracts for addition and scalar multiplication on the elliptic curve alt_bn128. ./eip-196.md -1. ERC-197 Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128. ./eip-197.md -1. Ethereum Name Service (ENS). https://ens.domains -1. RFC 2119 Key words for use in RFCs to Indicate Requirement Levels. https://www.ietf.org/rfc/rfc2119.txt - -##### Educational material: zk-SNARKs -1. Zcash. What are zk-SNARKs? https://z.cash/technology/zksnarks.html -1. Vitalik Buterin. zk-SNARKs: Under the Hood. https://medium.com/@VitalikButerin/zk-snarks-under-the-hood-b33151a013f6 -1. Christian Reitweissner. zk-SNARKs in a Nutshell. https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/ -1. Ben-Sasson, Chiesa, Tromer, et. al. Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture. https://eprint.iacr.org/2013/879.pdf - -##### Notable applications of zk-SNARKs - 1. EY. Implementation of a business agreement through Token Commitment transactions on the Ethereum mainnet. https://github.com/EYBlockchain/ZKPChallenge - 1. Zcash. https://z.cash - 1. Zcash. How Transactions Between Shielded Addresses Work. https://blog.z.cash/zcash-private-transactions/ - -##### Notable projects relating to zk-SNARKs - 1. libsnark: A C++ Library for zk-SNARKs ("project README)". https://github.com/scipr-lab/libsnark - 1. ZoKrates: Scalable Privacy-Preserving Off-Chain Computations. https://www.ise.tu-berlin.de/fileadmin/fg308/publications/2018/2018_eberhardt_ZoKrates.pdf - 1. ZoKrates Project Repository. https://github.com/JacobEberhardt/ZoKrates - 1. Joseph Stockermans. zkSNARKs: Driver's Ed. https://github.com/jstoxrocky/zksnarks_example - 1. Christian Reitweissner - snarktest.solidity. https://gist.github.com/chriseth/f9be9d9391efc5beb9704255a8e2989d - -##### Notable 'alternatives' to zk-SNARKs - areas of ongoing zero-knowledge proof research - 1. Vitalik Buterin. STARKs. https://vitalik.ca/general/2017/11/09/starks_part_1.html - 1. Bu ̈nz, Bootle, Boneh, et. al. Bulletproofs. https://eprint.iacr.org/2017/1066.pdf - 1. Range Proofs. https://www.cosic.esat.kuleuven.be/ecrypt/provpriv2012/abstracts/canard.pdf - 1. Apple. Secure Enclaves. https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_secure_enclave - 1. Intel Software Guard Extensions. https://software.intel.com/en-us/sgx - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1922.md diff --git a/EIPS/eip-1923.md b/EIPS/eip-1923.md index 2865c7dc9f8a99..58c9df15523718 100644 --- a/EIPS/eip-1923.md +++ b/EIPS/eip-1923.md @@ -1,164 +1 @@ ---- -eip: 1923 -title: zk-SNARK Verifier Registry Standard -author: Michael Connor , Chaitanya Konda , Duncan Westland -discussions-to: https://github.com/ethereum/EIPs/issues/1923 -type: Standards Track -category: ERC -status: Stagnant -created: 2018-12-22 -requires: 165, 196, 197 ---- - -## Simple Summary - - -A standard interface for a "Verifier Registry"'" contract, through which all zk-SNARK verification activity can be registered. - -## Abstract -The following standard allows for the implementation of a standard contract API for the registration of zk-SNARKs ("Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge"), also known as "proofs", "arguments", or "commitments". - -TODO: Which functionality is exposed in this standard interface? - -## Motivation -zk-SNARKs are a promising area of interest for the Ethereum community. Key applications of zk-SNARKs include: -- Private transactions -- Private computations -- Ethereum scaling through proofs of 'bundled' transactions - -A standard interface for registering all zk-SNARKs will allow applications to more easily implement private transactions, private contracts, and scaling solutions; and to extract and interpret the limited information which gets emitted during zk-SNARK verifications. - -:warning: TODO: Explain the motivation for standardizing a registry, other than simply standardizing the verifier interactions. - -⚠️ TODO: Explain the benefits to and perspective of a consumer of information. I.e. the thing that interfaces with the standard verifier registry. - -## Specification -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - - -```solidity -pragma solidity ^0.5.6; - -/// @title EIP-XXXX zk-SNARK Verifier Registry Standard -/// @dev See https://github.com/EYBlockchain/zksnark-verifier-standard -/// Note: the ERC-165 identifier for this interface is 0xXXXXXXXXX. -/// ⚠️ TODO: Set the interface identifier -interface EIP-XXXX /* is ERC165 */ { - - event NewProofSubmitted(bytes32 indexed _proofId, uint256[] _proof, uint64[] _inputs); - - event NewVkRegistered(bytes32 indexed _vkId); - - event NewVerifierContractRegistered(address indexed _contractAddress); - - event NewAttestation(bytes32 indexed _proofId, address indexed _verifier, bool indexed _result); - - - function getVk(bytes32 _vkId) external returns (uint256[] memory); - - function registerVerifierContract(address _verifierContract) external returns (bool); - - function registerVk(uint256[] calldata _vk, address[] calldata _verifierContracts) external returns (bytes32); - - function submitProof(uint256[] calldata _proof, uint64[] calldata _inputs, bytes32 _vkId) external returns (bytes32); - - function submitProof(uint256[] calldata _proof, uint64[] calldata _inputs, bytes32 _vkId, address _verifierContract) external returns (bytes32); - - function submitProofAndVerify(uint256[] calldata _proof, uint64[] calldata _inputs, bytes32 _vkId, address _verifierContract) external returns (bytes32); - - function attestProof(bytes32 _proofId, bytes32 _vkId, bool _result) external; - - function attestProofs(bytes32[] calldata _proofIds, bytes32[] calldata _vkIds, bool[] calldata _results) external; - - function challengeAttestation(bytes32 _proofId, uint256[] calldata _proof, uint64[] calldata _inputs, address _verifierContract) external; - - function createNewVkId(uint256[] calldata _vk) external pure returns (bytes32); - - function createNewProofId(uint256[] calldata _proof, uint64[] calldata _inputs) external pure returns (bytes32); - -} -``` -### Interface -``` solidity -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -## Rationale - -⚠️ TODO: Add Rationale section. - -### Backwards Compatibility - -⚠️ TODO: Add Backwards Compatibility section. - -### Test Cases - -Truffle tests of example implementations are included in this Repo. - -⚠️ TODO: Reference specific test cases because there are many currently in the repository. - - -## Implementations -Detailed example implementations and Truffle tests of these example implementations are included in this Repo. - -⚠️ TODO: Update referenced verifier registry implementations so that they are ready-to-deploy or reference deployed versions of those implementations. At current, the referenced code specifically states "DO NOT USE THIS IN PRODUCTION". - -⚠️ TODO: Provide reference to an implementation which interrogates a standard verifier registry contract that implements this standard. - - -## References - -⚠️ TODO: Update references and confirm that each reference is cited (parenthetical documentation not necessary) in the text. - -**Standards** - -1. ERC-20 Token Standard. ./eip-20.md - -1. ERC-165 Standard Interface Detection. ./eip-165.md -2. ERC-173 Contract Ownership Standard (DRAFT). ./eip-173.md -3. ERC-196 Precompiled contracts for addition and scalar multiplication on the elliptic curve alt_bn128. ./eip-196.md -4. ERC-197 Precompiled contracts for optimal ate pairing check on the elliptic curve alt_bn128. ./eip-197.md -5. Ethereum Name Service (ENS). https://ens.domains -6. RFC 2119 Key words for use in RFCs to Indicate Requirement Levels. https://www.ietf.org/rfc/rfc2119.txt - -##### Educational material: zk-SNARKs - -1. Zcash. What are zk-SNARKs? https://z.cash/technology/zksnarks.html -2. Vitalik Buterin. zk-SNARKs: Under the Hood. https://medium.com/@VitalikButerin/zk-snarks-under-the-hood-b33151a013f6 -3. Christian Reitweissner. zk-SNARKs in a Nutshell. https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/ -4. Ben-Sasson, Chiesa, Tromer, et. al. Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture. https://eprint.iacr.org/2013/879.pdf - -##### Notable applications of zk-SNARKs - -1. EY. Implementation of a business agreement through Token Commitment transactions on the Ethereum mainnet. https://github.com/EYBlockchain/ZKPChallenge -2. Zcash. https://z.cash -3. Zcash. How Transactions Between Shielded Addresses Work. https://blog.z.cash/zcash-private-transactions/ - -##### Notable projects relating to zk-SNARKs - -1. libsnark: A C++ Library for zk-SNARKs ("project README)". https://github.com/scipr-lab/libsnark -2. ZoKrates: Scalable Privacy-Preserving Off-Chain Computations. https://www.ise.tu-berlin.de/fileadmin/fg308/publications/2018/2018_eberhardt_ZoKrates.pdf -3. ZoKrates Project Repository. https://github.com/JacobEberhardt/ZoKrates -4. Joseph Stockermans. zkSNARKs: Driver's Ed. https://github.com/jstoxrocky/zksnarks_example -5. Christian Reitweissner - snarktest.solidity. https://gist.github.com/chriseth/f9be9d9391efc5beb9704255a8e2989d - -##### Notable 'alternatives' to zk-SNARKs - areas of ongoing zero-knowledge proof research - -1. Vitalik Buterin. STARKs. https://vitalik.ca/general/2017/11/09/starks_part_1.html -2. Bu ̈nz, Bootle, Boneh, et. al. Bulletproofs. https://eprint.iacr.org/2017/1066.pdf -3. Range Proofs. https://www.cosic.esat.kuleuven.be/ecrypt/provpriv2012/abstracts/canard.pdf -4. Apple. Secure Enclaves. https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_secure_enclave -5. Intel Software Guard Extensions. https://software.intel.com/en-us/sgx - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1923.md diff --git a/EIPS/eip-1948.md b/EIPS/eip-1948.md index 2b58a1afff421c..da8a45b61c9621 100644 --- a/EIPS/eip-1948.md +++ b/EIPS/eip-1948.md @@ -1,159 +1 @@ ---- -eip: 1948 -title: Non-fungible Data Token -author: Johann Barbie (@johannbarbie), Ben Bollen , pinkiebell (@pinkiebell) -discussions-to: https://ethereum-magicians.org/t/erc-non-fungible-data-token/3139 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-04-18 -requires: 721 ---- - -## Simple Summary - -Some NFT use-cases require to have dynamic data associated with a non-fungible token that can change during its lifetime. Examples for dynamic data: -- cryptokitties that can change color -- intellectual property tokens that encode rights holders -- tokens that store data to transport them across chains - -The existing metadata standard does not suffice as data can only be set at minting time and not modified later. - -## Abstract - -Non-fungible tokens (NFTs) are extended with the ability to store dynamic data. A 32 bytes data field is added and a read function allows to access it. The write function allows to update it, if the caller is the owner of the token. An event is emitted every time the data updates and the previous and new value is emitted in it. - -## Motivation - -The proposal is made to standardize on tokens with dynamic data. Interactions with bridges for side-chains like xDAI or Plasma chains will profit from the ability to use such tokens. Protocols that build on data tokens like [distributed breeding](https://ethresear.ch/t/a-distributed-breeding-function/5264) will be enabled. - -## Specification - -An extension of [ERC-721](./eip-721.md) interface with the following functions and events is suggested: - -``` solidity -pragma solidity ^0.5.2; - -/** - * @dev Interface of the ERC1948 contract. - */ -interface IERC1948 { - - /** - * @dev Emitted when `oldData` is replaced with `newData` in storage of `tokenId`. - * - * Note that `oldData` or `newData` may be empty bytes. - */ - event DataUpdated(uint256 indexed tokenId, bytes32 oldData, bytes32 newData); - - /** - * @dev Reads the data of a specified token. Returns the current data in - * storage of `tokenId`. - * - * @param tokenId The token to read the data off. - * - * @return A bytes32 representing the current data stored in the token. - */ - function readData(uint256 tokenId) external view returns (bytes32); - - /** - * @dev Updates the data of a specified token. Writes `newData` into storage - * of `tokenId`. - * - * @param tokenId The token to write data to. - * @param newData The data to be written to the token. - * - * Emits a `DataUpdated` event. - */ - function writeData(uint256 tokenId, bytes32 newData) external; - -} -``` - -## Rationale - -The suggested data field in the NFT is used either for storing data directly, like a counter or address. If more data is required the implementer should fall back to authenticated data structures, like merkle- or patricia-trees. - -The proposal for this ERC stems from the [distributed breeding proposal](https://ethresear.ch/t/a-distributed-breeding-function/5264) to allow better integration of NFTs across side-chains. [ost.com](https://ost.com/), [Skale](https://skalelabs.com/), [POA](https://poa.network/), and [LeapDAO](https://leapdao.org/) have been part of the discussion. - -## Backwards Compatibility - -🤷‍♂️ No related proposals are known to the author, hence no backwards compatibility to consider. - -## Test Cases - -Simple happy test: - -``` javascript -const ERC1948 = artifacts.require('./ERC1948.sol'); - -contract('ERC1948', (accounts) => { - const firstTokenId = 100; - const empty = '0x0000000000000000000000000000000000000000000000000000000000000000'; - const data = '0x0101010101010101010101010101010101010101010101010101010101010101'; - let dataToken; - - beforeEach(async () => { - dataToken = await ERC1948.new(); - await dataToken.mint(accounts[0], firstTokenId); - }); - - it('should allow to write and read', async () => { - let rsp = await dataToken.readData(firstTokenId); - assert.equal(rsp, empty); - await dataToken.writeData(firstTokenId, data); - rsp = await dataToken.readData(firstTokenId); - assert.equal(rsp, data); - }); - -}); -``` - - -## Implementation - -An example implementation of the interface in solidity would look like this: - -``` solidity -/** - * @dev Implementation of ERC721 token and the `IERC1948` interface. - * - * ERC1948 is a non-fungible token (NFT) extended with the ability to store - * dynamic data. The data is a bytes32 field for each tokenId. If 32 bytes - * do not suffice to store the data, an authenticated data structure (hash or - * merkle tree) shall be used. - */ -contract ERC1948 is IERC1948, ERC721 { - - mapping(uint256 => bytes32) data; - - /** - * @dev See `IERC1948.readData`. - * - * Requirements: - * - * - `tokenId` needs to exist. - */ - function readData(uint256 tokenId) external view returns (bytes32) { - require(_exists(tokenId)); - return data[tokenId]; - } - - /** - * @dev See `IERC1948.writeData`. - * - * Requirements: - * - * - `msg.sender` needs to be owner of `tokenId`. - */ - function writeData(uint256 tokenId, bytes32 newData) external { - require(msg.sender == ownerOf(tokenId)); - emit DataUpdated(tokenId, data[tokenId], newData); - data[tokenId] = newData; - } - -} -``` - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1948.md diff --git a/EIPS/eip-1967.md b/EIPS/eip-1967.md index 98165407df80b8..f34f9d062fd5f9 100644 --- a/EIPS/eip-1967.md +++ b/EIPS/eip-1967.md @@ -1,468 +1 @@ ---- -eip: 1967 -title: Proxy Storage Slots -description: A consistent location where proxies store the address of the logic contract they delegate to, as well as other proxy-specific information. -author: Santiago Palladino (@spalladino), Francisco Giordano (@frangio), Hadrien Croubois (@Amxx) -discussions-to: https://ethereum-magicians.org/t/eip-1967-standard-proxy-storage-slots/3185 -status: Final -type: Standards Track -category: ERC -created: 2019-04-24 ---- - -## Abstract -Delegating **proxy contracts** are widely used for both upgradeability and gas savings. These proxies rely on a **logic contract** (also known as implementation contract or master copy) that is called using `delegatecall`. This allows proxies to keep a persistent state (storage and balance) while the code is delegated to the logic contract. - -To avoid clashes in storage usage between the proxy and logic contract, the address of the logic contract is typically saved in a specific storage slot (for example `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` in OpenZeppelin contracts) guaranteed to be never allocated by a compiler. This EIP proposes a set of standard slots to store proxy information. This allows clients like block explorers to properly extract and show this information to end users, and logic contracts to optionally act upon it. - -## Motivation -Delegating proxies are widely in use, as a means to both support upgrades and reduce gas costs of deployments. Examples of these proxies are found in OpenZeppelin Contracts, Gnosis, AragonOS, Melonport, Limechain, WindingTree, Decentraland, and many others. - -However, the lack of a common interface for obtaining the logic address for a proxy makes it impossible to build common tools that act upon this information. - -A classic example of this is a block explorer. Here, the end user wants to interact with the underlying logic contract and not the proxy itself. Having a common way to retrieve the logic contract address from a proxy allows a block explorer to show the ABI of the logic contract and not that of the proxy. The explorer checks the storage of the contract at the distinguished slots to determine if it is indeed a proxy, in which case it shows information on both the proxy and the logic contract. As an example, this is how `0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48` is shown on Etherscan: - -![Sample proxy on Etherscan](../assets/eip-1967/Sample-proxy-on-etherscan.png) - -Another example is logic contracts that explicitly act upon the fact that they are being proxied. This allows them to potentially trigger a code update as part of their logic. A common storage slot allows these use cases independently of the specific proxy implementation being used. - -## Specification -Monitoring of proxies is essential to the security of many applications. It is thus essential to have the ability to track changes to the implementation and admin slots. Unfortunately, tracking changes to storage slots is not easy. Consequently, it is recommended that any function that changes any of these slots SHOULD also emit the corresponding event. This includes initialization, from `0x0` to the first non-zero value. - -The proposed storage slots for proxy-specific information are the following. More slots for additional information can be added in subsequent ERCs as needed. - -### Logic contract address - -Storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` -(obtained as `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`). - -Holds the address of the logic contract that this proxy delegates to. SHOULD be empty if a beacon is used instead. Changes to this slot SHOULD be notified by the event: - -```solidity -event Upgraded(address indexed implementation); -``` - -### Beacon contract address - -Storage slot `0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50` (obtained as `bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)`). - -Holds the address of the beacon contract this proxy relies on (fallback). SHOULD be empty if a logic address is used directly instead, and should only be considered if the logic contract slot is empty. Changes to this slot SHOULD be notified by the event: - -```solidity -event BeaconUpgraded(address indexed beacon); -``` - -Beacons are used for keeping the logic address for multiple proxies in a single location, allowing the upgrade of multiple proxies by modifying a single storage slot. A beacon contract MUST implement the function: - -``` -function implementation() returns (address) -``` - -Beacon based proxy contracts do not use the logic contract slot. Instead, they use the beacon contract slot to store the address of the beacon they are attached to. In order to know the logic contract used by a beacon proxy, a client SHOULD: - -- Read the address of the beacon for the beacon logic storage slot; -- Call the `implementation()` function on the beacon contract. - -The result of the `implementation()` function on the beacon contract SHOULD NOT depend on the caller (`msg.sender`). - - -### Admin address - -Storage slot `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` -(obtained as `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`). - -Holds the address that is allowed to upgrade the logic contract address for this proxy (optional). Changes to this slot SHOULD be notified by the event: - -```solidity -event AdminChanged(address previousAdmin, address newAdmin); -``` - -## Rationale - -This EIP standardises the **storage slot** for the logic contract address, instead of a public method on the proxy contract. The rationale for this is that proxies should never expose functions to end users that could potentially clash with those of the logic contract. - -Note that a clash may occur even among functions with different names, since the ABI relies on just four bytes for the function selector. This can lead to unexpected errors, or even exploits, where a call to a proxied contract returns a different value than expected, since the proxy intercepts the call and answers with a value of its own. - -From _Malicious backdoors in Ethereum proxies_ by Nomic Labs: - -> Any function in the Proxy contract whose selector matches with one in the implementation contract will be called directly, completely skipping the implementation code. -> -> Because the function selectors use a fixed amount of bytes, there will always be the possibility of a clash. This isn’t an issue for day to day development, given that the Solidity compiler will detect a selector clash within a contract, but this becomes exploitable when selectors are used for cross-contract interaction. Clashes can be abused to create a seemingly well-behaved contract that’s actually concealing a backdoor. - -The fact that proxy public functions are potentially exploitable makes it necessary to standardise the logic contract address in a different way. - -The main requirement for the storage slots chosen is that they must never be picked by the compiler to store any contract state variable. Otherwise, a logic contract could inadvertently overwrite this information on the proxy when writing to a variable of its own. - -Solidity maps variables to storage based on the order in which they were declared, after the contract inheritance chain is linearized: the first variable is assigned the first slot, and so on. The exception is values in dynamic arrays and mappings, which are stored in the hash of the concatenation of the key and the storage slot. The Solidity development team has confirmed that the storage layout is to be preserved among new versions: - -> The layout of state variables in storage is considered to be part of the external interface of Solidity due to the fact that storage pointers can be passed to libraries. This means that any change to the rules outlined in this section is considered a breaking change of the language and due to its critical nature should be considered very carefully before being executed. In the event of such a breaking change, we would want to release a compatibility mode in which the compiler would generate bytecode supporting the old layout. - -Vyper seems to follow the same strategy as Solidity. Note that contracts written in other languages, or directly in assembly, may incur in clashes. - -They are chosen in such a way so they are guaranteed to not clash with state variables allocated by the compiler, since they depend on the hash of a string that does not start with a storage index. Furthermore, a `-1` offset is added so the preimage of the hash cannot be known, further reducing the chances of a possible attack. - -## Reference Implementation - -```solidity -/** - * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an - * implementation address that can be changed. This address is stored in storage in the location specified by - * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the - * implementation behind the proxy. - */ -contract ERC1967Proxy is Proxy, ERC1967Upgrade { - /** - * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. - * - * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded - * function call, and allows initializing the storage of the proxy like a Solidity constructor. - */ - constructor(address _logic, bytes memory _data) payable { - assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); - _upgradeToAndCall(_logic, _data, false); - } - - /** - * @dev Returns the current implementation address. - */ - function _implementation() internal view virtual override returns (address impl) { - return ERC1967Upgrade._getImplementation(); - } -} - -/** - * @dev This abstract contract provides getters and event emitting update functions for - * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. - */ -abstract contract ERC1967Upgrade { - // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 - bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; - - /** - * @dev Storage slot with the address of the current implementation. - * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is - * validated in the constructor. - */ - bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - - /** - * @dev Emitted when the implementation is upgraded. - */ - event Upgraded(address indexed implementation); - - /** - * @dev Returns the current implementation address. - */ - function _getImplementation() internal view returns (address) { - return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; - } - - /** - * @dev Stores a new address in the EIP1967 implementation slot. - */ - function _setImplementation(address newImplementation) private { - require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); - StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; - } - - /** - * @dev Perform implementation upgrade - * - * Emits an {Upgraded} event. - */ - function _upgradeTo(address newImplementation) internal { - _setImplementation(newImplementation); - emit Upgraded(newImplementation); - } - - /** - * @dev Perform implementation upgrade with additional setup call. - * - * Emits an {Upgraded} event. - */ - function _upgradeToAndCall( - address newImplementation, - bytes memory data, - bool forceCall - ) internal { - _upgradeTo(newImplementation); - if (data.length > 0 || forceCall) { - Address.functionDelegateCall(newImplementation, data); - } - } - - /** - * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. - * - * Emits an {Upgraded} event. - */ - function _upgradeToAndCallSecure( - address newImplementation, - bytes memory data, - bool forceCall - ) internal { - address oldImplementation = _getImplementation(); - - // Initial upgrade and setup call - _setImplementation(newImplementation); - if (data.length > 0 || forceCall) { - Address.functionDelegateCall(newImplementation, data); - } - - // Perform rollback test if not already in progress - StorageSlot.BooleanSlot storage rollbackTesting = StorageSlot.getBooleanSlot(_ROLLBACK_SLOT); - if (!rollbackTesting.value) { - // Trigger rollback using upgradeTo from the new implementation - rollbackTesting.value = true; - Address.functionDelegateCall( - newImplementation, - abi.encodeWithSignature("upgradeTo(address)", oldImplementation) - ); - rollbackTesting.value = false; - // Check rollback was effective - require(oldImplementation == _getImplementation(), "ERC1967Upgrade: upgrade breaks further upgrades"); - // Finally reset to the new implementation and log the upgrade - _upgradeTo(newImplementation); - } - } - - /** - * @dev Storage slot with the admin of the contract. - * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is - * validated in the constructor. - */ - bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; - - /** - * @dev Emitted when the admin account has changed. - */ - event AdminChanged(address previousAdmin, address newAdmin); - - /** - * @dev Returns the current admin. - */ - function _getAdmin() internal view returns (address) { - return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; - } - - /** - * @dev Stores a new address in the EIP1967 admin slot. - */ - function _setAdmin(address newAdmin) private { - require(newAdmin != address(0), "ERC1967: new admin is the zero address"); - StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; - } - - /** - * @dev Changes the admin of the proxy. - * - * Emits an {AdminChanged} event. - */ - function _changeAdmin(address newAdmin) internal { - emit AdminChanged(_getAdmin(), newAdmin); - _setAdmin(newAdmin); - } - - /** - * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. - * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. - */ - bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; - - /** - * @dev Emitted when the beacon is upgraded. - */ - event BeaconUpgraded(address indexed beacon); - - /** - * @dev Returns the current beacon. - */ - function _getBeacon() internal view returns (address) { - return StorageSlot.getAddressSlot(_BEACON_SLOT).value; - } - - /** - * @dev Stores a new beacon in the EIP1967 beacon slot. - */ - function _setBeacon(address newBeacon) private { - require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract"); - require( - Address.isContract(IBeacon(newBeacon).implementation()), - "ERC1967: beacon implementation is not a contract" - ); - StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; - } - - /** - * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does - * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). - * - * Emits a {BeaconUpgraded} event. - */ - function _upgradeBeaconToAndCall( - address newBeacon, - bytes memory data, - bool forceCall - ) internal { - _setBeacon(newBeacon); - emit BeaconUpgraded(newBeacon); - if (data.length > 0 || forceCall) { - Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); - } - } -} - -/** - * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM - * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to - * be specified by overriding the virtual {_implementation} function. - * - * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a - * different contract through the {_delegate} function. - * - * The success and return data of the delegated call will be returned back to the caller of the proxy. - */ -abstract contract Proxy { - /** - * @dev Delegates the current call to `implementation`. - * - * This function does not return to its internal call site, it will return directly to the external caller. - */ - function _delegate(address implementation) internal virtual { - assembly { - // Copy msg.data. We take full control of memory in this inline assembly - // block because it will not return to Solidity code. We overwrite the - // Solidity scratch pad at memory position 0. - calldatacopy(0, 0, calldatasize()) - - // Call the implementation. - // out and outsize are 0 because we don't know the size yet. - let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) - - // Copy the returned data. - returndatacopy(0, 0, returndatasize()) - - switch result - // delegatecall returns 0 on error. - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } - - /** - * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function - * and {_fallback} should delegate. - */ - function _implementation() internal view virtual returns (address); - - /** - * @dev Delegates the current call to the address returned by `_implementation()`. - * - * This function does not return to its internal call site, it will return directly to the external caller. - */ - function _fallback() internal virtual { - _beforeFallback(); - _delegate(_implementation()); - } - - /** - * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other - * function in the contract matches the call data. - */ - fallback() external payable virtual { - _fallback(); - } - - /** - * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data - * is empty. - */ - receive() external payable virtual { - _fallback(); - } - - /** - * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` - * call, or as part of the Solidity `fallback` or `receive` functions. - * - * If overridden should call `super._beforeFallback()`. - */ - function _beforeFallback() internal virtual {} -} - -/** - * @dev Library for reading and writing primitive types to specific storage slots. - * - * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. - * This library helps with reading and writing to such slots without the need for inline assembly. - * - * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. - */ -library StorageSlot { - struct AddressSlot { - address value; - } - - struct BooleanSlot { - bool value; - } - - struct Bytes32Slot { - bytes32 value; - } - - struct Uint256Slot { - uint256 value; - } - - /** - * @dev Returns an `AddressSlot` with member `value` located at `slot`. - */ - function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { - assembly { - r.slot := slot - } - } - - /** - * @dev Returns an `BooleanSlot` with member `value` located at `slot`. - */ - function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { - assembly { - r.slot := slot - } - } - - /** - * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. - */ - function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { - assembly { - r.slot := slot - } - } - - /** - * @dev Returns an `Uint256Slot` with member `value` located at `slot`. - */ - function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { - assembly { - r.slot := slot - } - } -} -``` - -## Security Considerations - -This ERC relies on the fact that the chosen storage slots are **not** to be allocated by the solidity compiler. This guarantees that an implementation contract will not accidentally overwrite any of the information required for the proxy to operate. As such, locations with a high slot number were chosen to avoid clashes with the slots allocated by the compiler. Also, locations with no known preimage were picked, to ensure that a write to mapping with a maliciously crafted key could not overwrite it. - -Logic contracts that intend to modify proxy-specific information must do so deliberately (as is the case with UUPS) by writing to the specific storage slot. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1967.md diff --git a/EIPS/eip-1973.md b/EIPS/eip-1973.md index d3c94b9bf0850b..fd5b814f9b138c 100644 --- a/EIPS/eip-1973.md +++ b/EIPS/eip-1973.md @@ -1,270 +1 @@ ---- -eip: 1973 -title: Scalable Rewards -author: Lee Raj (@lerajk), Qin Jian (@qinjian) -type: Standards Track -category: ERC -status: Stagnant -created: 2019-04-01 ---- - -## Simple Summary - - A mintable token rewards interface that mints 'n' tokens per block which are distributed equally among the 'm' participants in the DAPP's ecosystem. - -## Abstract - - The mintable token rewards interface allows DApps to build a token economy where token rewards are distributed equally among the active participants. The tokens are minted based on per block basis that are configurable (E.g. 10.2356 tokens per block, 0.1 token per block, 1350 tokens per block) and the mint function can be initiated by any active participant. The token rewards distributed to each participant is dependent on the number of participants in the network. At the beginning, when the network has low volume, the tokens rewards per participant is high but as the network scales the token rewards decreases dynamically. - - - ## Motivation - -Distributing tokens through a push system to a large amount of participants fails due to block gas limit. As the number of participants in the network grow to tens of thousands, keeping track of the iterable registry of participants and their corresponding rewards in a push system becomes unmanagable. E.g. Looping through 5000 addresses to distribute 0.0000001 reward tokens is highly inefficient. Furthermore, the gas fees in these transactions are high and needs to be undertaken by the DApp developer or the respective company, leading to centralization concerns. - -A pull system is required to keep the application completely decentralized and to avoid the block gas limit problem. However, no standard solution has been proposed to distribute scalable rewards to tens of thousands participants with a pull system. This is what we propose with this EIP through concepts like TPP, round mask, participant mask. - -## Specification - -### Definitions - - `token amount per participant in the ecosytem or TPP (token per participant)`: TPP = (token amount to mint / total active participants) - - `roundMask`: the cumulative snapshot of TPP over time for the token contract. E.g. transactionOne = 10 tokens are minted with 100 available participants (TPP = 10 / 100) , transactionTwo = 12 tokens are minted with 95 participants (TPP = 12 / 95 ) - - roundMask = (10/100) + (12/95) - - `participantMask`: is used to keep track of a `msg.sender` (participant) rewards over time. When a `msg.sender` joins or leaves the ecosystem, the player mask is updated - - participantMask = previous roundMask OR (current roundMask - TPP) - - `rewards for msg.sender`: roundMask - participantMask - - E.g. Let's assume a total of 6 transactions (smart contract triggers or functions calls) are in place with 10 existing participants (denominator) and 20 tokens (numerator) are minted per transaction. At 2nd transaction, the 11th participant joins the network and exits before 5th transaction, the 11th participant's balance is as follows: - - ``` - t1 roundMask = (20/10) - t2 roundMask = (20/10) + (20/11) - t3 roundMask = (20/10) + (20/11) + (20/11) - t4 roundMask = (20/10) + (20/11) + (20/11) + (20/11) - t5 roundMask = (20/10) + (20/11) + (20/11) + (20/11)+ (20/10) - t6 roundMask = (20/10) + (20/11) + (20/11) + (20/11)+ (20/10) + (20/10) - ``` - - Total tokens released in 6 transactions = 60 tokens - - As the participant joins at t2 and leaves before t5, the participant deserves the rewards between t2 and t4. When the participant joins at t2, the 'participantMask = (20/10)', when the participant leaves before t5, the cumulative deserved reward tokens are : - - rewards for msg.sender: `[t4 roundMask = (20/10) + (20/11)+ (20/11) + (20/11)] - [participantMask = (20/10)] = [rewards = (20/11)+ (20/11) + (20/11)]` - - When the same participant joins the ecosystem at a later point (t27 or t35), a new 'participantMask' is given that is used to calculate the new deserved reward tokens when the participant exits. This process continues dynamically for each participant. - - `tokensPerBlock`: the amount of tokens that will be released per block - - `blockFreezeInterval`: the number of blocks that need to pass until the next mint. E.g. if set to 50 and 'n' tokens were minted at block 'b', the next 'n' tokens won't be minted until 'b + 50' blocks have passed - - `lastMintedBlockNumber`: the block number on which last 'n' tokens were minted - - `totalParticipants` : the total number of participants in the DApp network - - `tokencontractAddress` : the contract address to which tokens will be minted, default is address(this) - -```solidity - -pragma solidity ^0.5.2; - -import "openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol"; -import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; - -contract Rewards is ERC20Mintable, ERC20Detailed { - -using SafeMath for uint256; - -uint256 public roundMask; -uint256 public lastMintedBlockNumber; -uint256 public totalParticipants = 0; -uint256 public tokensPerBlock; -uint256 public blockFreezeInterval; -address public tokencontractAddress = address(this); -mapping(address => uint256) public participantMask; - -/** - * @dev constructor, initializes variables. - * @param _tokensPerBlock The amount of token that will be released per block, entered in wei format (E.g. 1000000000000000000) - * @param _blockFreezeInterval The amount of blocks that need to pass (E.g. 1, 10, 100) before more tokens are brought into the ecosystem. - */ - constructor(uint256 _tokensPerBlock, uint256 _blockFreezeInterval) public ERC20Detailed("Simple Token", "SIM", 18){ -lastMintedBlockNumber = block.number; -tokensPerBlock = _tokensPerBlock; -blockFreezeInterval = _blockFreezeInterval; -} - -/** - * @dev Modifier to check if msg.sender is whitelisted as a minter. - */ -modifier isAuthorized() { -require(isMinter(msg.sender)); -_; -} - -/** - * @dev Function to add participants in the network. - * @param _minter The address that will be able to mint tokens. - * @return A boolean that indicates if the operation was successful. - */ -function addMinters(address _minter) external returns (bool) { -_addMinter(_minter); -totalParticipants = totalParticipants.add(1); -updateParticipantMask(_minter); -return true; -} - - -/** - * @dev Function to remove participants in the network. - * @param _minter The address that will be unable to mint tokens. - * @return A boolean that indicates if the operation was successful. - */ -function removeMinters(address _minter) external returns (bool) { -totalParticipants = totalParticipants.sub(1); -_removeMinter(_minter); -return true; -} - - -/** - * @dev Function to introduce new tokens in the network. - * @return A boolean that indicates if the operation was successful. - */ -function trigger() external isAuthorized returns (bool) { -bool res = readyToMint(); -if(res == false) { -return false; -} else { -mintTokens(); -return true; -} -} - -/** - * @dev Function to withdraw rewarded tokens by a participant. - * @return A boolean that indicates if the operation was successful. - */ -function withdraw() external isAuthorized returns (bool) { -uint256 amount = calculateRewards(); -require(amount >0); -ERC20(tokencontractAddress).transfer(msg.sender, amount); -} - -/** - * @dev Function to check if new tokens are ready to be minted. - * @return A boolean that indicates if the operation was successful. - */ -function readyToMint() public view returns (bool) { -uint256 currentBlockNumber = block.number; -uint256 lastBlockNumber = lastMintedBlockNumber; -if(currentBlockNumber > lastBlockNumber + blockFreezeInterval) { -return true; -} else { -return false; -} -} - -/** - * @dev Function to calculate current rewards for a participant. - * @return A uint that returns the calculated rewards amount. - */ -function calculateRewards() private returns (uint256) { -uint256 playerMask = participantMask[msg.sender]; -uint256 rewards = roundMask.sub(playerMask); -updateParticipantMask(msg.sender); -return rewards; -} - -/** - * @dev Function to mint new tokens into the economy. - * @return A boolean that indicates if the operation was successful. - */ -function mintTokens() private returns (bool) { -uint256 currentBlockNumber = block.number; -uint256 tokenReleaseAmount = (currentBlockNumber.sub(lastMintedBlockNumber)).mul(tokensPerBlock); -lastMintedBlockNumber = currentBlockNumber; -mint(tokencontractAddress, tokenReleaseAmount); -calculateTPP(tokenReleaseAmount); -return true; -} - - /** -* @dev Function to calculate TPP (token amount per participant). -* @return A boolean that indicates if the operation was successful. -*/ -function calculateTPP(uint256 tokens) private returns (bool) { -uint256 tpp = tokens.div(totalParticipants); -updateRoundMask(tpp); -return true; -} - - /** -* @dev Function to update round mask. -* @return A boolean that indicates if the operation was successful. -*/ -function updateRoundMask(uint256 tpp) private returns (bool) { -roundMask = roundMask.add(tpp); -return true; -} - - /** -* @dev Function to update participant mask (store the previous round mask) -* @return A boolean that indicates if the operation was successful. -*/ -function updateParticipantMask(address participant) private returns (bool) { -uint256 previousRoundMask = roundMask; -participantMask[participant] = previousRoundMask; -return true; -} - -} -``` - -## Rationale - -Currently, there is no standard for a scalable reward distribution mechanism. In order to create a sustainable cryptoeconomic environment within DAPPs, incentives play a large role. However, without a scalable way to distribute rewards to tens of thousands of participants, most DAPPs lack a good incentive structure. The ones with a sustainable cryptoeconomic environment depend heavily on centralized servers or a group of selective nodes to trigger the smart contracts. But, in order to keep an application truly decentralized, the reward distribution mechanism must depend on the active participants itself and scale as the number of participants grow. This is what this EIP intends to accomplish. - -## Backwards Compatibility - -Not Applicable. - -## Test Cases - -WIP, will be added. - -## Implementation - -WIP, a proper implementation will be added later.A sample example is below: - -`etherscan rewards contract` : https://ropsten.etherscan.io/address/0x8b0abfc541ab7558857816a67e186221adf887bc#tokentxns - -`Step 1` : deploy Rewards contract with the following parameters_tokensPerBlock = 1e18, _blockFreezeInterval = 1 - -`Step 2` : add Alice(0x123) and Bob(0x456) as minters, addMinters(address _minter) - -`Step 3` : call trigger() from Alice / Bob's account. 65 blocks are passed, hence 65 SIM tokens are minted. The RM is 32500000000000000000 - -`Step 4` : Alice withdraws and receives 32.5 SIM tokens (65 tokens / 2 participants) and her PM = 32500000000000000000 - -`Step 5` : add Satoshi(0x321) and Vitalik(0x654) as minters, addMinters(address _minter) - -`Step 6` : call trigger() from Alice / Bob's / Satoshi / Vitalik account. 101 blocks are passed, hence 101 SIM tokens are minted. The RM is 57750000000000000000 - -`Step 7` : Alice withdraws and receives 25.25 SIM tokens (101 tokens / 4 participants) and her PM = 57750000000000000000 - -`Step 8` : Bob withdraws and receives 57.75 SIM tokens ((65 tokens / 2 participants) + (101 tokens / 4 participants)). Bob's PM = 57750000000000000000 - -## Copyright - -Copyright and related rights waived via CC0. - -## References - -1. Scalable Reward Distribution on the Ethereum Blockchain by Bogdan Batog, Lucian Boca and Nick Johnson - -2. Fomo3d DApp, https://fomo3d.hostedwiki.co/ +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1973.md diff --git a/EIPS/eip-1996.md b/EIPS/eip-1996.md index fffb24a77cb62c..8abb6c4ee4e916 100644 --- a/EIPS/eip-1996.md +++ b/EIPS/eip-1996.md @@ -1,294 +1 @@ ---- -eip: 1996 -title: Holdable Token -author: Julio Faura , Fernando Paris , Daniel Lehrner -discussions-to: https://github.com/ethereum/EIPs/issues/2103 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-04-10 -requires: 20 ---- - -## Simple Summary -An extension to the ERC-20 standard token that allows tokens to be put on hold. This guarantees a future transfer and makes the held tokens unavailable for transfer in the mean time. Holds are similar to escrows in that are firm and lead to final settlement. - -## Actors - -#### Operator -An account which has been approved by an account to create holds on its behalf. - -#### Hold issuer -The account, which creates a hold. This can be the account owner itself, or any account, which has been approved as an operator for the account. - -#### Notary -The account which decides if a hold should be executed. - -## Abstract -A hold specifies a payer, a payee, a maximum amount, a notary and an expiration time. When the hold is created, the specified token balance from the payer is put on hold. A held balance cannot be transferred until the hold is either executed or released. The hold can only be executed by the notary, which triggers the transfer of the tokens from the payer to the payee. If a hold is released, either by the notary at any time, or by anyone after the expiration, no transfer is carried out and the amount is available again for the payer. - -A hold can be partially executed, if the execution specifies an amount less than the maximum amount. In this case the specified amount is transferred to the payee and the remaining amount is available again to the payer. - -Holds can be specified to be perpetual. In this case, the hold cannot be released upon expiration, and thus can only be executed by the notary or released by the notary or payee. - -## Motivation - -A hold has to be used in different scenarios where a immediate transfer between accounts is not possible or has to be guaranteed beforehand: - -1. A regulated token may not allow to do a token transfer between accounts without verifying first, that it follows all the regulations. In this case a clearable transfer has to be used. During the clearing process a hold is created to ensure, that the transfer is successful after all checks have passed. If the transfer violates any of the regulations, it is cleared and not further processed. - -1. In certain business situations a payment has to be guaranteed before its services can be used. For example: When checking in a hotel, the hotel will put a hold on the guest's account to ensure that enough balance is available to pay for the room before handing over the keys. - -1. In other occasions a payment has to be guaranteed without knowing the exact amount beforehand. To stay with the hotel example: The hotel can put a hold on the guest's account as a guarantee for any possible extras, like room service. When the guest checks out the hold is partially executed and the remaining amount is available again on the guest's account. - -The ERC-20 `approve` function provides some of the necessary functionality for the use cases above. The main difference to holds, is that `approve` does not ensure a payment, as the approved money is not blocked and can be transferred at any moment. - -## Specification - -```solidity -interface IHoldable /* is ERC-20 */ { - enum HoldStatusCode { - Nonexistent, - Ordered, - Executed, - ReleasedByNotary, - ReleasedByPayee, - ReleasedOnExpiration - } - - function hold(string calldata operationId, address to, address notary, uint256 value, uint256 timeToExpiration) external returns (bool); - function holdFrom(string calldata operationId, address from, address to, address notary, uint256 value, uint256 timeToExpiration) external returns (bool); - function releaseHold(string calldata operationId) external returns (bool); - function executeHold(string calldata operationId, uint256 value) external returns (bool); - function renewHold(string calldata operationId, uint256 timeToExpiration) external returns (bool); - function retrieveHoldData(string calldata operationId) external view returns (address from, address to, address notary, uint256 value, uint256 expiration, HoldStatusCode status); - - function balanceOnHold(address account) external view returns (uint256); - function netBalanceOf(address account) external view returns (uint256); - function totalSupplyOnHold() external view returns (uint256); - - function authorizeHoldOperator(address operator) external returns (bool); - function revokeHoldOperator(address operator) external returns (bool); - function isHoldOperatorFor(address operator, address from) external view returns (bool); - - event HoldCreated(address indexed holdIssuer, string operationId, address from, address to, address indexed notary, uint256 value, uint256 expiration); - event HoldExecuted(address indexed holdIssuer, string operationId, address indexed notary, uint256 heldValue, uint256 transferredValue); - event HoldReleased(address indexed holdIssuer, string operationId, HoldStatusCode status); - event HoldRenewed(address indexed holdIssuer, string operationId, uint256 oldExpiration, uint256 newExpiration); - event AuthorizedHoldOperator(address indexed operator, address indexed account); - event RevokedHoldOperator(address indexed operator, address indexed account); -} -``` - -### Functions - -#### hold - -Creates a hold on behalf of the msg.sender in favor of the payee. It specifies a notary who is responsible to either execute or release the hold. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the hold | -| to | The address of the payee, to whom the tokens are to be transferred if executed | -| notary | The address of the notary who is going to determine whether the hold is to be executed or released | -| value | The amount to be transferred. Must be less or equal than the balance of the payer. | -| timeToExpiration | The duration until the hold is expired. If it is '0' the hold must be perpetual. | - -#### holdFrom - -Creates a hold on behalf of the payer in favor of the payee. The `from` account has to approve beforehand, that another account can issue holds on its behalf by calling `approveToHold`. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the hold | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be transferred if executed | -| notary | The address of the notary who is going to determine whether the hold is to be executed or released | -| value | The amount to be transferred. Must be less or equal than the balance of the payer. | -| timeToExpiration | The duration until the hold is expired. If it is '0' the hold must be perpetual. | - -#### releaseHold - -Releases a hold. Release means that the transfer is not executed and the held amount is available again for the payer. Until a hold has expired it can only be released by the notary or the payee. After it has expired it can be released by anyone. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the hold | - -#### executeHold - -Executes a hold. Execute means that the specified value is transferred from the payer to the payee. If the specified value is less than the hold value the remaining amount is available again to the payer. The implementation must verify that only the notary is able to successfully call the function. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the hold | -| value | The amount to be transferred. This amount has to be less or equal than the hold value | - -#### renewHold - -Renews a hold. The new expiration time must be the block timestamp plus the given `timeToExpiration`, independently if the hold was perpetual or not before that. Furthermore a hold must be made perpetual if `timeToExpiration` is '0'. The implementation must verify that only the payer or operator are able to successfully call the function. Furthermore the only a hold, which has not yet expired can be successfully renewed. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the hold | -| timeToExpiration | The new duration until the hold is expired. | - -#### retrieveHoldData - -Retrieves all the information available for a particular hold. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the hold | - -#### balanceOnHold - -Retrieves how much of the balance is currently held and therefore not available for transfer. - -| Parameter | Description | -| ---------|-------------| -| account | The address which held balance should be returned | - -#### netBalanceOf - -Retrieves the net balance, which is the sum of `balanceOf` and `balanceOnHold`. - -| Parameter | Description | -| ---------|-------------| -| account | The address which net balance should be returned | - -#### totalSupplyOnHold - -Retrieves the total sum of how many tokens are on hold. - -| Parameter | Description | -| ---------|-------------| -| - | - | - -#### authorizeHoldOperator - -Approves an operator to issue holds on behalf of msg.sender. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be approved as operator of holds | - -#### revokeHoldOperator - -Revokes the approval to issue holds on behalf of msg.sender. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be revoked as operator of holds | - -#### isHoldOperatorFor - -Retrieves if an operator is approved to create holds on behalf of `from`. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be a operator of holds | -| from | The address on which the holds would be created | - -#### balanceOf - -The standard implementation of ERC-20 has to be changed in order to deduct the held balance from the ERC-20 balance. - -#### transfer - -The standard implementation of ERC-20 has to be changed in order to deduct the held balance from the ERC-20 balance. Any amount that is held must not be transferred. - -#### transferFrom - -The standard implementation of ERC-20 has to be changed in order to deduct the held balance from the ERC-20 balance. Any amount that is held must not be transferred. - -### Events - -#### HoldCreated - -Emitted when a hold has been created. - -| Parameter | Description | -| ---------|-------------| -| holdIssuer | The address of the hold issuer of the hold | -| operationId | The unique ID to identify the hold | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be paid if executed | -| notary | The address of the notary who is going to determine whether the hold is to be executed or released | -| value | The amount to be transferred. Must be less or equal than the balance of the payer. | -| expiration | The unix timestamp when the hold is expired | - -#### HoldExecuted - -Emitted when a hold has been executed. - -| Parameter | Description | -| ---------|-------------| -| holdIssuer | The address of the hold issuer of the hold | -| operationId | The unique ID to identify the hold | -| notary | The address of the notary who executed the hold | -| heldValue | The amount which was put on hold during creation | -| transferredValue | The amount which was used for the transfer | - -#### HoldReleased - -Emitted when a hold has been released. - -| Parameter | Description | -| ---------|-------------| -| holdIssuer | The address of the hold issuer of the hold | -| operationId | The unique ID to identify the hold | -| status | Can be one of the following values: `ReleasedByNotary`, `ReleasedByPayee`, `ReleasedOnExpiration` | - -#### HoldRenewed - -Emitted when a hold has been renewed. - -| Parameter | Description | -| ---------|-------------| -| holdIssuer | The address of the hold issuer of the hold | -| operationId | The unique ID to identify the hold | -| oldExpiration | The expiration time before the renewal | -| newExpiration | The expiration time after the renewal | - -#### AuthorizedHoldOperator - -Emitted when an operator has been approved to create holds on behalf of another account. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be a operator of holds | -| account | Address on which behalf holds will potentially be created | - -#### RevokedHoldOperator - -Emitted when an operator has been revoked from creating holds on behalf of another account. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be a operator of holds | -| account | Address on which behalf holds could potentially be created | - -## Rationale - -This standards provides a functionality, to guarantee future payments, which is needed for many business cases where transfers have to be guaranteed. - -It goes a step further than the ERC-20 `approve` function by ensuring that the held balance will be available when the transfer is done. Something that can not be done with `approve`, as the approved amount is only a maximum spending amount, but never guaranteed to be available. - -While not requiring it, the naming of the functions `authorizeHoldOperator`, `revokeHoldOperator` and `isHoldOperatorFor` follows the naming convention of [ERC-777](./eip-777.md). - -The `operationId` is a string and not something more gas efficient to allow easy traceability of the hold and allow human readable ids. It is up to the implementer if the string should be stored on-chain or only its hash, as it is enough to identify a hold. - -The `operationId` is a competitive resource. It is recommended, but nor required, that the hold issuers used a unique prefix to avoid collisions. - -## Backwards Compatibility -This EIP is fully backwards compatible as its implementation extends the functionality of ERC-20. - -## Implementation -The GitHub repository [IoBuilders/holdable-token](https://github.com/IoBuilders/holdable-token) contains the reference implementation. - -## Contributors -This proposal has been collaboratively implemented by [adhara.io](https://adhara.io/) and [io.builders](https://io.builders/). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-1996.md diff --git a/EIPS/eip-20.md b/EIPS/eip-20.md index 98600bad836171..a1ca6498abc8d7 100644 --- a/EIPS/eip-20.md +++ b/EIPS/eip-20.md @@ -1,193 +1 @@ ---- -eip: 20 -title: Token Standard -author: Fabian Vogelsteller , Vitalik Buterin -type: Standards Track -category: ERC -status: Final -created: 2015-11-19 ---- - -## Simple Summary - -A standard interface for tokens. - - -## Abstract - -The following standard allows for the implementation of a standard API for tokens within smart contracts. -This standard provides basic functionality to transfer tokens, as well as allow tokens to be approved so they can be spent by another on-chain third party. - - -## Motivation - -A standard interface allows any tokens on Ethereum to be re-used by other applications: from wallets to decentralized exchanges. - - -## Specification - -## Token -### Methods - -**NOTES**: - - The following specifications use syntax from Solidity `0.4.17` (or above) - - Callers MUST handle `false` from `returns (bool success)`. Callers MUST NOT assume that `false` is never returned! - - -#### name - -Returns the name of the token - e.g. `"MyToken"`. - -OPTIONAL - This method can be used to improve usability, -but interfaces and other contracts MUST NOT expect these values to be present. - - -``` js -function name() public view returns (string) -``` - - -#### symbol - -Returns the symbol of the token. E.g. "HIX". - -OPTIONAL - This method can be used to improve usability, -but interfaces and other contracts MUST NOT expect these values to be present. - -``` js -function symbol() public view returns (string) -``` - - - -#### decimals - -Returns the number of decimals the token uses - e.g. `8`, means to divide the token amount by `100000000` to get its user representation. - -OPTIONAL - This method can be used to improve usability, -but interfaces and other contracts MUST NOT expect these values to be present. - -``` js -function decimals() public view returns (uint8) -``` - - -#### totalSupply - -Returns the total token supply. - -``` js -function totalSupply() public view returns (uint256) -``` - - - -#### balanceOf - -Returns the account balance of another account with address `_owner`. - -``` js -function balanceOf(address _owner) public view returns (uint256 balance) -``` - - - -#### transfer - -Transfers `_value` amount of tokens to address `_to`, and MUST fire the `Transfer` event. -The function SHOULD `throw` if the message caller's account balance does not have enough tokens to spend. - -*Note* Transfers of 0 values MUST be treated as normal transfers and fire the `Transfer` event. - -``` js -function transfer(address _to, uint256 _value) public returns (bool success) -``` - - - -#### transferFrom - -Transfers `_value` amount of tokens from address `_from` to address `_to`, and MUST fire the `Transfer` event. - -The `transferFrom` method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf. -This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies. -The function SHOULD `throw` unless the `_from` account has deliberately authorized the sender of the message via some mechanism. - -*Note* Transfers of 0 values MUST be treated as normal transfers and fire the `Transfer` event. - -``` js -function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) -``` - - - -#### approve - -Allows `_spender` to withdraw from your account multiple times, up to the `_value` amount. If this function is called again it overwrites the current allowance with `_value`. - -**NOTE**: To prevent attack vectors like the one [described here](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/) and discussed [here](https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729), -clients SHOULD make sure to create user interfaces in such a way that they set the allowance first to `0` before setting it to another value for the same spender. -THOUGH The contract itself shouldn't enforce it, to allow backwards compatibility with contracts deployed before - -``` js -function approve(address _spender, uint256 _value) public returns (bool success) -``` - - -#### allowance - -Returns the amount which `_spender` is still allowed to withdraw from `_owner`. - -``` js -function allowance(address _owner, address _spender) public view returns (uint256 remaining) -``` - - - -### Events - - -#### Transfer - -MUST trigger when tokens are transferred, including zero value transfers. - -A token contract which creates new tokens SHOULD trigger a Transfer event with the `_from` address set to `0x0` when tokens are created. - -``` js -event Transfer(address indexed _from, address indexed _to, uint256 _value) -``` - - - -#### Approval - -MUST trigger on any successful call to `approve(address _spender, uint256 _value)`. - -``` js -event Approval(address indexed _owner, address indexed _spender, uint256 _value) -``` - - - -## Implementation - -There are already plenty of ERC20-compliant tokens deployed on the Ethereum network. -Different implementations have been written by various teams that have different trade-offs: from gas saving to improved security. - -#### Example implementations are available at -- [OpenZeppelin implementation](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/9b3710465583284b8c4c5d2245749246bb2e0094/contracts/token/ERC20/ERC20.sol) -- [ConsenSys implementation](https://github.com/ConsenSys/Tokens/blob/fdf687c69d998266a95f15216b1955a4965a0a6d/contracts/eip20/EIP20.sol) - - -## History - -Historical links related to this standard: - -- Original proposal from Vitalik Buterin: https://github.com/ethereum/wiki/wiki/Standardized_Contract_APIs/499c882f3ec123537fc2fccd57eaa29e6032fe4a -- Reddit discussion: https://www.reddit.com/r/ethereum/comments/3n8fkn/lets_talk_about_the_coin_standard/ -- Original Issue #20: https://github.com/ethereum/EIPs/issues/20 - - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-20.md diff --git a/EIPS/eip-2009.md b/EIPS/eip-2009.md index c628e6b2343e06..311b411ad57880 100644 --- a/EIPS/eip-2009.md +++ b/EIPS/eip-2009.md @@ -1,300 +1 @@ ---- -eip: 2009 -title: Compliance Service -author: Daniel Lehrner -discussions-to: https://github.com/ethereum/EIPs/issues/2022 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-05-09 -requires: 1066 ---- - -## Simple Summary - -This EIP proposes a service for decentralized compliance checks for regulated tokens. - -## Actors - -#### Operator -An account which has been approved by a token to update the tokens accumulated. - -#### Token -An account, normally a smart contract, which uses the `Compliance Service` to check if the an action can be executed or not. - -#### Token holder -An account which is in possession of tokens and on for which the checks are made. - -## Abstract - -A regulated token needs to comply with several legal requirements, especially [KYC][KYC-Wikipedia] and [AML][AML-Wikipedia]. If the necessary checks have to be made off-chain the token transfer becomes centralized. Further the transfer in this case takes longer to complete as it can not be done in one transaction, but requires a second confirmation step. The goal of this proposal is to make this second step unnecessary by providing a service for compliance checks. - -## Motivation - -Currently there is no proposal on how to accomplish decentralized compliance checks. [ERC-1462][ERC-1462] proposes a basic set of functions to check if `transfer`, `mint` and `burn` are allowed for a user, but not how those checks should be implemented. This EIP proposes a way to implement them fully on-chain while being generic enough to leave the actual implementation of the checks up to the implementers, as these may vary a lot between different tokens. - -The proposed `Compliance Service` supports more than one token. Therefore it could be used by law-makers to maintain the compliance rules of regulated tokens in one smart contract. This smart contract could be used by all of the tokens that fall under this jurisdiction and ensure compliance with the current laws. - -By having a standard for compliance checks third-party developers can use them to verify if token movements for a specific account are allowed and act accordingly. - -## Specification - -```solidity -interface CompliantService { - function checkTransferAllowed(bytes32 tokenId, address from, address to, uint256 value) external view returns (byte); - function checkTransferFromAllowed(bytes32 tokenId, address sender, address from, address to, uint256 value) external view returns (byte); - function checkMintAllowed(bytes32 tokenId, address to, uint256 value) external view returns (byte); - function checkBurnAllowed(bytes32 tokenId, address from, uint256 value) external view returns (byte); - - function updateTransferAccumulated(bytes32 tokenId, address from, address to, uint256 value) external; - function updateMintAccumulated(bytes32 tokenId, address to, uint256 value) external; - function updateBurnAccumulated(bytes32 tokenId, address from, uint256 value) external; - - function addToken(bytes32 tokenId, address token) external; - function replaceToken(bytes32 tokenId, address token) external; - function removeToken(bytes32 tokenId) external; - function isToken(address token) external view returns (bool); - function getTokenId(address token) external view returns (bytes32); - - function authorizeAccumulatedOperator(address operator) external returns (bool); - function revokeAccumulatedOperator(address operator) external returns (bool); - function isAccumulatedOperatorFor(address operator, bytes32 tokenId) external view returns (bool); - - event TokenAdded(bytes32 indexed tokenId, address indexed token); - event TokenReplaced(bytes32 indexed tokenId, address indexed previousAddress, address indexed newAddress); - event TokenRemoved(bytes32 indexed tokenId); - event AuthorizedAccumulatedOperator(address indexed operator, bytes32 indexed tokenId); - event RevokedAccumulatedOperator(address indexed operator, bytes32 indexed tokenId); -} -``` - -### Mandatory checks - -The checks must be verified in their corresponding actions. The action must only be successful if the check return an `Allowed` status code. In any other case the functions must revert. - -### Status codes - -If an action is allowed `0x11` (Allowed) or an issuer-specific code with equivalent but more precise meaning must be returned. If the action is not allowed the status must be `0x10` (Disallowed) or an issuer-specific code with equivalent but more precise meaning. - -### Functions - -#### checkTransferAllowed - -Checks if the `transfer` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be transferred if executed | -| value | The amount to be transferred | - -#### checkTransferFromAllowed - -Checks if the `transferFrom` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| sender | The address of the sender, who initiated the transaction | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be transferred if executed | -| value | The amount to be transferred | - -#### checkMintAllowed - -Checks if the `mint` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| to | The address of the payee, to whom the tokens are to be given if executed | -| value | The amount to be minted | - -#### checkBurnAllowed - -Checks if the `burn` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| value | The amount to be burned | - -#### updateTransferAccumulated - -Must be called in the same transaction as `transfer` or `transferFrom`. It must revert if the update violates any of the compliance rules. It is up to the implementer which specific logic is executed in the function. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be transferred if executed | -| value | The amount to be transferred | - -#### updateMintAccumulated - -Must be called in the same transaction as `mint`. It must revert if the update violates any of the compliance rules. It is up to the implementer which specific logic is executed in the function. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| to | The address of the payee, to whom the tokens are to be given if executed | -| value | The amount to be minted | - -#### updateBurnAccumulated - -Must be called in the same transaction as `burn`. It must revert if the update violates any of the compliance rules. It is up to the implementer which specific logic is executed in the function. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| value | The amount to be minted | - -#### addToken - -Adds a token to the service, which allows the token to call the functions to update the accumulated. If an existing token id is used the function must revert. It is up to the implementer if adding a token should be restricted or not. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| token | The address from which the update functions will be called | - -#### replaceToken - -Replaces the address of a added token with another one. It is up to the implementer if replacing a token should be restricted or not, but a token should be able to replace its own address. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| token | The address from which the update functions will be called | - -#### removeToken - -Removes a token from the service, which disallows the token to call the functions to update the accumulated. It is up to the implementer if removing a token should be restricted or not. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | - -#### isToken - -Returns `true` if the address has been added to the service, `false` if not. - -| Parameter | Description | -| ---------|-------------| -| token | The address which should be checked | - -#### getTokenId - -Returns the token id of a token. If the token has not been added to the service, '0' must be returned. - -| Parameter | Description | -| ---------|-------------| -| token | The address which token id should be returned | - -#### authorizeAccumulatedOperator - -Approves an operator to update accumulated on behalf of the token id of msg.sender. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be approved as operator of accumulated updates | - -#### revokeAccumulatedOperator - -Revokes the approval to update accumulated on behalf the token id the token id ofof msg.sender. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be revoked as operator of accumulated updates | - -#### isAccumulatedOperatorFor - -Retrieves if an operator is approved to create holds on behalf of `tokenId`. - -| Parameter | Description | -| ---------|-------------| -| operator | The address which is operator of updating the accumulated | -| tokenId | The unique ID which identifies a token | - -### Events - -#### TokenAdded - -Must be emitted after a token has been added. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| token | The address from which the update functions will be called | - -#### TokenReplaced - -Must be emitted after the address of a token has been replaced. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | -| previousAddress | The previous address which was used before | -| newAddress | The address which will be used from now on | - -#### TokenRemoved - -Must be emitted after the a token has been removed. - -| Parameter | Description | -| ---------|-------------| -| tokenId | The unique ID which identifies a token | - -#### AuthorizedAccumulatedOperator - -Emitted when an operator has been approved to update the accumulated on behalf of a token. - -| Parameter | Description | -| ---------|-------------| -| operator | The address which is operator of updating the accumulated | -| tokenId | Token id on which behalf updates of the accumulated will potentially be made | - -#### RevokedHoldOperator - -Emitted when an operator has been revoked from updating the accumulated on behalf of a token. - -| Parameter | Description | -| ---------|-------------| -| operator | The address which was operator of updating the accumulated | -| tokenId | Token id on which behalf updates of the accumulated could be made | - -## Rationale - -The usage of a token id instead of the address has been chosen to give tokens the possibility to update their smart contracts and keeping all their associated accumulated. If the address would be used, a migration process would needed to be done after a smart contract update. - -No event is emitted after updating the accumulated as those are always associated with a `transfer`, `mint` or `burn` of a token which already emits an event of itself. - -While not requiring it, the naming of the functions `checkTransferAllowed`, `checkTransferFromAllowed`, `checkMintAllowed` and `checkBurnAllowed` was adopted from [ERC-1462][ERC-1462]. - -While not requiring it, the naming of the functions `authorizeAccumulatedOperator`, `revokeAccumulatedOperator` and `isAccumulatedOperatorFor` follows the naming convention of [ERC-777][ERC-777]. - -Localization is not part of this EIP, but [ERC-1066][ERC-1066] and [ERC-1444][ERC-1444] can be used together to achieve it. - -## Backwards Compatibility - -As the EIP is not using any existing EIP there are no backwards compatibilities to take into consideration. - -## Implementation - -The GitHub repository [IoBuilders/compliance-service](https://github.com/IoBuilders/compliance-service) contains the work in progress implementation. - -## Contributors -This proposal has been collaboratively implemented by [adhara.io](https://adhara.io/) and [io.builders](https://io.builders/). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[KYC-Wikipedia]: https://en.wikipedia.org/wiki/Know_your_customer -[AML-Wikipedia]: https://en.wikipedia.org/wiki/Money_laundering#Anti-money_laundering -[ERC-777]: ./eip-777.md -[ERC-1066]: ./eip-1066.md -[ERC-1444]: ./eip-1444.md -[ERC-1462]: ./eip-1462.md +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2009.md diff --git a/EIPS/eip-2018.md b/EIPS/eip-2018.md index 9959a341a2a9b6..33bf011a7e02ae 100644 --- a/EIPS/eip-2018.md +++ b/EIPS/eip-2018.md @@ -1,261 +1 @@ ---- -eip: 2018 -title: Clearable Token -author: Julio Faura , Fernando Paris , Daniel Lehrner -discussions-to: https://github.com/ethereum/EIPs/issues/2104 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-04-30 -requires: 1996 ---- - -## Simple Summary - -> "In banking and finance, clearing denotes all activities from the time a commitment is made for a transaction until it is settled." [[1]][Clearing-Wikipedia] - -## Actors - -#### Clearing Agent - -An account which processes, executes or rejects a clearable transfer. - -#### Operator -An account which has been approved by an account to order clearable transfers on its behalf. - -#### Orderer -The account which orders a clearable transfer. This can be the account owner itself, or any account, which has been approved as an operator for the account. - -## Abstract - -The clearing process turns the promise of a transfer into the actual movement of money from one account to another. A clearing agent decides if the transfer can be executed or not. The amount which should be transferred is not deducted from the balance of the payer, but neither is it available for another transfer and therefore ensures, that the execution of the transfer will be successful when it is executed. - -## Motivation - -A regulated token needs to comply with all the legal requirements, especially [KYC][KYC-Wikipedia] and [AML][AML-Wikipedia]. Some of these checks may not be able to be done on-chain and therefore a transfer may not be completed in one step. Currently there is no EIP to make such off-chain checks possible. This proposal allows a user to order a transfer, which can be checked by a clearing agent off-chain. Depending on the result of it, the clearing agent will either execute or cancel the transfer. To provide more information why a transfer is cancelled, the clearing agent can add a reason why it is not executed. - -## Specification - -```solidity -interface ClearableToken /* is ERC-1996 */ { - enum ClearableTransferStatusCode { Nonexistent, Ordered, InProcess, Executed, Rejected, Cancelled } - - function orderTransfer(string calldata operationId, address to, uint256 value) external returns (bool); - function orderTransferFrom(string calldata operationId, address from, address to, uint256 value) external returns (bool); - function cancelTransfer(string calldata operationId) external returns (bool); - function processClearableTransfer(string calldata operationId) external returns (bool); - function executeClearableTransfer(string calldata operationId) external returns (bool); - function rejectClearableTransfer(string calldata operationId, string calldata reason) external returns (bool); - function retrieveClearableTransferData(string calldata operationId) external view returns (address from, address to, uint256 value, ClearableTransferStatusCode status); - - function authorizeClearableTransferOperator(address operator) external returns (bool); - function revokeClearableTransferOperator(address operator) external returns (bool); - function isClearableTransferOperatorFor(address operator, address from) external view returns (bool); - - event ClearableTransferOrdered(address indexed orderer, string operationId, address indexed from, address indexed to, uint256 value); - event ClearableTransferInProcess(address indexed orderer, string operationId); - event ClearableTransferExecuted(address indexed orderer, string operationId); - event ClearableTransferRejected(address indexed orderer, string operationId, string reason); - event ClearableTransferCancelled(address indexed orderer, string operationId); - event AuthorizedClearableTransferOperator(address indexed operator, address indexed account); - event RevokedClearableTransferOperator(address indexed operator, address indexed account); -} -``` - -### Functions - -#### orderTransfer - -Orders a clearable transfer on behalf of the msg.sender in favor of `to`. A clearing agent is responsible to either execute or reject the transfer. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the clearable transfer | -| to | The address of the payee, to whom the tokens are to be paid if executed | -| value | The amount to be transferred. Must be less or equal than the balance of the payer. | - -#### orderTransferFrom - -Orders a clearable transfer on behalf of the payer in favor of the `to`. A clearing agent is responsible to either execute or reject the transfer. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the clearable transfer | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be paid if executed | -| value | The amount to be transferred. Must be less or equal than the balance of the payer. | - -#### cancelTransfer - -Cancels the order of a clearable transfer. Only the orderer can cancel their own orders. It must not be successful as soon as the transfer is in status `InProcess`. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the clearable transfer | - -#### processClearableTransfer - -Sets a clearable transfer to status `InProcess`. Only a clearing agent can successfully execute this action. This status is optional, but without it the orderer can cancel the transfer at any time. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the clearable transfer | - -#### executeClearableTransfer - -Executes a clearable transfer, which means that the tokens are transferred from the payer to the payee. Only a clearing agent can successfully execute this action. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the clearable transfer | - -#### rejectClearableTransfer - -Rejects a clearable transfer, which means that the amount that is held is available again to the payer and no transfer is done. Only a clearing agent can successfully execute this action. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the clearable transfer | -| reason | A reason given by the clearing agent why the transfer has been rejected | - -#### retrieveClearableTransferData - -Retrieves all the information available for a particular clearable transfer. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the clearable transfer | - -#### authorizeClearableTransferOperator - -Approves an operator to order transfers on behalf of msg.sender. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be approved as operator of clearable transfers | - -#### revokeClearableTransferOperator - -Revokes the approval to order transfers on behalf of msg.sender. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be revoked as operator of clearable transfers | - -#### isClearableTransferOperatorFor - -Returns if an operator is approved to order transfers on behalf of `from`. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be an operator of clearable transfers | -| from | The address on which the holds would be created | - -#### transfer - -It is up to the implementer of the EIP if the `transfer` function of ERC-20 should always revert or is allowed under certain circumstances. - -#### transferFrom - -It is up to the implementer of the EIP if the `transferFrom` function of ERC-20 should always revert or is allowed under certain circumstances. - - -### Events - -#### ClearableTransferOrdered - -Must be emitted when a clearable transfer is ordered. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer of the transfer | -| operationId | The unique ID to identify the clearable transfer | -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be paid if executed | -| value | The amount to be transferred if executed | - -#### ClearableTransferInProcess - -Must be emitted when a clearable transfer is put in status `ÌnProcess`. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer of the transfer | -| operationId | The unique ID to identify the clearable transfer | - -#### ClearableTransferExecuted - -Must be emitted when a clearable transfer is executed. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer of the transfer | -| operationId | The unique ID to identify the clearable transfer | - -#### ClearableTransferRejected - -Must be emitted when a clearable transfer is rejected. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer of the transfer | -| operationId | The unique ID to identify the clearable transfer | -| reason | A reason given by the clearing agent why the transfer has been rejected | - -#### ClearableTransferCancelled - -Must be emitted when a clearable transfer is cancelled by its orderer. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer of the transfer | -| operationId | The unique ID to identify the clearable transfer | - -#### AuthorizedClearableTransferOperator - -Emitted when an operator has been approved to order transfers on behalf of another account. - -| Parameter | Description | -| ---------|-------------| -| operator | The address which has been approved as operator of clearable transfers | -| account | Address on which behalf transfers will potentially be ordered | - -#### RevokedClearableTransferOperator - -Emitted when an operator has been revoked from ordering transfers on behalf of another account. - -| Parameter | Description | -| ---------|-------------| -| operator | The address which has been revoked as operator of clearable transfers | -| account | Address on which behalf transfers could potentially be ordered | - -## Rationale - -This EIP uses [EIP-1996][EIP-1996] to hold the money after a transfer is ordered. A clearing agent, whose implementation is not part of this proposal, acts as a predefined notary to decide if the transfer complies with the rules of the token or not. - -The `operationId` is a string and not something more gas efficient to allow easy traceability of the hold and allow human readable ids. It is up to the implementer if the string should be stored on-chain or only its hash, as it is enough to identify a hold. - -The `operationId` is a competitive resource. It is recommended, but not required, that the hold issuers used a unique prefix to avoid collisions. - -While not requiring it, the naming of the functions `authorizeClearableTransferOperator`, `revokeClearableTransferOperator` and `isClearableTransferOperatorFor` follows the naming convention of [ERC-777](./eip-777.md). - -## Backwards Compatibility - -This EIP is fully backwards compatible as its implementation extends the functionality of [EIP-1996][EIP-1996]. - -## Implementation - -The GitHub repository [IoBuilders/clearable-token](https://github.com/IoBuilders/clearable-token) contains the reference implementation. - -## Contributors -This proposal has been collaboratively implemented by [adhara.io](https://adhara.io/) and [io.builders](https://io.builders/). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[1] https://en.wikipedia.org/wiki/Clearing_(finance) - -[Clearing-Wikipedia]: https://en.wikipedia.org/wiki/Clearing_(finance) -[KYC-Wikipedia]: https://en.wikipedia.org/wiki/Know_your_customer -[AML-Wikipedia]: https://en.wikipedia.org/wiki/Money_laundering#Anti-money_laundering -[EIP-1996]: ./eip-1996.md +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2018.md diff --git a/EIPS/eip-2019.md b/EIPS/eip-2019.md index a59cc4fbc5fe25..19f312ed4c9469 100644 --- a/EIPS/eip-2019.md +++ b/EIPS/eip-2019.md @@ -1,255 +1 @@ ---- -eip: 2019 -title: Fundable Token -author: Fernando Paris , Julio Faura , Daniel Lehrner -discussions-to: https://github.com/ethereum/EIPs/issues/2105 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-05-10 -requires: 20 ---- - -## Simple Summary -An extension to the [ERC-20] standard token that allows Token wallet owners to request a wallet to be funded, by calling the smart contract and attaching a fund instruction string. - -## Actors - -#### Token Wallet Owners -The person or company who owns the wallet, and will order a token fund request into the wallet. - -#### Token contract owner / agent -The entity, company responsible/owner of the token contract, and token issuing/minting. This actor is in charge of trying to fulfill all fund request(s), reading the fund instruction(s), and correlate the private payment details. - -#### Orderer -An actor who is enabled to initiate funding orders on behalf of a token wallet owner. - -## Abstract -Token wallet owners (or approved addresses) can order tokenization requests through blockchain. This is done by calling the ```orderFund``` or ```orderFundFrom``` methods, which initiate the workflow for the token contract operator to either honor or reject the fund request. In this case, fund instructions are provided when submitting the request, which are used by the operator to determine the source of the funds to be debited in order to do fund the token wallet (through minting). - -In general, it is not advisable to place explicit routing instructions for debiting funds on a verbatim basis on the blockchain, and it is advised to use a private communication alternatives, such as private channels, encrypted storage or similar, to do so (external to the blockchain ledger). Another (less desirable) possibility is to place these instructions on the instructions field in encrypted form. - -## Motivation -Nowadays most of the token issuing/funding request, based on any fiat based payment method need a previous centralized transaction, to be able to get the desired tokens issued on requester's wallet. -In the aim of trying to bring all the needed steps into decentralization, exposing all the needed steps of token lifecycle and payment transactions, a funding request can allow wallet owner to initiate the funding request via blockchain. -Key benefits: - -* Funding and payment traceability is enhanced bringing the initiation into the ledger. All payment stat -s can be stored on chain. -* Almost all money/token lifecycle is covered via a decentralized approach, complemented with private communications which is common use in the ecosystem. - -## Specification - -```solidity -interface IFundable /* is ERC-20 */ { - enum FundStatusCode { - Nonexistent, - Ordered, - InProcess, - Executed, - Rejected, - Cancelled - } - function authorizeFundOperator(address orderer) external returns (bool); - function revokeFundOperator(address orderer) external returns (bool) ; - function orderFund(string calldata operationId, uint256 value, string calldata instructions) external returns (bool); - function orderFundFrom(string calldata operationId, address walletToFund, uint256 value, string calldata instructions) external returns (bool); - function cancelFund(string calldata operationId) external returns (bool); - function processFund(string calldata operationId) external returns (bool); - function executeFund(string calldata operationId) external returns (bool); - function rejectFund(string calldata operationId, string calldata reason) external returns (bool); - - function isFundOperatorFor(address walletToFund, address orderer) external view returns (bool); - function retrieveFundData(address orderer, string calldata operationId) external view returns (address walletToFund, uint256 value, string memory instructions, FundStatusCode status); - - event FundOrdered(address indexed orderer, string indexed operationId, address indexed , uint256 value, string instructions); - event FundInProcess(address indexed orderer, string indexed operationId); - event FundExecuted(address indexed orderer, string indexed operationId); - event FundRejected(address indexed orderer, string indexed operationId, string reason); - event FundCancelled(address indexed orderer, string indexed operationId); - event FundOperatorAuthorized(address indexed walletToFund, address indexed orderer); - event FundOperatorRevoked(address indexed walletToFund, address indexed orderer); -} -``` - -### Functions - -#### authorizeFundOperator - -Wallet owner, authorizes a given address to be fund orderer. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer. - -#### revokeFundOperator - -Wallet owner, revokes a given address to be fund orderer. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer. - -#### orderFund - -Creates a fund request, that will be processed by the token operator. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request | -| value | The amount to be funded. | -| instruction | A string including the payment instruction. | - -#### orderFundFrom - -Creates a fund request, on behalf of a wallet owner, that will be processed by the token operator. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId |The unique ID to identify the request | -| walletToFund | The wallet to be funded on behalf. -| value | The amount to be funded. | -| instruction | A string including the payment instruction. | - -#### cancelFund - -Cancels a funding request. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request that is going to be cancelled. This can only be done by token holder, or the fund initiator. | - -#### processFund - -Marks a funding request as on process. After the status is on process, order cannot be cancelled. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request is in process. - -#### executeFund - -Issues the amount of tokens and marks a funding request as executed. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request that has been executed. - -#### rejectFund - -Rejects a given operation with a reason. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request that has been executed. -| reason | The specific reason that explains why the fund request was rejected. EIP 1066 codes can be used | - -#### isFundOperatorFor - -Checks that given player is allowed to order fund requests, for a given wallet. - -| Parameter | Description | -| ---------|-------------| -| walletToFund | The wallet to be funded, and checked for approval permission. -| orderer | The address of the orderer, to be checked for approval permission. - -#### retrieveFundData - -Retrieves all the fund request data. Only operator, tokenHolder, and orderer can get the given operation data. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the fund order. - -### Events - -#### FundOrdered - -Emitted when an token wallet owner orders a funding request. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request | -| walletToFund | The wallet that the player is allowed to start funding requests | -| value | The amount to be funded. | -| instruction | A string including the payment instruction. | - -#### FundInProcess - -Emitted when an operator starts a funding request after validating the instruction, and the operation is marked as in process. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the fund request orderer. | -| operationId | The unique ID to identify the fund. | - -#### FundExecuted - -Emitted when an operator has executed a funding request. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the fund request orderer. | -| operationId | The unique ID to identify the fund. | - -#### FundRejected - -Emitted when an operator has rejected a funding request. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the fund request orderer. | -| operationId | The unique ID to identify the fund. | -| reason | The specific reason that explains why the fund request was rejected. EIP 1066 codes can be used | - -#### FundCancelled - -Emitted when a token holder, orderer, has cancelled a funding request. This can only be done if the operator hasn't put the funding order in process. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the fund request orderer. | -| operationId | The unique ID to identify the fund. | - -#### FundOperatorAuthorized - -Emitted when a given player, operator, company or a given persona, has been approved to start fund request for a given token holder. - -| Parameter | Description | -| ---------|-------------| -| walletToFund | The wallet that the player is allowed to start funding requests | -| orderer | The address that allows the the player to start requests. | - -#### FundOperatorRevoked - -Emitted when a given player has been revoked initiate funding requests. - -| Parameter | Description | -| ---------|-------------| -| walletToFund | The wallet that the player is allowed to start funding requests | -| orderer | The address that allows the the player to start requests. | - -## Rationale -This standards provides a functionality to allow token holders to start funding requests in a decentralized way. - -It's important to highlight that the token operator, need to process all funding request, updating the fund status based on the linked payment that will be done. - -Funding instruction format is open. ISO payment standard like is a good start point, - -The `operationId` is a string and not something more gas efficient to allow easy traceability of the hold and allow human readable ids. It is up to the implementer if the string should be stored on-chain or only its hash, as it is enough to identify a hold. - -The `operationId` is a competitive resource. It is recommended, but not required, that the hold issuers used a unique prefix to avoid collisions. - -## Backwards Compatibility -This EIP is fully backwards compatible as its implementation extends the functionality of [ERC-20]. - -## Implementation -The GitHub repository [IoBuilders/fundable-token](https://github.com/IoBuilders/fundable-token) contains the work in progress implementation. - -## Contributors -This proposal has been collaboratively implemented by [adhara.io](https://adhara.io/) and [io.builders](https://io.builders/). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[ERC-20]: ./eip-20.md +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2019.md diff --git a/EIPS/eip-2020.md b/EIPS/eip-2020.md index ddf3419f59ab49..0d474c62d8b38b 100644 --- a/EIPS/eip-2020.md +++ b/EIPS/eip-2020.md @@ -1,233 +1 @@ ---- -eip: 2020 -title: E-Money Standard Token -author: Julio Faura , Fernando Paris , Daniel Lehrner -discussions-to: https://github.com/ethereum/EIPs/issues/2407 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-05-10 -requires: 20, 1066, 1996, 2009, 2018, 2019, 2021 ---- - -## Simple Summary - -The E-Money Standard Token aims to enable the issuance of regulated electronic money on blockchain networks, and its practical usage in real financial applications. - -## Actors - -#### Operator -An account, which has been approved by an account to perform an action on the behalf of another account. - -## Abstract - -Financial institutions work today with electronic systems, which hold account balances in databases on core banking systems. In order for an institution to be allowed to maintain records of client balances segregated and available for clients, such institution must be regulated under a known legal framework and must possess a license to do so. Maintaining a license under regulatory supervision entails ensuring compliance (i.e. performing KYC on all clients and ensuring good AML practices before allowing transactions) and demonstrating technical and operational solvency through periodic audits, so clients depositing funds with the institution can rest assured that their money is safe. - -## Motivation - -There are only a number of potential regulatory license frameworks that allow institutions to issue and hold money balances for customers (be it retail corporate or institutional types). The most important and practical ones are three: -* **Electronic money entities**: these are legally regulated vehicles that are mostly used today for cash and payments services, instead of more complex financial services. For example prepaid cards or online payment systems such as PayPal run on such schemes. In most jurisdictions, electronic money balances are required to be 100% backed by assets, which often entails holding cash on an omnibus account at a bank with 100% of the funds issued to clients in the electronic money ledger. -* **Banking licenses**: these include commercial and investment banks, which segregate client funds using current and other type of accounts implemented on core banking systems. Banks can create money by lending to clients, so bank money can be backed by promises to pay and other illiquid assets. -* **Central banks**: central banks hold balances for banks in RTGS systems, similar to core banking systems but with much more restricted yet critical functionality. Central banks create money by lending it to banks, which pledge their assets to central banks as a lender of last resort for an official interest rate. - -Regulations for all these types of electronic money are local, i.e. only valid for each jurisdiction and not valid in others. Regulations can vary as well dramatically in different jurisdictions — for example there are places with no electronic money frameworks, on everything has to be done through banking licenses or directly with a central bank. But in all cases compliance with existing regulation needs to ensured, in particular: -* **Know Your Customer (KYC)**: the institution needs to identify the client before providing them with the possibility of depositing money or transact. In different jurisdictions and for different types of licenses there are different levels of balance and activity that can be allowed for different levels of KYC. For example, low KYC requirements with little checks or even no checks at all can usually be acceptable in many jurisdictions if cashin balances are kept low (i.e. hundreds of dollars) -* **Anti Money Laundering (AML)**: the institution needs to perform checks of parties transacting with its clients, typically checking against black lists and doing sanction screening, most notably in the context of international transactions - -Beyond cash, financial instruments such as equities or bonds are also registered in electronic systems in most cases, although all these systems and the bank accounting systems are only connected through rudimentary messaging means, which leads to the need for reconciliations and manual management in many cases. Cash systems to provide settlement of transactions in the capital markets are not well-connected to the transactional systems, and often entail delays and settlement risk. - -The E-Money Standard Token builds on Ethereum standards currently in use such as [ERC-20], but it extends them to provide few key additional pieces of functionality, needed in the regulated financial world: -* **Compliance**: E-Money Standard Token implements a set of methods to check in advance whether user-initiated transactions can be done from a compliance point of view. Implementations must `require` that these methods return a positive answer before executing the transaction. -* **Clearing**: In addition to the standard [ERC-20] `transfer` method, E-Money Standard Token provides a way to submit transfers that need to be cleared by the token issuing authority off-chain. These transfers are then executed in two steps: - 1. transfers are ordered - 1. after clearing them, transfers are executed or rejected by the operator of the token contract -* **Holds**: token balances can be put on hold, which will make the held amount unavailable for further use until the hold is resolved (i.e. either executed or released). Holds have a payer, a payee, and a notary who is in charge of resolving the hold. Holds also implement expiration periods, after which anyone can release the hold Holds are similar to escrows in that are firm and lead to final settlement. Holds can also be used to implement collateralization. -* **Funding requests**: users can request for a wallet to be funded by calling the smart contract and attaching a debit instruction string. The tokenizer reads this request, interprets the debit instructions, and triggers a transfer in the bank ledger to initiate the tokenization process. -* **Payouts**: users can request payouts by calling the smart contract and attaching a payment instruction string. The (de)tokenizer reads this request, interprets the payment instructions, and triggers the transfer of funds (typically from the omnibus account) into the destination account, if possible. Note that a redemption request is a special type of payout in which the destination (bank) account for the payout is the bank account linked to the token wallet. - -The E-Money Standard Token is thus different from other tokens commonly referred to as "stable coins" in that it is designed to be issued, burnt and made available to users in a compliant manner (i.e. with full KYC and AML compliance) through a licensed vehicle (an electronic money entity, a bank, or a central bank), and in that it provides the additional functionality described above, so it can be used by other smart contracts implementing more complex financial applications such as interbank payments, supply chain finance instruments, or the creation of E-Money Standard Token denominated bonds and equities with automatic delivery-vs-payment. - -## Specification - -```solidity -interface EMoneyToken /* is ERC-1996, ERC-2018, ERC-2019, ERC-2021 */ { - function currency() external view returns (string memory); - function version() external pure returns (string memory); - - function availableFunds(address account) external view returns (uint256); - - function checkTransferAllowed(address from, address to, uint256 value) external view returns (byte status); - function checkApproveAllowed(address from, address spender, uint256 value) external view returns (byte status); - - function checkHoldAllowed(address from, address to, address notary, uint256 value) external view returns (byte status); - function checkAuthorizeHoldOperatorAllowed(address operator, address from) external view returns (byte status); - - function checkOrderTransferAllowed(address from, address to, uint256 value) external view returns (byte status); - function checkAuthorizeClearableTransferOperatorAllowed(address operator, address from) external view returns (byte status); - - function checkOrderFundAllowed(address to, address operator, uint256 value) external view returns (byte status); - function checkAuthorizeFundOperatorAllowed(address operator, address to) external view returns (byte status); - - function checkOrderPayoutAllowed(address from, address operator, uint256 value) external view returns (byte status); - function checkAuthorizePayoutOperatorAllowed(address operator, address from) external view returns (byte status); -} -``` - -### Mandatory checks - -The checks must be verified in their corresponding actions. The action must only be successful if the check return an `Allowed` status code. In any other case the functions must revert. - -### Status codes - -If an action is allowed `0x11` (Allowed), or an issuer-specific code with equivalent but more precise meaning must be returned. If the action is not allowed the status must be `0x10` (Disallowed), or an issuer-specific code with equivalent but more precise meaning. - -### Functions - -#### currency - -Returns the currency that backs the token. The value must be a code defined in [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217). - -| Parameter | Description | -| ---------|-------------| -| - | - | - -#### version - -Returns the current version of the smart contract. The format of the version is up to the implementer of the EIP. - -| Parameter | Description | -| ---------|-------------| -| - | - | - -#### availableFunds - -Returns the total net funds of an account. Taking into consideration the outright balance and the held balances. - -| Parameter | Description | -| ---------|-------------| -| account | The account which available funds should be returned | - -#### checkTransferAllowed - -Checks if the `transfer` or `transferFrom` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be transferred if executed | -| value | The amount to be transferred | - -#### checkApproveAllowed - -Checks if the `approve` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| from | The address of the payer, from whom the tokens are to be taken if executed | -| spender | The address of the spender, which potentially can initiate transfers on behalf of `from` | -| value | The maximum amount to be transferred | - -#### checkHoldAllowed - -Checks if the `hold` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be transferred if executed | -| notary | The address of the notary who is going to determine whether the hold is to be executed or released | -| value | The amount to be transferred. Must be less or equal than the balance of the payer | - -#### checkAuthorizeHoldOperatorAllowed - -Checks if the `checkAuthorizeHoldOperatorAllowed` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be approved as operator of clearable transfers | -| from | The address on which behalf holds could potentially be issued | - -#### checkOrderTransferAllowed - -Checks if the `orderTransfer` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| from | The address of the payer, from whom the tokens are to be taken if executed | -| to | The address of the payee, to whom the tokens are to be paid if executed | -| value | The amount to be transferred. Must be less or equal than the balance of the payer | - -#### checkAuthorizeClearableTransferOperatorAllowed - -Checks if the `authorizeClearableTransferOperator` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be approved as operator of clearable transfers | -| from | The address on which behalf clearable transfers could potentially be ordered | - -#### checkOrderFundAllowed - -Checks if the `orderFund` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| to | The address to which the tokens are to be given if executed | -| operator | The address of the requester, which initiates the funding order | -| value | The amount to be funded | - -#### checkAuthorizeFundOperatorAllowed - -Checks if the `authorizeFundOperator` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be approved as operator of ordering funding | -| to | The address which the tokens are to be given if executed | - -#### checkOrderPayoutAllowed - -Checks if the `orderPayout` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| from | The address from whom the tokens are to be taken if executed | -| operator | The address of the requester, which initiates the payout request | -| value | The amount to be paid out | - -#### checkAuthorizePayoutOperatorAllowed - -Checks if the `authorizePayoutOperator` function is allowed to be executed with the given parameters. - -| Parameter | Description | -| ---------|-------------| -| operator | The address to be approved as operator of ordering payouts | -| from | The address from which the tokens are to be taken if executed | - -## Rationale - -This EIP unifies [ERC-1996][ERC-1996], [ERC-2018][ERC-2018], [ERC-2019][ERC-2019] and [ERC-2021][ERC-2021] and adds the checks for the compliance on top of it. By this way the separate EIPs are otherwise independent of each other, and the E-Money Standard Token offers a solution for all necessary functionality of regulated electronic money. - -While not requiring it, the naming of the check functions was adopted from [ERC-1462][ERC-1462]. - -## Backwards Compatibility - -This EIP is fully backwards compatible as its implementation extends the functionality of [ERC-1996][ERC-1996], [ERC-2018][ERC-2018], [ERC-2019][ERC-2019], [ERC-2021][ERC-2021] and [ERC-1066][ERC-1066]. - -## Implementation - -The GitHub repository [IoBuilders/em-token](https://github.com/IoBuilders/em-token) contains the work in progress implementation. - -## Contributors -This proposal has been collaboratively implemented by [adhara.io](https://adhara.io/) and [io.builders](https://io.builders/). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[ERC-20]: ./eip-20.md -[ERC-1066]: ./eip-1066.md -[ERC-1462]: ./eip-1462.md -[ERC-1996]: ./eip-1996.md -[ERC-2018]: ./eip-2018.md -[ERC-2019]: ./eip-2019.md -[ERC-2021]: ./eip-2021.md +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2020.md diff --git a/EIPS/eip-2021.md b/EIPS/eip-2021.md index 8464686dd789ce..7511ee78aca887 100644 --- a/EIPS/eip-2021.md +++ b/EIPS/eip-2021.md @@ -1,290 +1 @@ ---- -eip: 2021 -title: Payoutable Token -author: Fernando Paris , Julio Faura , Daniel Lehrner -discussions-to: https://github.com/ethereum/EIPs/issues/2106 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-05-10 -requires: 20, 1066, 1996 ---- - -## Simple Summary -An extension to the [ERC-20] standard token that allows Token wallet owners to request payout from their wallet, by calling the smart contract and attaching a payout instruction string. - -## Actors - -#### Token Wallet Owners -The person or company who owns the wallet, and will order payout. - -#### Token contract owner / agent -The entity, company responsible/owner of the token contract, and token issuing/minting. This actor is in charge of trying to fulfill all payout request(s), reading the payout instruction(s), and correlate the payout details. - -#### Orderer -An actor who is enabled to initiate payout orders on behalf of a token wallet owner. - -## Abstract -Token wallet owners (or approved addresses) can order payout requests through blockchain. This is done by calling the ```orderPayoutFrom``` or ```orderPayoutFrom``` methods, which initiate the workflow for the token contract operator to either honor or reject the payout request. In this case, payout instructions are provided when submitting the request, which are used by the operator to determine the destination of the funds. - -In general, it is not advisable to place explicit routing instructions for the payouts on a verbatim basis on the blockchain, and it is advised to use a private communication alternatives, such as private channels, encrypted storage or similar, to do so (external to the blockchain ledger). Another (less desirable) possibility is to place these instructions on the instructions field in encrypted form. - -## Motivation -Nowadays most of the token payout requests, need a previous centralized transaction, to be able to define the payout destination to be able to execute the payout (burn transaction). -In the aim of trying to bring all the needed steps into decentralization, exposing all the needed steps of token lifecycle and payment transactions, a payout request can allow wallet owner to initiate the payout order via blockchain. -Key benefits: - -* Payout, burning traceability is enhanced bringing the initiation into the ledger. All payment, payout statuses can be stored on chain. -* Almost all money/token lifecycle is covered via a decentralized approach, complemented with private communications which is common use in the ecosystem. - -In this case, the following movement of tokens are done as the process progresses: - -* Upon launch of the payout request, the appropriate amount of funds are placed on a hold with a predefined notary defined by the platform, and the payout is placed into a ```Ordered``` state -* The operator then can put the payout request ```InProcess```, which prevents the _orderer_ of the payout from being able to cancel the payout request -* After checking the payout is actually possible the operator then executes the hold, which moves the funds to a suspense wallet and places the payout into the ```FundsInSuspense``` state -* The operator then moves the funds offchain (usually from the omnibus account) to the appropriate destination account, then burning the tokens from the suspense wallet and rendering the payout into the ```Executed``` state -* Either before or after placing the request ```InProcess```, the operator can also reject the payout, which returns the funds to the payer and eliminates the hold. The resulting end state of the payout is ```Rejected``` -* When the payout is ```Ordered``` and before the operator places it into the ```InProcess``` state, the orderer of the payout can also cancel it, which frees up the hold and puts the payout into the final ```Cancelled``` state - -## Specification - -```solidity -interface IPayoutable /* is ERC-20 */ { - enum PayoutStatusCode { - Nonexistent, - Ordered, - InProcess, - FundsInSuspense, - Executed, - Rejected, - Cancelled - } - function authorizePayoutOperator(address orderer) external returns (bool); - function revokePayoutOperator(address orderer) external returns (bool); - function orderPayout(string calldata operationId, uint256 value, string calldata instructions) external returns (bool); - function orderPayoutFrom(string calldata operationId, address walletToBePaidOut, uint256 value, string calldata instructions) external returns (bool); - function cancelPayout(string calldata operationId) external returns (bool); - function processPayout(string calldata operationId) external returns (bool); - function putFundsInSuspenseInPayout(string calldata operationId) external returns (bool); - function executePayout(string calldata operationId) external returns (bool); - function rejectPayout(string calldata operationId, string calldata reason) external returns (bool); - - function isPayoutOperatorFor(address walletToDebit, address orderer) external view returns (bool); - function retrievePayoutData(string calldata operationId) external view returns (address walletToDebit, uint256 value, string memory instructions, PayoutStatusCode status); - - event PayoutOrdered(address indexed orderer, string indexed operationId, address indexed walletToDebit, uint256 value, string instructions); - event PayoutInProcess(address indexed orderer, string indexed operationId); - event PayoutFundsInSuspense(address indexed orderer, string indexed operationId); - event PayoutExecuted(address indexed orderer, string indexed operationId); - event PayoutRejected(address indexed orderer, string indexed operationId, string reason); - event PayoutCancelled(address indexed orderer, string indexed operationId); - event PayoutOperatorAuthorized(address indexed walletToBePaidOut, address indexed orderer); - event PayoutOperatorRevoked(address indexed walletToBePaidOut, address indexed orderer); -} -``` - -### Functions - -#### authorizePayoutOperator - -Wallet owner, allows a given address to be payout orderer. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer. | - -#### revokePayoutOperator - -Wallet owner, Revokes a given address to be payout orderer. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer. | - -#### orderPayout - -Creates a payout request, that will be processed by the token operator. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request | -| value | The amount to be paid out. | -| instruction | A string including the payment instruction. | - -#### orderPayoutFrom - -Creates a payout request, on behalf of a wallet owner, that will be processed by the token operator. The function must revert if the operation ID has been used before. - -| Parameter | Description | -| ---------|-------------| -| operationId |The unique ID to identify the request | -| walletToBePaidOut | The wallet to be paid out on behalf. | -| value | The amount to be paid out. | -| instruction | A string including the payment instruction. | - -#### cancelPayout - -Cancels a payout request. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request that is going to be cancelled. This can only be done by token holder, or the payout initiator/orderer. | -| reason | The specific reason that explains why the payout request was rejected. [EIP-1066] codes can be used. | - - -#### processPayout - -Marks a payout request as on process. After the status is on process, order cannot be cancelled. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify that the request is in process. | - -#### putFundsInSuspenseInPayout - -Put a given payout in suspense. Can only be done if it is in process. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify that the request is in process. | - -#### executePayout - -Burn the amount of tokens and marks a payout request as executed. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request that has been executed. | - -#### rejectPayout - -Rejects a given operation with a reason. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request that has been executed. | -| reason | The specific reason that explains why the payout request was rejected. [EIP-1066] codes can be used | - -#### isApprovedToOrderPayout - -Checks that given player is allowed to order payout requests, for a given wallet. - -| Parameter | Description | -| ---------|-------------| -| walletToBePaidOut | The wallet to be paid out, and checked for approval permission. | -| orderer | The address of the orderer, to be checked for approval permission. | - -#### retrievePayoutData - -Retrieves all the payout request data. Only operator, tokenHolder, and orderer can get the given operation data. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the orderer, to correlate the right data. | -| operationId | The unique ID to identify the payout order. | - -### Events - -#### Payout Ordered - -Emitted when an token wallet owner orders a payout request. - -| Parameter | Description | -| ---------|-------------| -| operationId | The unique ID to identify the request | -| walletToBePaidOut | The wallet that is requested to be paid out | -| value | The amount to be funded. | -| instruction | A string including the payment instruction. | - -#### PayoutFundsInSuspense - -Emitted when an operator puts fund in suspense. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the payout request orderer. | -| operationId | The unique ID to identify the payout. | - -#### PayoutInProcess - -Emitted when an operator accepts a payout request, and the operation is in process. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the payout request orderer. | -| operationId | The unique ID to identify the payout. | - -#### PayoutExecuted - -Emitted when an operator has executed a payout request. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the payout request orderer. | -| operationId | The unique ID to identify the payout. | - -#### PayoutRejected - -Emitted when an operator has rejected a payout request. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the payout request orderer. | -| operationId | The unique ID to identify the payout. | -| reason | The specific reason that explains why the payout request was rejected. [EIP-1066] codes can be used | - -#### PayoutCancelled - -Emitted when a token holder, orderer, has cancelled a payout request. This can only be done if the operator hasn't put the payout order in process. - -| Parameter | Description | -| ---------|-------------| -| orderer | The address of the payout request orderer. | -| operationId | The unique ID per payout issuer to identify the payout. | - -#### PayoutOperatorAuthorized - -Emitted when a given player, operator, company or a given persona, has been approved to start payout request for a given token holder. - -| Parameter | Description | -| ---------|-------------| -| walletToBePaidOut | The wallet that the player is allowed to start payout requests | -| orderer |The address that allows the the player to start requests. | - -#### PayoutOperatorRevoked - -Emitted when a given player has been revoked initiate payout requests. - -| Parameter | Description | -| ---------|-------------| -| walletToBePaidOut | The wallet that the player is allowed to start payout requests | -| orderer |The address that allows the the player to start requests. | - -## Rationale -This standards provides a functionality to allow token holders to start payout requests in a decentralized way. - -It's important to highlight that the token operator, need to process all payout request, updating the payout status based on the linked payment that will be done. - -Payout instruction format is open. ISO payment standard like is a good start point. - -This EIP uses [EIP-1996] to hold the money after a payout is ordered. The token contract owner or agent, whose implementation is not part of this proposal, acts as a predefined notary to decide if the payout is executed or not. - -The `operationId` is a string and not something more gas efficient to allow easy traceability of the hold and allow human readable ids. It is up to the implementer if the string should be stored on-chain or only its hash, as it is enough to identify a hold. - -The `operationId` is a competitive resource. It is recommended, but not required, that the hold issuers used a unique prefix to avoid collisions. - -## Backwards Compatibility -This EIP is fully backwards compatible as its implementation extends the functionality of [ERC-20] and [ERC-1996]. - -## Implementation -The GitHub repository [IoBuilders/payoutable-token](https://github.com/IoBuilders/payoutable-token) contains the reference implementation. - -## Contributors -This proposal has been collaboratively implemented by [adhara.io](https://adhara.io/) and [io.builders](https://io.builders/). - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[ERC-20]: ./eip-20.md -[EIP-1066]: ./eip-1066.md -[EIP-1996]: ./eip-1996.md +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2021.md diff --git a/EIPS/eip-205.md b/EIPS/eip-205.md index 451cc835e4f67a..4b94e8e34db596 100644 --- a/EIPS/eip-205.md +++ b/EIPS/eip-205.md @@ -1,69 +1 @@ ---- -eip: 205 -title: ENS support for contract ABIs -author: Nick Johnson -type: Standards Track -category: ERC -status: Stagnant -created: 2017-02-06 -requires: 137, 181 ---- - -## Simple Summary -This EIP proposes a mechanism for storing ABI definitions in ENS, for easy lookup of contract interfaces by callers. - -## Abstract -ABIs are important metadata required for interacting with most contracts. At present, they are typically supplied out-of-band, which adds an additional burden to interacting with contracts, particularly on a one-off basis or where the ABI may be updated over time. The small size of ABIs permits an alternative solution, storing them in ENS, permitting name lookup and ABI discovery via the same process. - -ABIs are typically quite compact; the largest in-use ABI we could find, that for the DAO, is 9450 bytes uncompressed JSON, 6920 bytes uncompressed CBOR, and 1128 bytes when the JSON form is compressed with zlib. Further gains on CBOR encoding are possible using a CBOR extension that permits eliminating repeated strings, which feature extensively in ABIs. Most ABIs, however, are far shorter than this, consisting of only a few hundred bytes of uncompressed JSON. - -This EIP defines a resolver profile for retrieving contract ABIs, as well as encoding standards for storing ABIs for different applications, allowing the user to select between different representations based on their need for compactness and other considerations such as onchain access. - -## Specification -### ABI encodings -In order to allow for different tradeoffs between onchain size and accessibility, several ABI encodings are defined. Each ABI encoding is defined by a unique constant with only a single bit set, allowing for the specification of 256 unique encodings in a single uint. - -The currently recognised encodings are: - -| ID | Description | -|----|----------------------| -| 1 | JSON | -| 2 | zlib-compressed JSON | -| 4 | CBOR | -| 8 | URI | - -This table may be extended in future through the EIP process. - -Encoding type 1 specifies plaintext JSON, uncompressed; this is the standard format in which ABIs are typically encoded, but also the bulkiest, and is not easily parseable onchain. - -Encoding type 2 specifies zlib-compressed JSON. This is significantly smaller than uncompressed JSON, and is straightforward to decode offchain. However, it is impracticalfor onchain consumers to use. - -Encoding type 4 is [CBOR](https://cbor.io/). CBOR is a binary encoding format that is a superset of JSON, and is both more compact and easier to parse in limited environments such as the EVM. Consumers that support CBOR are strongly encouraged to also support the [stringref extension](http://cbor.schmorp.de/stringref) to CBOR, which provides significant additional reduction in encoded size. - -Encoding type 8 indicates that the ABI can be found elsewhere, at the specified URI. This is typically the most compact of the supported forms, but also adds external dependencies for implementers. The specified URI may use any schema, but HTTP, IPFS, and Swarm are expected to be the most common. - -### Resolver profile -A new resolver interface is defined, consisting of the following method: - - function ABI(bytes32 node, uint256 contentType) constant returns (uint256, bytes); - -The interface ID of this interface is 0x2203ab56. - -contentType is a bitfield, and is the bitwise OR of all the encoding types the caller will accept. Resolvers that implement this interface must return an ABI encoded using one of the requested formats, or `(0, "")` if they do not have an ABI for this function, or do not support any of the requested formats. - -The `abi` resolver profile is valid on both forward and reverse records. - -### ABI lookup process - -When attempting to fetch an ABI based on an ENS name, implementers should first attempt an ABI lookup on the name itself. If that lookup returns no results, they should attempt a reverse lookup on the Ethereum address the name resolves to. - -Implementers should support as many of the ABI encoding formats as practical. - -## Rationale - -Storing ABIs onchain avoids the need to introduce additional dependencies for applications wishing to fetch them, such as swarm or HTTP access. Given the typical compactness of ABIs, we believe this is a worthwhile tradeoff in many cases. - -The two-step resolution process permits different names to provide different ABIs for the same contract, such as in the case where it's useful to provide a minimal ABI to some callers, as well as specifying ABIs for contracts that did not specify one of their own. The fallback to looking up an ABI on the reverse record permits contracts to specify their own canonical ABI, and prevents the need for duplication when multiple names reference the same contract without the need for different ABIs. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-205.md diff --git a/EIPS/eip-2098.md b/EIPS/eip-2098.md index f440b4057e7655..4deaf492fd97ea 100644 --- a/EIPS/eip-2098.md +++ b/EIPS/eip-2098.md @@ -1,138 +1 @@ ---- -eip: 2098 -title: Compact Signature Representation -description: A compact representation of an Ethereum Signature. -status: Final -type: Standards Track -category: ERC -author: Richard Moore (@ricmoo), Nick Johnson -discussions-to: https://github.com/ethereum/EIPs/issues/2440 -created: 2019-03-14 -requires: 2 ---- - - -## Abstract - -The secp256k1 curve permits the computation of the public key of signed -digest when coupled with a signature, which is used implicitly to -establish the origin of a transaction from an Externally Owned Account -as well as on-chain in EVM contracts for example, in meta-transactions and -multi-sig contracts. - -Currently signatures require 65 bytes to represent, which when aligned -to 256-bit words, requires 96 bytes (with 31 zero bytes injected). The -yParity in RLP-encoded transactions also require (on average) 1.5 bytes. -With compact signatures, this can be reduced to 64 bytes, which remains 64 -bytes when word-aligned, and in the case of RLP-encoded transactions -saves the 1.5 bytes required for the yParity. - -## Motivation - -The motivations for a compact representation are to simplify handling -transactions in client code, reduce gas costs and reduce transaction sizes. - - -## Specification - -A secp256k1 signature is made up of 3 parameters, `r`, `s` and `yParity`. -The `r` represents the `x` component on the curve (from which the `y` can be -computed), and the `s` represents the challenge solution for signing by a -private key. Due to the symmetric nature of an elliptic curve, a `yParity` -is required, which indicates which of the 2 possible solutions was intended, -by indicating its parity (odd-ness). - -Two key observations are required to create a compact representation. - -First, the `yParity` parameter is always either 0 or 1 (canonically the values -used have historically been 27 and 28, as these values didn't collide with other -binary prefixes used in Bitcoin). - -Second, the top bit of the `s` parameters is **always** 0, due to the use of -canonical signatures which flip the solution parity to prevent negative values, -which was introduced as [a constraint in Homestead](./eip-2.md). - -So, we can hijack the top bit in the `s` parameter to store the value of -`yParity`, resulting in: - -``` -[256-bit r value][1-bit yParity value][255-bit s value] -``` - - -### Example Implementation In Python - -```python -# Assume yParity is 0 or 1, normalized from the canonical 27 or 28 -def to_compact(r, s, yParity): - return { - "r": r, - "yParityAndS": (yParity << 255) | s - } - -def to_canonical(r, yParityAndS): - return { - "r": r, - "s": yParityAndS & ((1 << 255) - 1), - "yParity": (yParityAndS >> 255) - } -``` - - -## Rationale - -The compact representation proposed is simple to both compose and decompose -in clients and in Solidity, so that it can be easily (and intuitively) supported, -while reducing transaction sizes and gas costs. - - -## Backwards Compatibility - -The Compact Representation does not collide with canonical signature as -it uses 2 parameters (r, yParityAndS) and is 64 bytes long while canonical -signatures involve 3 separate parameters (r, s, yParity) and are 65 bytes long. - - -## Test Cases - -``` -Private Key: 0x1234567890123456789012345678901234567890123456789012345678901234 -Message: "Hello World" -Signature: - r: 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90 - s: 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064 - v: 27 -Compact Signature: - r: 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90 - yParityAndS: 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064 -``` - -``` -Private Key: 0x1234567890123456789012345678901234567890123456789012345678901234 -Message: "It's a small(er) world" -Signature: - r: 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76 - s: 0x139c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793 - v: 28 -Compact Signature: - r: 0x9328da16089fcba9bececa81663203989f2df5fe1faa6291a45381c81bd17f76 - yParityAndS: 0x939c6d6b623b42da56557e5e734a43dc83345ddfadec52cbe24d0cc64f550793 -``` - - -## Reference Implementation - -The ethers.js library [supports this in v5](https://github.com/ethers-io/ethers.js/blob/ethers-v5-beta/packages/bytes/src.ts/index.ts#L323) -as an unofficial property of split signatures (i.e. `sig._vs`), but should be -considered an internal property that may change at discretion of the community -and any changes to this EIP. - - -## Security Considerations - -There are no additional security concerns introduced by this EIP. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2098.md diff --git a/EIPS/eip-2135.md b/EIPS/eip-2135.md index 5d600773f27803..09861f578b18f8 100644 --- a/EIPS/eip-2135.md +++ b/EIPS/eip-2135.md @@ -1,167 +1 @@ ---- -eip: 2135 -title: Consumable Interface (Tickets, etc) -description: An interface extending ERC-721 and ERC-1155 for consumability, supporting use case such as an event ticket. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/eip-2135-erc-consumable-interface/3439 -status: Final -type: Standards Track -category: ERC -created: 2019-06-23 -requires: 165, 721, 1155 ---- - -## Abstract - -This EIP defines an interface to mark a digital asset as "consumable" and to react to its "consumption." - -## Motivation - -Digital assets sometimes need to be consumaed. One of the most common examples is a concert ticket. -It is "consumed" when the ticket-holder enters the concert hall. - -Having a standard interface enables interoperability for services, clients, UI, and inter-contract functionalities on top of this use-case. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -1. Any compliant contract **MUST** implement the following interface: - -```solidity -pragma solidity >=0.7.0 <0.9.0; - -/// The ERC-165 identifier of this interface is 0xdd691946 -interface IERC2135 { - /// @notice The consume function consumes a token every time it succeeds. - /// @param _consumer the address of consumer of this token. It doesn't have - /// to be the EOA or contract Account that initiates the TX. - /// @param _assetId the NFT asset being consumed - /// @param _data extra data passed in for consume for extra message - /// or future extension. - function consume( - address _consumer, - uint256 _assetId, - uint256 _amount, - bytes calldata _data - ) external returns (bool _success); - - /// @notice The interface to check whether an asset is consumable. - /// @param _consumer the address of consumer of this token. It doesn't have - /// to be the EOA or contract Account that initiates the TX. - /// @param _assetId the NFT asset being consumed. - /// @param _amount the amount of the asset being consumed. - function isConsumableBy( - address _consumer, - uint256 _assetId, - uint256 _amount - ) external view returns (bool _consumable); - - /// @notice The event emitted when there is a successful consumption. - /// @param consumer the address of consumer of this token. It doesn't have - /// to be the EOA or contract Account that initiates the TX. - /// @param assetId the NFT asset being consumed - /// @param amount the amount of the asset being consumed. - /// @param data extra data passed in for consume for extra message - /// or future extension. - event OnConsumption( - address indexed consumer, - uint256 indexed assetId, - uint256 amount, - bytes data - ); -} -``` - -2. If the compliant contract is an [ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md) token, in addition to `OnConsumption`, it **MUST** also emit the `Transfer` / `TransferSingle` event (as applicable) as if a token has been transferred from the current holder to the zero address if the call to `consume` method succeeds. - -3. `supportsInterface(0xdd691946)` **MUST** return `true` for any compliant contract, as per [ERC-165](./eip-165.md). - -## Rationale - -1. The function `consume` performs the consume action. This EIP does not assume: - -- who has the power to perform consumption -- under what condition consumption can occur - -It does, however, assume the asset can be identified in a `uint256` asset id as in the parameter. A design convention and compatibility consideration is put in place to follow the ERC-721 pattern. - -2. The event notifies subscribers whoever are interested to learn an asset is being consumed. - -3. To keep it simple, this standard *intentionally* contains no functions or events related to the creation of a consumable asset. This is because the creation of a consumable asset will need to make assumptions about the nature of an actual use-case. If there are common use-cases for creation, another follow up standard can be created. - -4. Metadata associated to the consumables is not included the standard. If necessary, related metadata can be created with a separate metadata extension interface like `ERC721Metadata` from [ERC-721](./eip-721.md) - -5. We choose to include an `address consumer` for `consume` function and `isConsumableBy` so that an NFT MAY be consumed for someone other than the transaction initiator. - -6. We choose to include an extra `_data` field for future extension, such as -adding crypto endorsements. - -7. We explicitly stay opinion-less about whether ERC-721 or ERC-1155 shall be required because -while we design this EIP with ERC-721 and ERC-1155 in mind mostly, we don't want to rule out -the potential future case someone use a different token standard or use it in different use cases. - -8. The boolean view function of `isConsumableBy` can be used to check whether an asset is -consumable by the `_consumer`. - -## Backwards Compatibility - -This interface is designed to be compatible with ERC-721 and NFT of ERC-1155. It can be tweaked to used for [ERC-20](./eip-20.md), [ERC-777](./eip-777.md) and Fungible Token of ERC-1155. - -## Test Cases - -```ts - - describe("Consumption", function () { - it("Should consume when minted", async function () { - const fakeTokenId = "0x1234"; - const { contract, addr1 } = await loadFixture(deployFixture); - await contract.safeMint(addr1.address, fakeTokenId); - expect(await contract.balanceOf(addr1.address)).to.equal(1); - expect(await contract.ownerOf(fakeTokenId)).to.equal(addr1.address); - expect(await contract.isConsumableBy(addr1.address, fakeTokenId, 1)).to.be.true; - const tx = await contract.consume(addr1.address, fakeTokenId, 1, []); - const receipt = await tx.wait(); - const events = receipt.events.filter((x: any) => { return x.event == "OnConsumption" }); - expect(events.length).to.equal(1); - expect(events[0].args.consumer).to.equal(addr1.address); - expect(events[0].args.assetId).to.equal(fakeTokenId); - expect(events[0].args.amount).to.equal(1); - expect(await contract.balanceOf(addr1.address)).to.equal(0); - await expect(contract.ownerOf(fakeTokenId)) - .to.be.rejectedWith('ERC721: invalid token ID'); - await expect(contract.isConsumableBy(addr1.address, fakeTokenId, 1)) - .to.be.rejectedWith('ERC721: invalid token ID'); - }); - }); - - describe("EIP-165 Identifier", function () { - it("Should match", async function () { - const { contract } = await loadFixture(deployFixture); - expect(await contract.get165()).to.equal("0xdd691946"); - expect(await contract.supportsInterface("0xdd691946")).to.be.true; - }); - }); -``` - -## Reference Implementation - -A deployment of version 0x1002 has been deployed onto `goerli` testnet at address `0x3682bcD67b8A5c0257Ab163a226fBe07BF46379B`. - -Find the reference contract verified source code on Etherscan's -`goerli` site for the address above. - -## Security Considerations - -Compliant contracts should pay attention to the balance change when a token is consumed. -When the contract is being paused, or the user is being restricted from transferring a token, -the consumeability should be consistent with the transferral restriction. - -Compliant contracts should also carefully define access control, particularlly whether any EOA or contract account may or may not initiate a `consume` method in their own use case. - -Security audits and tests should be used to verify that the access control to the `consume` -function behaves as expected. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2135.md diff --git a/EIPS/eip-2157.md b/EIPS/eip-2157.md index 47481224e37605..ce1749bb771f53 100644 --- a/EIPS/eip-2157.md +++ b/EIPS/eip-2157.md @@ -1,136 +1 @@ ---- -eip: 2157 -title: dType Storage Extension - Decentralized Type System for EVM -author: Loredana Cirstea (@loredanacirstea), Christian Tzurcanu (@ctzurcanu) -discussions-to: https://github.com/ethereum/EIPs/issues/2157 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-06-28 -requires: 1900 ---- - -## Simple Summary - -This ERC is an extension of ERC-1900, proposing an optional storage extension for dType, a decentralized type system, specifying a general ABI for all storage contracts that contain type instances. - -## Abstract - -The storage extension will enable easy navigation and retrieval of type data that is intended to be of public use. This is possible through standardizing the ABI of the dType storage contracts, with the effect of having a deterministic path to a type instance record. This standardization enables a more effective on-chain and off-chain use of data and opens up possibilities for decentralized applications, enabling developers to build on top of public global data. - -## Motivation - -Currently, Ethereum does not have standardization of data addressability. This might not be needed for data that is meant to be quasi-private, however, it is needed for data that is meant for public consumption. ERC-1900 has started standardizing data types for increasing interoperability between projects, but this is not enough if we want to build a global ecosystem. Deterministic data addressability will enable anyone to build upon the same public data sets, off-chain or on-chain. - -It is true that with ERC-1900, blockchain data analysis and type-specific data retrieval will be possible off-chain, but this implies relying on centralized data caches (blockchain explorers) or maintaining your own data cache. Moreover, this option does not allow on-chain standardization on data retrieval paths, therefore limiting the type of on-chain interoperable operations that can be done. - -Having a clear way of retrieving data, instead of analyzing the blockchain for contracts that have a certain type in their ABI or bytecode, will make development easier and more decentralized for applications that target global data on specific types. - -For example, a decentralized market place can be built on top of some marketplace-specific types, and by knowing exactly where the type data is stored, it is easy to create custom algorithms that provide the user with the product information they seek. Everyone has access to the data and the data path is standardized. - -Moreover, by standardizing storage contract interfaces, ABI inference is possible. The common interface, together with the dType registry will provide all the data needed to reconstruct the ABI. - -This system can be extended with access and mutability control later on, in a future proposal. Access and mutability control will be necessary for public-use global systems. Moreover, we can have a homogeneous application of permissions across system components. This is not detailed in the present proposal. - -Another use case is data bridges between Ethereum shards or between Ethereum and other chains. Data syncing between shards/chains can be done programmatically, across data types (from various projects). Imagine a user having a public profile/identity contract on one chain, wishing to move that profile on Ethereum. By supporting the origin chain types and having a standardized storage mechanism, data moving processes will be the same. - -This pattern of separating data type definitions and storage allows developers to create functional programming-like patterns on Ethereum, even though languages such as Solidity are not functional. - -## Specification - -### TypeRootContract - -ERC-1900 defines a `contractAddress` field in the type metadata. For the limited purpose of ERC-1900, this field contains the value of the Ethereum type library in which the type definition exists. For the purpose of this ERC, the `contractAddress` will contain the Etherereum address of a `TypeRootContract`. - -```solidity -contract TypeRootContract { - address public libraryAddress; - address public storageAddress; - - constructor(address _library, address _storage) public { - libraryAddress = _library; - storageAddress = _storage; - } -} -``` - -- `libraryAddress` - Ethereum address of the type definition library, from ERC-1900 -- `storageAddress` - Ethereum address of the type data storage contract - - -### TypeStorageContract - -This contract will use the type library to define the internal data stored in it. Each record will be a type instance, addressable by a primary identifier. The primary identifier is calculated by the type library's `getIdentifier` function, based on the type instance values. - -We propose a Solidity CRUD pattern, as described in https://medium.com/robhitchens/solidity-crud-part-1-824ffa69509a, where records can also be retrieved using their index - a monotonically increasing counter. - -An stub implementation for the TypeStorageContract would look like: - -```solidity -import './TypeALib.sol'; - -contract TypeAStorage { - using TypeALib for TypeALib.TypeA; - - bytes32[] public typeIndex; - mapping(bytes32 => Type) public typeStruct; - - struct Type { - TypeALib.TypeA data; - uint256 index; - } - - event LogNew(bytes32 indexed identifier, uint256 indexed index); - event LogUpdate(bytes32 indexed identifier, uint256 indexed index); - event LogRemove(bytes32 indexed identifier, uint256 indexed index); - - function insert(TypeALib.TypeA memory data) public returns (bytes32 identifier); - - function insertBytes(bytes memory data) public returns (bytes32 identifier); - - function remove(bytes32 identifier) public returns(uint256 index); - - function update(bytes32 identifier, TypeALib.TypeA memory data) public returns(bytes32 identifier) - - function isStored(bytes32 identifier) public view returns(bool stored); - - function getByHash(bytes32 identifier) public view returns(TypeALib.TypeA memory data); - - function getByIndex(uint256 index) public view returns(TypeALib.TypeA memory data); - - function count() public view returns(uint256 counter); -} -``` - -## Rationale - -We are now thinking about a building block as a smart contract with an encapsulated object that contains state changing functions that are only understood from within. This is more akin to Object-Oriented Programming and poses interoperability and scalability issues. Not necessarily for an individual project, but for a global Ethereum OS. This is why we are proposing to separate data from business logic and data structure definitions. - -When you have public aggregated data, categorized on each type, anyone can build tools on top of it. This is a radical change from the closed or dispersed data patterns that we find in web2. - -We have chosen to define a `TypeRootContract` instead of extending the dType registry with fields for the TypeStorage contract, because this approach enables easier interface updates in the future. It is more extensible. - -The storage pattern used for dType itself and all the Type Storage contracts can be the same. This lowers the cost of building, testing and auditing the code. - -The `TypeStorageContract` pattern should ensure: -- type instance addressability by the primary identifier -- a way to retrieve all records from the contract -- counting the number of records - - -## Backwards Compatibility - -This proposal does not affect existent Ethereum standards or implementations. It uses the present experimental version of ABIEncoderV2. - -## Test Cases - -Will be added. - -## Implementation - -An in-work implementation can be found at https://github.com/pipeos-one/dType/tree/master/contracts/contracts. -This proposal will be updated with an appropriate implementation when consensus is reached on the specifications. - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2157.md diff --git a/EIPS/eip-2193.md b/EIPS/eip-2193.md index 34152793a68686..b6c2d7e544aeca 100644 --- a/EIPS/eip-2193.md +++ b/EIPS/eip-2193.md @@ -1,92 +1 @@ ---- -eip: 2193 -title: dType Alias Extension - Decentralized Type System -author: Loredana Cirstea (@loredanacirstea), Christian Tzurcanu (@ctzurcanu) -discussions-to: https://github.com/ethereum/EIPs/issues/2192 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-07-16 -requires: 155, 1900, 2157 ---- - -## Simple Summary - -We are proposing Alias - a semantic standard for identifying on-chain resources by human-readable qualifiers, supporting any type of data. - -## Abstract - -The dType Alias is a system for providing human-readable resource identifiers to on-chain content. A resource identifier is based on the type of data (identifier provided by dType, [EIP-1900](./eip-1900.md)) and the data content (identifier provided by a dType Storage Contract, [EIP-2157](./eip-2157.md)). It is a universal way of addressing content, supporting any type of data. - -## Motivation - -There are standards that currently address the need for attaching human-readable identifiers to Ethereum accounts, such as [EIP-137](./eip-137.md). These standards are an attempt to bring domain names to Ethereum, following the same format as DNS: `subdomain.domain.tld`. This leaf -> root format is unintuitive and contradicts the semantic meaning that `.` has in programming languages, which is a root -> leaf connection (e.g. in OOP, when accessing an object's property). A more intuitive and widely used approach is a root->leaf format, used in file browsers, hierarchical menus, and even in other decentralized systems, which give unique identifiers to resources (e.g. `0x56.Currency.TCoin` in [Libra](https://medium.com/r/?url=https%3A%2F%2Fdevelopers.libra.org). - -Moreover, [EIP-137](./eip-137.md) is not flexible enough to address smart contract content, which can contain heterogeneous data that belongs to various accounts. For example, a `PaymentChannel` smart contract can have an domain name. However, the `Alice-Bob` channel data from inside the smart contract, cannot have a subdomain name. Having uniquely identified, granular resources opens the way to creating both human and machine-readable protocols on top of Ethereum. It also provides a basis for protocols based on functional programming. - -This ERC proposes a set of separators which maintain their semantic meaning and provides a way to address any type of resource - from Ethereum addresses, to individual `struct` instances inside smart contracts. - -Imagine the following dType types: `SocialNetwork` and `Profile`, with related storage data about user profiles. One could access such a profile using an alias for the data content: `alice@socialnetwork.profile`. For a `PaymentChannel` type, Alice can refer to her channel with Bob with `alice-bob.paymentchannel`. -This alias system can be used off-chain, to replace the old DNS system with a deterministic and machine-readable way of displaying content, based on the dType type's metadata. - -## Specification - -The dType registry will provide domain and subdomain names for the resource type. Subdomains can be attributed recursively, to dType types which contain other complex types in their composition. - -We define an `Alias` registry contract, that keeps track of the human-readable identifiers for data resources, which exist in dType storage contracts. -Anyone can set an alias in the `Alias` registry, as long as the Ethereum address that signs the alias data has ownership on the resource, in the dType storage contract. Storage contract data ownership will be detailed in [EIP-2157](./eip-2157.md). An owner can update or delete an alias at any time. - -```solidity -interface Alias { - - event AliasSet(bytes32 dtypeIdentifier, bytes1 separator, string name, bytes32 indexed identifier); - - function setAlias(bytes32 dtypeIdentifier, bytes1 separator, string memory name, bytes32 identifier, bytes memory signature) external; - - function getAliased(bytes1 separator, string memory name) view external returns (bytes32 identifier); -} -``` - -- `dtypeIdentifier`: Type identifier from the dType registry, needed to ensure uniqueness of `name` for a dType type. `dtypeIdentifier` is checked to see if it exists in the dType registry. The dType registry also links the type's data storage contract, where the existence and ownership of the `identifier` is checked. -- `name`: user-defined human-readable name for the resource referenced by `identifier` -- `separator`: Character acting as a separator between the name and the rest of the alias. Allowed values: - - `.`: general domain separation, using root->leaf semantics. E.g. `domain.subdomain.leafsubdomain.resource` - - `@`: identifying actor-related data, such as user profiles, using leaf->root semantics. E.g. `alice@socialnetwork.profile` or `alice@dao@eth` - - `#`: identifying concepts, using root->leaf semantics. E.g. `topicX#postY` - - `/`: general resource path definition, using root->leaf semantics. E.g. `resourceRoot/resource` -- `identifier`: Resource identifier from a smart contract linked with dType -- `signature`: Alias owner signature on `dtypeIdentifier`, `identifier`, `name`, `separator`, `nonce`, `aliasAddress`, `chainId`. - - `nonce`: monotonically increasing counter, used to prevent replay attacks - - `aliasAddress`: Ethereum address of `Alias` contract - - `chainId`: chain on which the `Alias` contract is deployed, as detailed in [EIP-155](./eip-155.md), used to prevent replay attacks when updating the `identifier` for an alias. - -Content addressability can be done: -- using the `bytes32` identifiers directly, e.g. `0x0b5e76559822448f6243a6f76ac7864eba89c810084471bdee2a63429c92d2e7@0x9dbb9abe0c47484c5707699b3ceea23b1c2cca2ac72681256ab42ae01bd347da` -- using the human identifiers, e.g. `alice@socialnetwork` - -Both of the above examples will resolve to the same content. - - -## Rationale - -Current attempts to solve content addressability, such as [EIP-137](./eip-137.md), only target Ethereum accounts. These are based on inherited concepts from HTTP and DNS, which are not machine friendly. - -With [EIP-1900](./eip-1900.md) and [EIP-2157](./eip-2157.md), general content addressability can be achieved. dType provides type information and a reference to the smart contract where the type instances are stored. Additionally, Alias uses the semantic meaning of subdomain separators to have a [intuitive order rule](https://github.com/loredanacirstea/articles/blob/master/articles/Flexible_Alias_or_Why_ENS_is_Obsolete.md). - -Multiple aliases can be assigned to a single resource. Either by using a different `name` or by using a different `separator`. Each `separator` can have a specific standard for displaying and processing data, based on its semantic meaning. - -## Backwards Compatibility - -Will be added. - -## Test Cases - -Will be added. - -## Implementation - -An in-work implementation can be found at https://github.com/pipeos-one/dType/blob/master/contracts/contracts/Alias.sol. -This proposal will be updated with an appropriate implementation when consensus is reached on the specifications. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2193.md diff --git a/EIPS/eip-223.md b/EIPS/eip-223.md index d11b87b959b38a..1d5502516c8b16 100644 --- a/EIPS/eip-223.md +++ b/EIPS/eip-223.md @@ -1,391 +1 @@ ---- -eip: 223 -title: Token with transaction handling model -description: Token with transaction handling model designed to behave identical to native currency (ether) -author: Dexaran (@Dexaran) -discussions-to: https://ethereum-magicians.org/t/erc-223-token-standard/12894 -status: Final -type: Standards Track -category: ERC -created: 2017-05-03 ---- - -## Abstract - -The following describes an interface and logic for fungible tokens that supports a `tokenReceived` callback to notify contract recipients when tokens are received. This makes tokens behave identical to ether. - -## Motivation - -This token introduces a communication model for contracts that can be utilized to straighten the behavior of contracts that interact with such tokens. Specifically, this proposal: - -1. Informs receiving contracts of incoming token transfers, as opposed to [ERC-20](./eip-20.md) where the recipient of a token transfer gets no notification. -2. Is more gas-efficient when depositing tokens to contracts. -3. Allows for `_data` recording for financial transfers. - -## Specification - -Contracts intending to receive these tokens MUST implement `tokenReceived`. - -Token transfers to contracts not implementing `tokenReceived` as described below MUST revert. - -### Token contract - -#### Token Methods - -##### `totalSupply` - -```solidity -function totalSupply() view returns (uint256) -``` - -Returns the total supply of the token. The functionality of this method is identical to that of ERC-20. - -##### `name` - -```solidity -function name() view returns (string memory) -``` - -Returns the name of the token. The functionality of this method is identical to that of ERC-20. - -OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present. - -##### `symbol` - -```solidity -function symbol() view returns (string memory) -``` - -Returns the symbol of the token. The functionality of this method is identical to that of ERC-20. - -OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present. - -##### `decimals` - -```solidity -function decimals() view returns (uint8) -``` - -Returns the number of decimals of the token. The functionality of this method is identical to that of ERC-20. - -OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present. - -##### `standard` - -```solidity -function standard() view returns (string memory) -``` - -Returns the identifier of the standard. If the token is [ERC-223](./eip-20.md) then it must return `"223"`. - -OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present. - -##### `balanceOf` - -```solidity -function balanceOf(address _owner) view returns (uint256) -``` - -Returns the account balance of another account with address `_owner`. The functionality of this method is identical to that of ERC-20. - -##### `transfer(address, uint)` - -```solidity -function transfer(address _to, uint _value) returns (bool) -``` - -This function must transfer tokens, and if `_to` is a contract, it must call the `tokenReceived(address, uint256, bytes calldata)` function of `_to`. If the `tokenReceived` function is not implemented in `_to` (recipient contract), then the transaction must fail and the transfer of tokens must be reverted. -If `_to` is an externally owned address, then the transaction must be sent without executing `tokenReceived` in `_to`. - `_data` can be attached to this token transaction, but it requires more gas. `_data` can be empty. - -The `tokenReceived` function of `_to` MUST be called after all other operations to avoid re-entrancy attacks. - -NOTE: If `transfer` function is `payable` and ether was deposited then the amount of deposited ether MUST be delivered to `_to` address alongside tokens. If ether was sent alongside tokens in this way then ether MUST be delivered first, then token balances must be updated, then `tokenReceived` function MUST be called in `_to` if it is a contract. - -##### `transfer(address, uint, bytes)` - -```solidity -function transfer(address _to, uint _value, bytes calldata _data) returns (bool) -``` - -This function must transfer tokens and invoke the function `tokenReceived (address, uint256, bytes)` in `_to`, if `_to` is a contract. If the `tokenReceived` function is not implemented in `_to` (recipient contract), then the transaction must fail and the transfer of tokens must not occur. -If `_to` is an externally owned address (determined by the code size being zero), then the transaction must be sent without executing `tokenReceived` in `_to`. - `_data` can be attached to this token transaction, but it requires more gas. `_data` can be empty. - -NOTE: A possible way to check whether the `_to` is a contract or an address is to assemble the code of `_to`. If there is no code in `_to`, then this is an externally owned address, otherwise it's a contract. If `transfer` function is `payable` and ether was deposited then the amount of deposited ether MUST be delivered to `_to` address alongside tokens. - -The `tokenReceived` function of `_to` MUST be called after all other operations to avoid re-entrancy attacks. - -#### Events - -##### `Transfer` - -```solidity -event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes _data) -``` - -Triggered when tokens are transferred. Compatible with and similar to the ERC-20 `Transfer` event. - -### [ERC-223](./eip-223.md) Token Receiver - -#### Receiver Methods - -```solidity -function tokenReceived(address _from, uint _value, bytes calldata _data) returns (bytes4) -``` - -A function for handling token transfers, which is called from the token contract, when a token holder sends tokens. `_from` is the address of the sender of the token, `_value` is the amount of incoming tokens, and `_data` is attached data similar to `msg.data` of ether transactions. It works by analogy with the fallback function of Ether transactions and returns nothing. - -NOTE: `msg.sender` will be a token-contract inside the `tokenReceived` function. It may be important to filter which tokens were sent (by token-contract address). The token sender (the person who initiated the token transaction) will be `_from` inside the `tokenReceived` function. The `tokenReceived` function must return `0x8943ec02` after handling an incoming token transfer. The `tokenReceived` function call can be handled by the fallback function of the recipient contact (and in this case it may not return the magic value 0x8943ec02). - -IMPORTANT: This function must be named `tokenReceived` and take parameters `address`, `uint256`, `bytes` to match the function signature `0x8943ec02`. This function can be manually called by a EOA. - -## Rationale - -This standard introduces a communication model by enforcing the `transfer` to execute a handler function in the destination address. This is an important security consideration as it is required that the receiver explicitly implements the token handling function. In cases where the receiver does not implements such function the transfer MUST be reverted. - -This standard sticks to the push transaction model where the transfer of assets is initiated on the senders side and handled on the receivers side. As the result, ERC-223 transfers are more gas-efficient while dealing with depositing to contracts as ERC-223 tokens can be deposited with just one transaction while ERC-20 tokens require at least two calls (one for `approve` and the second that will invoke `transferFrom`). - -- [ERC-20](./eip-20.md) deposit: `approve` ~46 gas, `transferFrom` ~75K gas - -- ERC-223 deposit: `transfer` and handling on the receivers side ~54K gas - -This standard introduces the ability to correct user errors by allowing to handle ANY transactions on the recipients side and reject incorrect or improper transfers. This tokens utilize ONE transferring method for both types of interactions with contracts and externally owned addresses which can simplify the user experience and allow to avoid possible user mistakes. - -One downside of the commonly used [ERC-20](./eip-20.md) standard that ERC-223 is intended to solve is that [ERC-20](./eip-20.md) implements two methods of token transferring: (1) `transfer` function and (2) `approve + transferFrom` pattern. Transfer function of [ERC-20](./eip-20.md) standard does not notify the receiver and therefore if any tokens are sent to a contract with the `transfer` function then the receiver will not recognize this transfer and the tokens can become stuck in the receivers address without any possibility of recovering them. [ERC-20](./eip-20.md) standard places the burden of determining the transferring method on the user and if the incorrect method is chosen the user can lose the transferred tokens. ERC-223 automatically determines the transferring method, preventing the user from losing tokens due to chosing wrong method. - -ERC-223 is intended to simplify the interaction with contracts that are intended to work with tokens. ERC-223 utilizes a "deposit" pattern, similar to that of plain Ether. An ERC-223 deposit to a contract is a simple call of the `transfer` function. This is one transaction as opposed to two step process of `approve + transferFrom` depositing. - -This standard allows payloads to be attached to transactions using the `bytes calldata _data` parameter, which can encode a second function call in the destination address, similar to how `msg.data` does in an ether transaction, or allow for public logging on chain should it be necessary for financial transactions. - -## Backwards Compatibility - -The interface of this token is similar to that of ERC-20 and most functions serve the same purpose as their analogues in ERC-20. -`transfer(address, uint256, bytes calldata)` function is not backwards compatible with ERC-20 interface. - -ERC-20 tokens can be delivered to a non-contract address with `transfer` function. ERC-20 tokens can be deposited to a contract address with `approve` + `transferFrom` pattern. Depositing ERC-20 tokens to the contract address with `transfer` function will always result in token deposit not being recognized by the recipient contract. - -Here is an example of the contract code that handles ERC-20 token deposit. The following contract can accepts `tokenA` deposits. It is impossible to prevent deposits of non-tokenA to this contract. If tokenA is deposited with `transfer` function then it will result in a loss of tokens for the depositor because the balance of the user will be decreased in the contract of tokenA but the value of `deposits` variable in the `ERC20Receiver` will not be increased i.e. the deposit will not be credited. As of 5/9/2023 **$201M worth of 50 examined ERC-20 tokens are already lost** in this way on Ethereum mainnet. - -```solidity -contract ERC20Receiver -{ - address tokenA; - mapping (address => uint256) deposits; - function deposit(uint _value, address _token) public - { - require(_token == tokenA); - IERC20(_token).transferFrom(msg.sender, address(this), _value); - deposits[msg.sender] += _value; - } -} -``` - -ERC-223 tokens must be delivered to non-contract address or contract address in the same way with `transfer` function. - -Here is an example of the contract code that handles ERC-223 token deposit. The following contract can filter tokens and only accepts `tokenA`. Other ERC-223 tokens would be rejected. - -```solidity -contract ERC223Receiver -{ - address tokenA; - mapping (address => uint256) deposits; - function tokenReceived(address _from, uint _value, bytes memory _data) public returns (bytes4) - { - require(msg.sender == tokenA); - deposits[_from] += _value; - return 0x8943ec02; - } -} -``` - -## Security Considerations - -This token utilizes the model similar to plain ether behavior. Therefore replay issues must be taken into account. - -### Reference Implementation - -```solidity -pragma solidity ^0.8.19; - -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * This test is non-exhaustive, and there may be false-negatives: during the - * execution of a contract's constructor, its address will be reported as - * not containing a contract. - * - * > It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - */ - function isContract(address account) internal view returns (bool) { - // This method relies in 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; - } -} - -abstract contract IERC223Recipient { -/** - * @dev Standard ERC-223 receiving function that will handle incoming token transfers. - * - * @param _from Token sender address. - * @param _value Amount of tokens. - * @param _data Transaction metadata. - */ - function tokenReceived(address _from, uint _value, bytes memory _data) public virtual returns (bytes4); -} - -/** - * @title Reference implementation of the ERC223 standard token. - */ -contract ERC223Token { - - /** - * @dev Event that is fired on successful transfer. - */ - event Transfer(address indexed from, address indexed to, uint value, bytes data); - - string private _name; - string private _symbol; - uint8 private _decimals; - uint256 private _totalSupply; - - mapping(address => uint256) private balances; // List of user balances. - - /** - * @dev Sets the values for {name} and {symbol}, initializes {decimals} with - * a default value of 18. - * - * To select a different value for {decimals}, use {_setupDecimals}. - * - * All three of these values are immutable: they can only be set once during - * construction. - */ - - constructor(string memory new_name, string memory new_symbol, uint8 new_decimals) - { - _name = new_name; - _symbol = new_symbol; - _decimals = new_decimals; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view returns (string memory) - { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view returns (string memory) - { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5,05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the value {ERC223} uses, unless {_setupDecimals} is - * called. - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC223-balanceOf} and {IERC223-transfer}. - */ - function decimals() public view returns (uint8) - { - return _decimals; - } - - /** - * @dev See {IERC223-totalSupply}. - */ - function totalSupply() public view returns (uint256) - { - return _totalSupply; - } - - /** - * @dev See {IERC223-standard}. - */ - function standard() public view returns (string memory) - { - return "223"; - } - - - /** - * @dev Returns balance of the `_owner`. - * - * @param _owner The address whose balance will be returned. - * @return balance Balance of the `_owner`. - */ - function balanceOf(address _owner) public view returns (uint256) - { - return balances[_owner]; - } - - /** - * @dev Transfer the specified amount of tokens to the specified address. - * Invokes the `tokenFallback` function if the recipient is a contract. - * The token transfer fails if the recipient is a contract - * but does not implement the `tokenFallback` function - * or the fallback function to receive funds. - * - * @param _to Receiver address. - * @param _value Amount of tokens that will be transferred. - * @param _data Transaction metadata. - */ - function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success) - { - // Standard function transfer similar to ERC20 transfer with no _data . - // Added due to backwards compatibility reasons . - balances[msg.sender] = balances[msg.sender] - _value; - balances[_to] = balances[_to] + _value; - if(Address.isContract(_to)) { - IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data); - } - emit Transfer(msg.sender, _to, _value, _data); - return true; - } - - /** - * @dev Transfer the specified amount of tokens to the specified address. - * This function works the same with the previous one - * but doesn't contain `_data` param. - * Added due to backwards compatibility reasons. - * - * @param _to Receiver address. - * @param _value Amount of tokens that will be transferred. - */ - function transfer(address _to, uint _value) public 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); - return true; - } -} -``` - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-223.md diff --git a/EIPS/eip-2266.md b/EIPS/eip-2266.md index 2489d320f18ec3..fc88c6fc6b2dc6 100644 --- a/EIPS/eip-2266.md +++ b/EIPS/eip-2266.md @@ -1,252 +1 @@ ---- -eip: 2266 -title: Atomic Swap-based American Call Option Contract Standard -author: Runchao Han , Haoyu Lin , Jiangshan Yu -discussions-to: https://github.com/ethereum/EIPs/issues/2266 -status: Last Call -type: Standards Track -category: ERC -created: 2019-08-17 -last-call-deadline: 2020-12-31 ---- - -## Simple Summary - -A standard for token contracts providing Atomic Swap-based American Call Option functionalities. - -## Abstract - -This standard provides functionality to make Atomic Swap-based American Call Option payment. The Atomic Swap protocol based on Hashed Time-Locked Contract (HTLC) [^1] has optionality [^2], and such optionality can be utilised to construct American Call Options without trusted third party. This standard defines the common way of implementing this protocol. In particular, this EIP defines technical terms, provides interfaces, and gives reference implementations of this protocol. - - -## Motivation - -Atomic Swap allows users to atomically exchange their tokens without trusted third parties while the HTLC is commonly used for the implementation. However, the HTLC-based Atomic Swap has optionality. More specifically, the swap initiator can choose to proceed or abort the swap for several hours, which gives him time for speculating according to the exchange rate. A discussion[^2] shows that the HTLC-based Atomic Swap is equivalent to an American Call Option in finance. On the other hand,thanks to such optionality, the HTLC-based Atomic Swap can be utilised to construct American Call Options without trusted third party. A paper[^3] proposes a secure Atomic-Swap-based American Call Option protocol on smart contracts. This protocol not only eliminates the arbitrage opportunity but also prevents any party from locking the other party's money maliciously. This EIP aims at providing the standard of implementing this protocol in existing token standards. - -## Specification - -The Atomic Swap-based American Call Option smart contract should follow the syntax and semantics of Ethereum smart contracts. - -### Definitions - -+ `initiator`: the party who publishes the advertisement of the swap. -+ `participant`: the party who agrees on the advertisement and participates in the swap with `initiator`. -+ `asset`: the amount of token(s) to be exchanged. -+ `premium`: the amount of token(s) that `initiator` pays to `participant` as the premium. -+ `redeem`: the action to claim the token from the other party. -+ `refund`: the action to claim the token from the party herself/himself, because of timelock expiration. -+ `secrect`: a random string chosen by `initiator` as the preimage of a hash. -+ `secrectHash`: a string equals to the hash of `secrect`, used for constructing HTLCs. -+ `timelock`: a timestamp representing the timelimit, before when the asset can be redeemed, and otherwise can only be refunded. - -### Storage Variables - -#### swap - -This mapping stores the metadata of the swap contracts, including the parties and tokens involved. Each contract uses different `secretHash`, and is distinguished by `secretHash`. - -```solidity -mapping(bytes32 => Swap) public swap; -``` - -#### initiatorAsset - -This mapping stores the detail of the asset initiators want to sell, including the amount, the timelock and the state. It is associated with the swap contract with the same `secretHash`. - -```solidity -mapping(bytes32 => InitiatorAsset) public initiatorAsset; -``` - -#### participantAsset - -This mapping stores the details of the asset participants want to sell, including the amount, the timelock and the state. It is associated with the swap contract with the same `secretHash`. - -```solidity -mapping(bytes32 => ParticipantAsset) public participantAsset; -``` - -#### premiumAsset - -This mapping stores the details of the premium initiators attach in the swap contract, including the amount, the timelock and the state. It is associated with the swap contract with the same `secretHash`. - -```solidity -mapping(bytes32 => Premium) public premium; -``` - - -### Methods - -#### setup - -This function sets up the swap contract, including the both parties involved, the tokens to exchanged, and so on. - -```solidity -function setup(bytes32 secretHash, address payable initiator, address tokenA, address tokenB, uint256 initiatorAssetAmount, address payable participant, uint256 participantAssetAmount, uint256 premiumAmount) public payable -``` - -#### initiate - -The initiator invokes this function to fill and lock the token she/he wants to sell and join the contract. - -```solidity -function initiate(bytes32 secretHash, uint256 assetRefundTime) public payable -``` - -#### fillPremium - -The initiator invokes this function to fill and lock the premium. - -```solidity -function fillPremium(bytes32 secretHash, uint256 premiumRefundTime) public payable -``` - -#### participate - -The participant invokes this function to fill and lock the token she/he wants to sell and join the contract. - -```solidity -function participate(bytes32 secretHash, uint256 assetRefundTime) public payable -``` - -#### redeemAsset - -One of the parties invokes this function to get the token from the other party, by providing the preimage of the hash lock `secret`. - -```solidity -function redeemAsset(bytes32 secret, bytes32 secretHash) public -``` - -#### refundAsset - -One of the parties invokes this function to get the token back after the timelock expires. - -```solidity -function refundAsset(bytes32 secretHash) public -``` - -#### redeemPremium - -The participant invokes this function to get the premium. This can be invoked only if the participant has already invoked `participate` and the participant's token is redeemed or refunded. - -```solidity -function redeemPremium(bytes32 secretHash) public -``` - -#### refundPremium - -The initiator invokes this function to get the premium back after the timelock expires. - -```solidity -function refundPremium(bytes32 secretHash) public -``` - - -### Events - -#### SetUp - -This event indicates that one party has set up the contract using the function `setup()`. - -```solidity -event SetUp(bytes32 secretHash, address initiator, address participant, address tokenA, address tokenB, uint256 initiatorAssetAmount, uint256 participantAssetAmount, uint256 premiumAmount); -``` - -#### Initiated - -This event indicates that `initiator` has filled and locked the token to be exchanged using the function `initiate()`. - -```solidity -event Initiated(uint256 initiateTimestamp, bytes32 secretHash, address initiator, address participant, address initiatorAssetToken, uint256 initiatorAssetAmount, uint256 initiatorAssetRefundTimestamp); -``` - -#### Participated - -This event indicates that `participant` has filled and locked the token to be exchanged using the function `participate()`. - -```solidity -event Participated(uint256 participateTimestamp, bytes32 secretHash, address initiator, address participant, address participantAssetToken, uint256 participantAssetAmount, uint256 participantAssetRefundTimestamp); -``` - -#### PremiumFilled - -This event indicates that `initiator` has filled and locked `premium` using the function `fillPremium()`. - -```solidity -event PremiumFilled(uint256 fillPremiumTimestamp, bytes32 secretHash, address initiator, address participant, address premiumToken, uint256 premiumAmount, uint256 premiumRefundTimestamp); -``` - -#### InitiatorAssetRedeemed/ParticipantAssetRedeemed - -These two events indicate that `asset` has been redeemed by the other party before the timelock by providing `secret`. - -```solidity -event InitiatorAssetRedeemed(uint256 redeemTimestamp, bytes32 secretHash, bytes32 secret, address redeemer, address assetToken, uint256 amount); -``` - -```solidity -event ParticipantAssetRedeemed(uint256 redeemTimestamp, bytes32 secretHash, bytes32 secret, address redeemer, address assetToken, uint256 amount); -``` - -#### InitiatorAssetRefunded/ParticipantAssetRefunded - -These two events indicate that `asset` has been refunded by the original owner after the timelock expires. - -```solidity -event InitiatorAssetRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address assetToken, uint256 amount); -``` - -```solidity -event ParticipantAssetRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address assetToken, uint256 amount); -``` - -#### PremiumRedeemed - -This event indicates that `premium` has been redeemed by `participant`. This implies that `asset` is either redeemed by `initiator` if it can provide the preimage of `secrectHash` before `asset` timelock expires; or refunded by `participant` if `asset` timelock expires. - -```solidity -event PremiumRedeemed(uint256 redeemTimestamp,bytes32 secretHash,address redeemer,address token,uint256 amount); -``` - -#### PremiumRefunded - -This event indicates that `premium` has been refunded back to `initiator`, because of `participant` doesn't participate at all, by the time of `premium` timelock expires. - -```solidity -event PremiumRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address token, uint256 amount); -``` - -## Rationale - -+ To achieve the atomicity, HTLC is used. -+ The participant should decide whether to participate after the initiator locks the token and sets up the timelock. -+ The initiator should decide whether to proceed the swap (redeem the tokens from the participant and reveal the preimage of the hash lock), after the participant locks the tokens and sets up the time locks. -+ Premium is redeemable for the participant only if the participant participates in the swap and redeems the initiator's token before premium's timelock expires. -+ Premium is refundable for the initiator only if the initiator initiates but the participant does not participate in the swap at all. - - -## Security Considerations - -+ The `initiateTimestamp` should cover the whole swap process. -+ The participant should never participate before the premium has been deposited. - - -## Backwards Compatibility - -This proposal is fully backward compatible. Functionalities of existing standards will not be affected by this proposal, as it only provides additional features to them. - - -## Implementation - -Please visit [here](../assets/eip-2266/Example.sol) to find our example implementation. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - -## References - -[^1]: [Hash Time Locked Contracts](https://en.bitcoin.it/wiki/Hash_Time_Locked_Contracts) - -[^2]: [An Argument For Single-Asset Lightning Network](https://lists.linuxfoundation.org/pipermail/lightning-dev/2019-January/001798.html) - -[^3]: [On the optionality and fairness of Atomic Swaps](https://eprint.iacr.org/2019/896) +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2266.md diff --git a/EIPS/eip-2304.md b/EIPS/eip-2304.md index 16a550edad50c9..451a972831ded7 100644 --- a/EIPS/eip-2304.md +++ b/EIPS/eip-2304.md @@ -1,219 +1 @@ ---- -eip: 2304 -title: Multichain address resolution for ENS -author: Nick Johnson -type: Standards Track -category: ERC -status: Stagnant -created: 2019-09-09 -discussions-to: https://discuss.ens.domains/t/new-standard-proposal-ens-multicoin-support/1148 -requires: 137 ---- - -## Abstract - -This EIP introduces new overloads for the the `addr` field for ENS resolvers, which permit resolution of addresses for other blockchains via ENS. - -## Motivation - -With the increasing uptake of ENS by multi-coin wallets, wallet authors have requested the ability to resolve addresses for non-Ethereum chains inside ENS. This specification standardises a way to enter and retrieve these addresses in a cross-client fashion. - -## Specification - -A new accessor function for resolvers is specified: - -```solidity -function addr(bytes32 node, uint coinType) external view returns(bytes memory); -``` - -The EIP165 interface ID for this function is 0xf1cb7e06. - -When called on a resolver, this function must return the cryptocurrency address for the specified namehash and coin type. A zero-length string must be returned if the specified coin ID does not exist on the specified node. - -`coinType` is the cryptocurrency coin type index from [SLIP44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). - -The return value is the cryptocurency address in its native binary format. Detailed descriptions of the binary encodings for several popular chains are provided in the Address Encoding section below. - -A new event for resolvers is defined: - -```solidity -event AddressChanged(bytes32 indexed node, uint coinType, bytes newAddress); -``` - -Resolvers MUST emit this event on each change to the address for a name and coin type. - -### Recommended accessor functions - -The following function provides the recommended interface for changing the addresses stored for a node. Resolvers SHOULD implement this interface for setting addresses unless their needs dictate a different interface. - -```solidity -function setAddr(bytes32 node, uint coinType, bytes calldata addr); -``` - -`setAddr` adds or replaces the address for the given node and coin type. The parameters for this function are as per those described in `addr()` above. - -This function emits an `AddressChanged` event with the new address; see also the backwards compatibility section below for resolvers that also support `addr(bytes32)`. - -### Address Encoding - -In general, the native binary representation of the address should be used, without any checksum commonly used in the text representation. - -A table of encodings for common blockchains is provided, followed by a more detailed description of each format. In the table, 'encodings' lists the address encodings supported by that chain, along with any relevant parameters. Details of those address encodings are described in the following sections. - -| Cryptocurrency | Coin Type | Encoding | -| --- | --- | --- | -| Bitcoin | 0 | P2PKH(0x00), P2SH(0x05), SegWit('bc') | -| Litecoin | 2 | P2PKH(0x30), P2SH(0x32), P2SH(0x05), SegWit('ltc') | -| Dogecoin | 3 | P2PKH(0x1e), P2SH(0x16) | -| Monacoin | 22 | P2PKH(0x32), P2SH(0x05) | -| Ethereum | 60 | ChecksummedHex | -| Ethereum Classic | 61 | ChecksummedHex | -| Rootstock | 137 | ChecksummedHex(30) | -| Ripple | 144 | Ripple | -| Bitcoin Cash | 145 | P2PKH(0x00), P2SH(0x05), CashAddr | -| Binance | 714 | Bech32('bnb') | - -#### P2PKH(version) - -Pay to Public Key Hash addresses are [base58check](https://en.bitcoin.it/wiki/Base58Check_encoding) encoded. After decoding, the first byte is a version byte. For example, the Bitcoin address `1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa` base58check decodes to the 21 bytes `0062e907b15cbf27d5425399ebf6f0fb50ebb88f18`. - -P2PKH addresses have a version byte, followed by a 20 byte pubkey hash. Their canonical encoding is their scriptPubkey encoding (specified [here](https://en.bitcoin.it/wiki/Transaction#Types_of_Transaction)) is `OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG`. - -The above example address is thus encoded as the 25 bytes `76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac`. - -##### P2SH(version) - -P2SH addresses are base58check encoded in the same manner as P2PKH addresses. -P2SH addresses have a version, followed by a 20 byte script hash. Their scriptPubkey encoding (specified [here](https://en.bitcoin.it/wiki/Transaction#Pay-to-Script-Hash)) is `OP_HASH160 OP_EQUAL`. A Bitcoin address of `3Ai1JZ8pdJb2ksieUV8FsxSNVJCpoPi8W6` decodes to the 21 bytes `0562e907b15cbf27d5425399ebf6f0fb50ebb88f18` and is encoded as the 23 bytes `a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1887`. - -##### SegWit(hrp) - -SegWit addresses are encoded with [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). Bech32 addresses consist of a human-readable part - 'bc' for Bitcoin mainnet - and a machine readable part. For SegWit addresses, this decodes to a 'witness version', between 0 and 15, and a 'witness program', as defined in [BIP141](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). - -The scriptPubkey encoding for a bech32 address, as defined in BIP141, is `OP_n`, where `n` is the witness version, followed by a push of the witness program. Note this warning from BIP173: - -> Implementations should take special care when converting the address to a scriptPubkey, where witness version n is stored as OP_n. OP_0 is encoded as 0x00, but OP_1 through OP_16 are encoded as 0x51 though 0x60 (81 to 96 in decimal). If a bech32 address is converted to an incorrect scriptPubKey the result will likely be either unspendable or insecure. - -For example, the Bitcoin SegWit address `BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4` decodes to a version of `0` and a witness script of `751e76e8199196d454941c45d1b3a323f1433bd6`, and then encodes to a scriptPubkey of `0014751e76e8199196d454941c45d1b3a323f1433bd6`. - -#### ChecksummedHex(chainId?) - -To translate a text format checksummed hex address into binary format, simply remove the '0x' prefix and hex decode it. `0x314159265dD8dbb310642f98f50C066173C1259b` is hex-decoded and stored as the 20 bytes `314159265dd8dbb310642f98f50c066173c1259b`. - -A checksum format is specified by [EIP-55](./eip-55.md), and extended by [RSKIP60](https://github.com/rsksmart/RSKIPs/blob/master/IPs/RSKIP60.md), which specifies a means of including the chain ID in the checksum. The checksum on a text format address must be checked. Addresses with invalid checksums that are not all uppercase or all lowercase MUST be rejected with an error. Implementations may choose whether to accept non-checksummed addresses, but the authors recommend at least providing a warning to users in this situation. - -When encoding an address from binary to text, an EIP55/RSKIP60 checksum MUST be used - so the correct encoding of the above address for Ethereum is `0x314159265dD8dbb310642f98f50C066173C1259b`. - -#### Ripple - -Ripple addresses are encoded using a version of base58check with an alternative alphabet, described [here](https://xrpl.org/base58-encodings.html). Two types of ripple addresses are supported, 'r-addresses', and 'X-addresss'. r-addresses consist of a version byte followed by a 20 byte hash, while X-addresses consist of a version byte, a 20 byte hash, and a tag, specified [here](https://github.com/xrp-community/standards-drafts/issues/6). - -Both address types should be stored in ENS by performing ripple's version of base58check decoding and storing them directly (including version byte). For example, the ripple address `rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn` decodes to and is stored as `004b4e9c06f24296074f7bc48f92a97916c6dc5ea9`, while the address `X7qvLs7gSnNoKvZzNWUT2e8st17QPY64PPe7zriLNuJszeg` decodes to and is stored as `05444b4e9c06f24296074f7bc48f92a97916c6dc5ea9000000000000000000`. - -#### CashAddr - -Bitcoin Cash defines a new address format called 'CashAddr', specified [here](https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md). This uses a variant of bech32 encoding to encode and decode (non-segwit) Bitcoin Cash addresses, using a prefix of 'bitcoincash:'. A CashAddr should be decoded using this bech32 variant, then converted and stored based on its type (P2PKH or P2SH) as described in the relevant sections above. - -#### Bech32 - -[Bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) addresses consist of a human-readable part - for example, 'bnb' for Binance - and a machine readable part. The encoded data is simply the address, which can be converted to binary and stored directly. - -For example, the BNB address `bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2` decodes to the binary representation `40c2979694bbc961023d1d27be6fc4d21a9febe6`, which is stored directly in ENS. - -### Example - -An example implementation of a resolver that supports this EIP is provided here: - -```solidity -pragma solidity ^0.5.8; - -contract AddrResolver is ResolverBase { - bytes4 constant private ADDR_INTERFACE_ID = 0x3b3b57de; - bytes4 constant private ADDRESS_INTERFACE_ID = 0xf1cb7e06; - uint constant private COIN_TYPE_ETH = 60; - - event AddrChanged(bytes32 indexed node, address a); - event AddressChanged(bytes32 indexed node, uint coinType, bytes newAddress); - - mapping(bytes32=>mapping(uint=>bytes)) _addresses; - - /** - * Sets the address associated with an ENS node. - * May only be called by the owner of that node in the ENS registry. - * @param node The node to update. - * @param a The address to set. - */ - function setAddr(bytes32 node, address a) external authorised(node) { - setAddr(node, COIN_TYPE_ETH, addressToBytes(a)); - } - - /** - * Returns the address associated with an ENS node. - * @param node The ENS node to query. - * @return The associated address. - */ - function addr(bytes32 node) public view returns (address) { - bytes memory a = addr(node, COIN_TYPE_ETH); - if(a.length == 0) { - return address(0); - } - return bytesToAddress(a); - } - - function setAddr(bytes32 node, uint coinType, bytes memory a) public authorised(node) { - emit AddressChanged(node, coinType, a); - if(coinType == COIN_TYPE_ETH) { - emit AddrChanged(node, bytesToAddress(a)); - } - _addresses[node][coinType] = a; - } - - function addr(bytes32 node, uint coinType) public view returns(bytes memory) { - return _addresses[node][coinType]; - } - - function supportsInterface(bytes4 interfaceID) public pure returns(bool) { - return interfaceID == ADDR_INTERFACE_ID || interfaceID == ADDRESS_INTERFACE_ID || super.supportsInterface(interfaceID); - } -} -``` - -### Implementation - -An implementation of this interface is provided in the [ensdomains/resolvers](https://github.com/ensdomains/resolvers/) repository. - -## Backwards Compatibility - -If the resolver supports the `addr(bytes32)` interface defined in EIP137, the resolver MUST treat this as a special case of this new specification in the following ways: - - 1. The value returned by `addr(node)` from EIP137 should always match the value returned by `addr(node, 60)` (60 is the coin type ID for Ethereum). - 2. Anything that causes the `AddrChanged` event from EIP137 to be emitted must also emit an `AddressChanged` event from this EIP, with the `coinType` specified as 60, and vice-versa. - -## Tests - -The table below specifies test vectors for valid address encodings for each cryptocurrency described above. - -| Cryptocurrency | Coin Type | Text | Onchain (hex) | -| --- | --- | --- | --- | -| Bitcoin | 0 | `1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa` | `76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac` | -| | | `3Ai1JZ8pdJb2ksieUV8FsxSNVJCpoPi8W6` | `a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1887` | -| | | `BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4` | `0014751e76e8199196d454941c45d1b3a323f1433bd6` | -| Litecoin | 2 | `LaMT348PWRnrqeeWArpwQPbuanpXDZGEUz` | `76a914a5f4d12ce3685781b227c1f39548ddef429e978388ac` | -| | | `MQMcJhpWHYVeQArcZR3sBgyPZxxRtnH441` | `a914b48297bff5dadecc5f36145cec6a5f20d57c8f9b87` | -| | | `ltc1qdp7p2rpx4a2f80h7a4crvppczgg4egmv5c78w8` | `0014687c150c26af5493befeed7036043812115ca36c` | -| Dogecoin | 3 | `DBXu2kgc3xtvCUWFcxFE3r9hEYgmuaaCyD` | `76a9144620b70031f0e9437e374a2100934fba4911046088ac` | -| | | `AF8ekvSf6eiSBRspJjnfzK6d1EM6pnPq3G` | `a914f8f5d99a9fc21aa676e74d15e7b8134557615bda87` | -| Monacoin | 22 | `MHxgS2XMXjeJ4if2PRRbWYcdwZPWfdwaDT` | `76a9146e5bb7226a337fe8307b4192ae5c3fab9fa9edf588ac` | -| Ethereum | 60 | `0x314159265dD8dbb310642f98f50C066173C1259b` | `314159265dd8dbb310642f98f50c066173c1259b` | -| Ethereum Classic | 61 | `0x314159265dD8dbb310642f98f50C066173C1259b` | `314159265dd8dbb310642f98f50c066173c1259b` | -| Rootstock | 137 | `0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD` | `5aaeb6053f3e94c9b9a09f33669435e7ef1beaed` | -| Ripple | 144 | `rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn` | `004b4e9c06f24296074f7bc48f92a97916c6dc5ea9` | -| | | `X7qvLs7gSnNoKvZzNWUT2e8st17QPY64PPe7zriLNuJszeg` | `05444b4e9c06f24296074f7bc48f92a97916c6dc5ea9000000000000000000` | -| Bitcoin Cash | 145 | `1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu` | `76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac` | -| | | `bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a` | `76a91476a04053bda0a88bda5177b86a15c3b29f55987388ac` | -| | | `3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC` | `a91476a04053bda0a88bda5177b86a15c3b29f55987387` | -| | | `bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq` | `a91476a04053bda0a88bda5177b86a15c3b29f55987387` | -| Binance | 714 | `bnb1grpf0955h0ykzq3ar5nmum7y6gdfl6lxfn46h2` | `40c2979694bbc961023d1d27be6fc4d21a9febe6` | - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2304.md diff --git a/EIPS/eip-2309.md b/EIPS/eip-2309.md index a98b8e0daae20e..a0a47ccf291cbe 100644 --- a/EIPS/eip-2309.md +++ b/EIPS/eip-2309.md @@ -1,121 +1 @@ ---- -eip: 2309 -title: ERC-721 Consecutive Transfer Extension -author: Sean Papanikolas (@pizzarob) -discussions-to: https://github.com/ethereum/EIPs/issues/2309 -status: Final -type: Standards Track -category: ERC -created: 2019-10-08 -requires: 721 ---- - -## Simple Summary - -A standardized event emitted when creating/transferring one, or many non-fungible tokens using consecutive token identifiers. - -## Abstract - -The optional ERC-721 Consecutive Transfer Extension provides a standardized event which could be emitted during the creation/transfer of one, or many non-fungible tokens. This standard does not set the expectation of how you might create/transfer many tokens it is only concerned with the event emitted after the creation, or transfer of ownership of these tokens. This extension assumes that token identifiers are in consecutive order. - -## Motivation - -This extension provides even more scalibility of the [ERC-721 specification](./eip-721.md). It is possible to create, transfer, and burn 2^256 non-fungible tokens in one transaction. However, it is not possible to emit that many `Transfer` events in one transaction. The `Transfer` event is part of the original specification which states: - -> This emits when ownership of any NFT changes by any mechanism. -> This event emits when NFTs are created (`from` == 0) and destroyed -> (`to` == 0). Exception: during contract creation, any number of NFTs -> may be created and assigned without emitting Transfer. At the time of -> any transfer, the approved address for that NFT (if any) is reset to none. - -This allows for the original `Transfer` event to be emitted for one token at a time, which in turn gives us O(n) time complexity. Minting one billion NFTs can be done in one transaction using efficient data structures, but in order to emit the `Transfer` event - according to the original spec - one would need a loop with one billion iterations which is bound to run out of gas, or exceed transaction timeout limits. This cannot be accomplished with the current spec. This extension solves that problem. - -Many decentralized marketplaces and block explorers utilize the `Transfer` event as a way to determine which NFTs an address owns. The Consecutive Transfer Extension provides a standard mechanism for these platforms to use to determine ownership of many tokens. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL -NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and -"OPTIONAL" in this document are to be interpreted as described in -RFC 2119. - -**ERC-721 compliant contracts MAY implement this Consecutive Transfer Extension to provide a standard event to be emitted at the time of creation, burn, or transfer of one or many consecutive tokens** - -The address executing the transaction **MUST** own all the tokens within the range of `fromTokenId` and `toTokenId`, or **MUST** be an approved operator to act on the owners behalf. - -The `fromTokenId` and `toTokenId` **MUST** be a consecutive range of tokens IDs. - -The `fromTokenId`, `fromAddress`, and `toAddress` **MUST** be indexed parameters - -The `toTokenId` **MUST NOT** be an indexed parameter - -When minting/creating tokens, the `fromAddress` argument **MUST** be set to `0x0` (i.e. zero address). - -When burning/destroying tokens, the `toAddress` argument **MUST** be set to `0x0` (i.e. zero address). - -When emitting the ConsecutiveTransfer event the Transfer event **MUST NOT** be emitted - -Contracts that implement the `ConsecutiveTransfer` event **MAY** still use the original `Transfer` event, however when emitting the `ConsecutiveTransfer` event the `Transfer` event **MUST NOT** be emitted. - -```solidity - event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed fromAddress, address indexed toAddress); -``` - -### Examples - -The `ConsecutiveTransfer` event can be used for a single token as well as many tokens: - -**Single token creation** - -`emit ConsecutiveTransfer(1, 1, address(0), toAddress);` - -**Batch token creation** - -`emit ConsecutiveTransfer(1, 100000, address(0), toAddress);` - -**Batch token transfer** - -`emit ConsecutiveTransfer(1, 100000, fromAddress, toAddress);` - -**Burn** - -`emit ConsecutiveTransfer(1, 100000, from, address(0));` - - -## Rationale - -Standardizing the `ConsecutiveTransfer` event gives decentralized platforms a standard way of determining ownership of large quantities of non-fungible tokens without the need to support a new token standard. There are many ways in which the batch creation and transfer of NFTs can be implemented. The Consecutive Transfer Extension allows contract creators to implement batch creation, transfer, and burn methods however they see fit, but provides a standardized event in which all implementations can use. By specifying a range of consecutive token identifiers we can easily cover the transfer, or creation of 2^(256) tokens and decentralized platforms can react accordingly. - -Take this example. I sell magical fruit and have a farm with 10,000 magical fruit trees each with different fruit and 1,000 new trees every few years. I want to turn each tree into a non-fungible token that people can own. Each person that owns one of my non-fungible tree tokens will receive a quarterly percentage of each harvest from that tree. The problem is that I would need to create and transfer each of these tokens individually - which will cost me a lot of time and money and frankly would keep me from doing this. - -With this extension I would be able to to mint my initial 10,000 tree tokens in one transaction. I would be able to quickly and cheaply mint my additional 1,000 tree tokens when a new batch is planted. I would then be able to transfer all of the 10,000+ tree tokens to a special smart contract that keeps track of the selling and distribution of funds in one transaction all while adhering to a specified standard. - -**Rationale to have a single event that covers minting, burning, and transferring** - -The `ConsecutiveTransfer` event can be used to cover minting, burning, and transferring events. While there may have been confusion in the beginning adhering to transfer to/from "0" pattern this is mitigated by checking for the `ConsecutiveTransfer` topic and verifying the emitting contract supports the ERC-721 interface by using the ERC-165 standard. - -**Indexed event parameters** - -Events in Solidity can have up to three indexed parameters which will make it possible to filter for specific values of indexed arguments. This standard sets the `fromAddress`, `toAddress`, and `fromTokenId` as the indexed parameters. The `toTokenId` can be retrieved from the data part of the log. The reason for this is that more often than not one may be searching for events to learn about the history of ownership for a given address. The `fromTokenId` can then be retrieved along with the other two indexed parameters for simplicity. Then one only needs to decode the log data which is ensured to be the `toTokenId`. - -**Rationale to not emit `Transfer` when `ConsecutiveTransfer` is also emitted** - -This can lead to bugs and unnecessary complex logic for platforms using these events to track token ownership. When transferring a single token it is acceptable to emit the original `Transfer` event, but the `ConsecutiveTransfer` event should not be emitted during the same transaction and vice-versa. - -**Comparing 2309 and 1155** - -As the NFT market continues to grow so does the need for the ability to scale the smart contracts. Users need to be able to do things like mint a massive amount of tokens at one time, transfer a massive amount of tokens, and be able to track ownership of all these assets. We need to do this in a way that is cost effective and doesn’t fail under the confines of the Ethereum blockchain. As millions of tokens are minted we need contracts with the ability to scale. - -[ERC-1155](./eip-1155.md) was created and added as a standard in 2019 to try to solve these problems, but it falls short when it comes to minting massive amounts of unique tokens in a cost-effective way. With ERC-1155 it’s either going to cost hundreds (or thousands) of dollars or it’s going to run out of gas. ERC-1155 works well when minting many semi-fungible tokens but falls short when minting many unique tokens. Using the 2309 standard you could mint millions of blank NFTs upfront and update the metadata for each one in a cost effective way. - - -## Backwards Compatibility - -This extension was written to allow for the smallest change possible to the original ERC-721 spec while still providing a mechanism to track the creation, transfer, and deletion of a massive amount of tokens. While it is a minimal change the effects on platforms that only use the original `Transfer` event to index token ownership would be severe. They would not be properly recording token ownership information that could be known by listening for the `ConsecutiveTransfer` event. For platforms that wish to support the `ConsecutiveTransfer` event it would be best to support both the original `Transfer` event and the `ConsecutiveTransfer` event to track token ownership. - -## Security Considerations -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2309.md diff --git a/EIPS/eip-2333.md b/EIPS/eip-2333.md index 095c82a874cb6b..0a472b4329d7d9 100644 --- a/EIPS/eip-2333.md +++ b/EIPS/eip-2333.md @@ -1,816 +1 @@ ---- -eip: 2333 -title: BLS12-381 Key Generation -author: Carl Beekhuizen -discussions-to: https://github.com/ethereum/EIPs/issues/2337 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-09-30 ---- - -## Simple Summary - -This EIP is a method based on a tree structure for deriving BLS private keys from a single source of entropy while providing a post-quantum cryptographic fallback for each key. - -## Abstract - -This standard is a method for deriving a tree-hierarchy of BLS12-381 keys based on an entropy seed. Starting with the aforementioned seed, a tree of keys is built out using only the parent node's private key and the index of the desired child. This allows for a practically limitless number of keys to be derived for many different purposes while only requiring knowledge of a single ancestor key in the tree. This allows for keys, or families thereof, to be provisioned for different purposes by further standards. - -In addition to the above, this method of deriving keys provides an emergency backup signature scheme that is resistant to quantum computers for in the event that BLS12-381 is ever deemed insecure. - -## A note on purpose - -This specification is designed not only to be an Ethereum 2.0 standard, but one that is adopted by the wider community who have adopted [BLS signatures over BLS12-381](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/). It is therefore important also to consider the needs of the wider industry along with those specific to Ethereum. As a part of these considerations, it is the intention of the author that this standard eventually migrate to a more neutral repository in the future. - -## Motivation - -### Deficiencies of the existing mechanism - -The curve BLS12-381 used for BLS signatures within Ethereum 2.0 (alongside many other projects) mandates a new key derivation scheme. The most commonly used scheme for key derivation within Ethereum 1.x is [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) (also known as HD derivation) which deems keys greater than the curve order invalid. Based on the order of the private key subgroup of BLS12-381 and the size of the entropy utilised, more than 54% of keys generated by BIP32 would be invalid. (secp256k1 keys derived by BIP32 are invalid with probability less than 1 in 2-127.) - -### Establishing a multi-chain standard early on - -By establishing a standard before the first users begin to generate their keys, the hope is that a single standard is highly pervasive and therefore can be assumed to be the method by which the majority of keys are provided. This is valuable for two reasons, firstly in order for a post-quantum backup mechanism to be effective, there needs to be an enshrined mechanism whereby users can switch to a post-quantum signature scheme with pre-shared public keys (something this EIP provides at 0 extra storage cost). Secondly, this unifies the inter- and intra-chain ecosystem by having common tooling ideally allowing users to switch between key-management systems. - -### A post-quantum backup - -This key derivation scheme has a Lamport key pair which is generated as a intermediate step in the key generation process. This key pair can be used to provide a Lamport signature which is a useful backup in the event of BLS12-381 no longer being considered secure (in the event of quantum computing making a sudden advancement, for example). The idea is the Lamport signature will act as a bridge to a new signature scheme which is deemed to be secure. - -## Specification - -### Version - -Due to the evolving BLS signatures CFRG draft (currently v4), the `KeyGen` function was updated, meaning that `hkdf_mod_r` no longer reflected what appeared in the BLS standard. This EIP was updated on the 17th of September 2020 to reflect this new method for deriving keys, **if you are implementing this EIP, please make sure your version is up to date.** - -### Specification - -Keys are defined in terms of a tree structure where a key is determined by the tree's seed and a tree path. This is very useful as one can start with a single source of entropy and build out a practically unlimited number of keys. The specification can be broken into two sub-components: generating the master key, and constructing a child key from its parent. The master key is used as the root of the tree and then the tree is built in layers on top of this root. - -### The Tree Structure - -The key tree is defined purely through the relationship between a child-node and its ancestors. Starting with the root of the tree, the *master key*, a child node can be derived by knowing the parent's private key and the index of the child. The tree is broken up into depths which are indicated by `/` and the master node is described as `m`. The first child of the master node is therefore described as `m / 0` and `m / 0`'s siblings are `m / i` for all `0 <= i < 2**32`. - -```text - [m / 0] - [m / 0 / 0] - / \ - / [m / 0 / 1] -[m] - [m / 1] - \ - ... - [m / i] -``` - -### Key derivation - -Every key generated via the key derivation process derives a child key via a set of intermediate Lamport keys. The idea behind the Lamport keys is to provide a post-quantum backup in case BLS12-381 is no longer deemed secure. At a high level, the key derivation process works by using the parent node's privkey as an entropy source for the Lamport private keys which are then hashed together into a compressed Lamport public key, this public key is then hashed into BLS12-381's private key group. - -#### `IKM_to_lamport_SK` - -##### Inputs - -* `IKM`, a secret octet string -* `salt`, an octet string - -##### Outputs - -* `lamport_SK`, an array of 255 32-octet strings - -##### Definitions - -* `HKDF-Extract` is as defined in [RFC5869](https://tools.ietf.org/html/rfc5869), instantiated with SHA256 -* `HKDF-Expand` is as defined in [RFC5869](https://tools.ietf.org/html/rfc5869), instantiated with SHA256 -* `K = 32` is the digest size (in octets) of the hash function (SHA256) -* `L = K * 255` is the HKDF output size (in octets) -* `""` is the empty string -* `bytes_split` is a function takes in an octet string and splits it into `K`-byte chunks which are returned as an array - -##### Procedure - -``` text -0. PRK = HKDF-Extract(salt, IKM) -1. OKM = HKDF-Expand(PRK, "" , L) -2. lamport_SK = bytes_split(OKM, K) -3. return lamport_SK -``` - -#### `parent_SK_to_lamport_PK` - -##### Inputs - -* `parent_SK`, the BLS Secret Key of the parent node -* `index`, the index of the desired child node, an integer `0 <= index < 2^32` - -##### Outputs - -* `lamport_PK`, the compressed lamport PK, a 32 octet string - -##### Definitions - -* `I2OSP` is as defined in [RFC3447](https://ietf.org/rfc/rfc3447.txt) (Big endian decoding) -* `flip_bits` is a function that returns the bitwise negation of its input -* `""` is the empty string -* `a | b` is the concatenation of `a` with `b` - -##### Procedure - -```text -0. salt = I2OSP(index, 4) -1. IKM = I2OSP(parent_SK, 32) -2. lamport_0 = IKM_to_lamport_SK(IKM, salt) -3. not_IKM = flip_bits(IKM) -4. lamport_1 = IKM_to_lamport_SK(not_IKM, salt) -5. lamport_PK = "" -6. for i in 1, .., 255 - lamport_PK = lamport_PK | SHA256(lamport_0[i]) -7. for i in 1, .., 255 - lamport_PK = lamport_PK | SHA256(lamport_1[i]) -8. compressed_lamport_PK = SHA256(lamport_PK) -9. return compressed_lamport_PK -``` - -**Note:** The indexing, `i`, in the above procedure iterates from 1 to 255 (inclusive). This is due to the limit to which HKDF can stretch the input bytes (255 times the length of the input bytes). The result of this is that the security of the lamport-backup signature is \*only\* 127.5 bit. - -#### `HKDF_mod_r` - -`hkdf_mod_r()` is used to hash 32 random bytes into the subgroup of the BLS12-381 private keys. - -##### Inputs - -* `IKM`, a secret octet string >= 256 bits in length -* `key_info`, an optional octet string (default=`""`, the empty string) - -##### Outputs - -* `SK`, the corresponding secret key, an integer 0 <= SK < r. - -##### Definitions - -* `HKDF-Extract` is as defined in RFC5869, instantiated with hash H. -* `HKDF-Expand` is as defined in RFC5869, instantiated with hash H. -* `L` is the integer given by `ceil((3 * ceil(log2(r))) / 16)`.(`L=48`) -* `"BLS-SIG-KEYGEN-SALT-"` is an ASCII string comprising 20 octets. -* `OS2IP` is as defined in [RFC3447](https://ietf.org/rfc/rfc3447.txt) (Big endian encoding) -* `I2OSP` is as defined in [RFC3447](https://ietf.org/rfc/rfc3447.txt) (Big endian decoding) -* `r` is the order of the BLS 12-381 curve defined in [the v4 draft IETF BLS signature scheme standard](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04) `r=52435875175126190479447740508185965837690552500527637822603658699938581184513` - -##### Procedure - -```text -1. salt = "BLS-SIG-KEYGEN-SALT-" -2. SK = 0 -3. while SK == 0: -4. salt = H(salt) -5. PRK = HKDF-Extract(salt, IKM || I2OSP(0, 1)) -6. OKM = HKDF-Expand(PRK, key_info || I2OSP(L, 2), L) -7. SK = OS2IP(OKM) mod r -8. return SK -``` - -### `derive_child_SK` - -The child key derivation function takes in the parent's private key and the index of the child and returns the child private key. - -##### Inputs - -* `parent_SK`, the secret key of the parent node, a big endian encoded integer -* `index`, the index of the desired child node, an integer `0 <= index < 2^32` - -##### Outputs - -* `child_SK`, the secret key of the child node, a big endian encoded integer - -##### Procedure - -```text -0. compressed_lamport_PK = parent_SK_to_lamport_PK(parent_SK, index) -1. SK = HKDF_mod_r(compressed_lamport_PK) -2. return SK -``` - -### `derive_master_SK` - -The child key derivation function takes in the parent's private key and the index of the child and returns the child private key. The seed should ideally be derived from a mnemonic, with the intention being that [BIP39 mnemonics](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki), with the associated [mnemonic_to_seed method](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki#from-mnemonic-to-seed) be used. - -##### Inputs - -* `seed`, the source entropy for the entire tree, a octet string >= 256 bits in length - -##### Outputs - -* `SK`, the secret key of master node within the tree, a big endian encoded integer - -##### Procedure - -```text -0. SK = HKDF_mod_r(seed) -1. return SK -``` - -## Rationale - -### Lamport signatures - -Lamport signatures are used as the backup mechanism because of their relative simplicity for a post-quantum signature scheme. Lamport signatures are very easy both to explain and implement as the sole cryptographic dependency is a secure hash function. This is important as it minimises the complexity of implementing this standard as well as the compute time for deriving a key. Lamport signatures have very large key sizes which make them impractical for many use cases, but this is not deemed to be an issue in this case as this scheme is only meant to be a once-off event to migrate to a new scheme. - -Revealing the associated Lamport public key for a corresponding BLS key is done by verifying that the Lamport public key is the pre-image of the corresponding BLS private key (which in turn is verified against the BLS public key). This means that using a key's Lamport signature reveals the BLS private key rendering the BLS key pair unsafe. This has the upside of not requiring additional storage space for backup keys alongside BLS keys but does require that the Lamport signatures be used once and that the BLS key is no longer trusted after that point. - -The Lamport signatures used within this scheme have 255 bits worth of security, not 256. This is done because HKDF-SHA256, the mechanism used to stretch a key's entropy, has a length-limit of `255 * hash_function_digest_size`. The 1-bit reduction in security is deemed preferable over increasing the complexity of the entropy stretching mechanism. - -### SHA256 - -SHA256 is used as the hash function throughout this standard as it is the hash function chosen by the [IETF BLS signature proposed standard](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/). Using a single hash function for everything decreases the number of cryptographic primitives required to implement the entire BLS standardised key-stack while reducing the surface for flaws in the overall system. - -### `hkdf_mod_r()` - -The function `hkdf_mod_r()` in this standard is the same as the `KeyGen` function described in the [proposed standard](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/) and therefore the private key obtained from `KeyGen` is equal to that obtained from `hkdf_mod_r` for the same seed bytes. This means that common engineering can be done when implementing this function. Additionally because of its inclusion in an IETF standard, it has had much scrutiny by many cryptographers and cryptanalysts, thereby lending credence to its safety as a key derivation mechanism. - -While `hkdf_mod_r()` has modulo bias, the magnitude of this bias is minuscule (the output size of HKDF is set to 48 bytes which is greater 2128 time larger than the curve order). This bias is deemed acceptable in light of the simplicity of the constant time scheme. - -### Only using hardened keys - -Widely accepted standards that existed before this one ([BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) and [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)) utilise the notion of hardened and non-hardened keys whereas this specification only offers the former. Non-hardened keys are primarily useful in a UTXO system in which having one's balance spilt amongst many accounts does not present much additionally complexity, but such keys are much less useful outside of this context. Further complicating matters is the problem of deriving non-hardened keys using a post-quantum signature scheme as non-hardened keys are made possible by the very group arithmetic quantum computers gain an advantage over. - -## Backwards Compatibility - -There are no major backwards compatibility issues brought upon by this EIP as it is not designed for use within Ethereum 1.0 as it currently stands. That said, this standard is not compatible with BIP32/ BIP44 style paths as paths specified by these systems make use of non-hardened keys, something that does not exist within this standard. - -## Test Cases - -### Test Case 0 - -```text -seed = 0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04 -master_SK = 6083874454709270928345386274498605044986640685124978867557563392430687146096 -child_index = 0 -child_SK = 20397789859736650942317412262472558107875392172444076792671091975210932703118 -``` - -This test case can be extended to test the entire mnemonic-to-`child_SK` stack, assuming [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) is used as the mnemonic generation mechanism. Using the following parameters, the above seed can be calculated: - -```test -mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" -passphrase = "TREZOR" -``` - -This test case can be extended to test the entire `mnemonic-to -child_SK` stack, assuming [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) is used as the mnemonic generation mechanism. Using the following parameters, the above seed can be calculated: - -```text -mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" -passphrase = "TREZOR" -``` - -### Test Case 1 - -```text -seed = 0x3141592653589793238462643383279502884197169399375105820974944592 -master_SK = 29757020647961307431480504535336562678282505419141012933316116377660817309383 -child_index = 3141592653 -child_SK = 25457201688850691947727629385191704516744796114925897962676248250929345014287 -``` - -### Test Case 2 - -```text -seed = 0x0099FF991111002299DD7744EE3355BBDD8844115566CC55663355668888CC00 -master_SK = 27580842291869792442942448775674722299803720648445448686099262467207037398656 -child_index = 4294967295 -child_SK = 29358610794459428860402234341874281240803786294062035874021252734817515685787 -``` - -### Test Case 3 - -```text -seed = 0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 -master_SK = 19022158461524446591288038168518313374041767046816487870552872741050760015818 -child_index = 42 -child_SK = 31372231650479070279774297061823572166496564838472787488249775572789064611981 -``` - -### Test Vector with Intermediate values - -```text -seed = 0xc55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04 -master_SK = 6083874454709270928345386274498605044986640685124978867557563392430687146096 -child_index = 0 -lamport_0 = [0xe345d0ad7be270737de05cf036f688f385d5f99c7fddb054837658bdd2ebd519, -0x65050bd4db9c77c051f67dcc801bf1cdf33d81131e608505bb3e4523868eb76c, -0xc4f8e8d251fbdaed41bdd9c135b9ed5f83a614f49c38fffad67775a16575645a, -0x638ad0feace7567255120a4165a687829ca97e0205108b8b73a204fba6a66faa, -0xb29f95f64d0fcd0f45f265f15ff7209106ab5f5ce6a566eaa5b4a6f733139936, -0xbcfbdd744c391229f340f02c4f2d092b28fe9f1201d4253b9045838dd341a6bf, -0x8b9cf3531bfcf0e4acbfd4d7b4ed614fa2be7f81e9f4eaef53bedb509d0b186f, -0xb32fcc5c4e2a95fb674fa629f3e2e7d85335f6a4eafe7f0e6bb83246a7eced5f, -0xb4fe80f7ac23065e30c3398623b2761ac443902616e67ce55649aaa685d769ce, -0xb99354f04cfe5f393193c699b8a93e5e11e6be40ec16f04c739d9b58c1f55bf3, -0x93963f58802099ededb7843219efc66a097fab997c1501f8c7491991c780f169, -0x430f3b027dbe9bd6136c0f0524a0848dad67b253a11a0e4301b44074ebf82894, -0xd635c39b4a40ad8a54d9d49fc8111bd9d11fb65c3b30d8d3eaef7d7556aac805, -0x1f7253a6474cf0b2c05b02a7e91269137acddedcb548144821f9a90b10eccbab, -0x6e3bdb270b00e7b6eb8b044dbfae07b51ea7806e0d24218c59a807a7fd099c18, -0x895488ad2169d8eaae332ce5b0fe1e60ffab70e62e1cb15a2a1487544af0a6e8, -0x32d45a99d458c90e173a3087ea3661ab62d429b285089e92806a9663ba825342, -0xc15c52106c3177f5848a173076a20d46600ca65958a1e3c7d45a593aaa9670ed, -0xd8180c550fbe4cd6d5b676ff75e0728729d8e28a3b521d56152594ac6959d563, -0x58fe153fac8f4213aaf175e458435e06304548024bcb845844212c774bdffb2a, -0x10fff610a50f4bee5c978f512efa6ab4fafacb65929606951ba5b93eeb617b5a, -0x78ac9819799b52eba329f13dd52cf0f6148a80bf04f93341814c4b47bb4aa5ec, -0xa5c3339caa433fc11e74d1765bec577a13b054381a44b23c2482e750696876a9, -0x9f716640ab5cdc2a5eb016235cddca2dc41fa4ec5acd7e58af628dade99ec376, -0x2544364320e67577c4fed8c7c7c839deed93c24076d5343c5b8faca4cc6dc2d8, -0x62553e782541f822c589796be5d5c83bfc814819100b2be0710b246f5aa7149c, -0x229fb761c46c04b22ba5479f2696be0f936fded68d54dd74bcd736b8ba512afb, -0x0af23996a65b98a0ebaf19f3ec0b3ef20177d1bfd6eb958b3bd36e0bdbe04c8c, -0x6f0954f9deab52fd4c8d2daba69f73a80dea143dd49d9705c98db3d653adf98c, -0xfa9221dd8823919a95b35196c1faeb59713735827f3e84298c25c83ac700c480, -0x70c428e3ff9e5e3cda92d6bb85018fb89475c19f526461cca7cda64ebb2ff544, -0xdcaac3413e22314f0f402f8058a719b62966b3a7429f890d947be952f2e314ba, -0xb6b383cb5ec25afa701234824491916bfe6b09d28cf88185637e2367f0cf6edc, -0x7b0d91488fc916aba3e9cb61a5a5645b9def3b02e4884603542f679f602afb8d, -0xe9c20abca284acfde70c59584b9852b85c52fa7c263bb981389ff8d638429cd7, -0x838524f798daee6507652877feb9597f5c47e9bb5f9aa52a35fb6fff796813b9, -0xbe1ca18faf9bf322474fad1b3d9b4f1bc76ae9076e38e6dd2b16e2faf487742b, -0xbf02d70f1a8519343a16d24bade7f7222912fd57fe4f739f367dfd99d0337e8e, -0xc979eb67c107ff7ab257d1c0f4871adf327a4f2a69e01c42828ea27407caf058, -0xf769123d3a3f19eb7b5c3fd4f467a042944a7c5ff8834cebe427f47dbd71460c, -0xaefc8edc23257e1168a35999fe3832bcbc25053888cc89c38667482d6748095b, -0x8ff399f364d3a2428b1c92213e4fdc5341e7998007da46a5a2f671929b42aaab, -0xcf2a3d9e6963b24c5001fbba1e5ae7f45dd6cf520fd24861f745552db86bab48, -0xb380e272d7f3091e5c887fa2e7c690c67d59f4d95f8376d150e555da8c738559, -0xc006a749b091d91204dbb64f59059d284899de5986a7f84f8877afd5e0e4c253, -0x818d8bb9b7da2dafa2ef059f91975e7b6257f5e199d217320de0a576f020de5c, -0x7aabf4a1297d2e550a2ee20acb44c1033569e51b6ec09d95b22a8d131e30fd32, -0xdd01c80964a5d682418a616fb10810647c9425d150df643c8ddbbe1bfb2768b7, -0x1e2354e1d97d1b06eb6cfe9b3e611e8d75b5c57a444523e28a8f72a767eff115, -0x989c9a649dca0580256113e49ea0dd232bbfd312f68c272fe7c878acc5da7a2c, -0x14ee1efe512826fff9c028f8c7c86708b841f9dbf47ce4598298b01134ebdc1a, -0x6f861dba4503f85762d9741fa8b652ce441373f0ef2b7ebbd5a794e48cdab51b, -0xda110c9492ffdb87efe790214b7c9f707655a5ec08e5af19fb2ab2acc428e7dc, -0x5576aa898f6448d16e40473fcb24c46c609a3fc46a404559faa2d0d34d7d49ce, -0x9bd9a35675f2857792bc45893655bfdf905ffeaee942d93ad39fbcadd4ca9e11, -0xfa95e4c37db9303d5213890fd984034089cbc9c6d754741625da0aa59cc45ccf, -0xfef7d2079713f17b47239b76c8681bf7f800b1bfeac7a53265147579572ddf29, -0x39aa7c0fecf9a1ed037c685144745fda16da36f6d2004844cf0e2d608ef6ed0e, -0x5530654d502d6ba30f2b16f49cc5818279697308778fd8d40db8e84938144fb6, -0xb1beaa36397ba1521d7bf7df16536969d8a716e63510b1b82a715940180eb29f, -0x21abe342789f7c15a137afa373f686330c0db8c861572935a3cd8dcf9e4e1d45, -0x27b5a1acda55b4e0658887bd884d3203696fcae0e94f19e31bfe931342b1c257, -0x58401a02502d7708a812c0c72725f768f5a556480517258069f2d72543cda888, -0x4b38f291548f51bee7e4cf8cc5c8aa8f4ad3ec2461dba4ccbab70f1c1bfd7feb, -0x9b39a53fdafaaf1d23378e0aa8ae65d38480de69821de2910873eefc9f508568, -0x932200566a3563ee9141913d12fd1812cb008cb735724e8610890e101ec10112, -0x6a72f70b4ec5491f04780b17c4776a335fcc5bff5073d775150e08521dc74c91, -0x86d5c60e627a4b7d5d075b0ba33e779c45f3f46d22ed51f31360afd140851b67, -0x5ca2a736bb642abc4104faa781c9aff13d692a400d91dc961aec073889836946, -0xa14bca5a262ac46ceac21388a763561fc85fb9db343148d786826930f3e510cd, -0x87be03a87a9211504aa70ec149634ee1b97f7732c96377a3c04e98643dcba915, -0x8fe283bc19a377823377e9c326374ebb3f29527c12ea77bfb809c18eef8943b0, -0x8f519078b39a3969f7e4caeca9839d4e0eccc883b89e4a86d0e1731bfc5e33fc, -0x33d7c28c3d26fdfc015a8c2131920e1392ef0aea55505637b54ea63069c7858e, -0xe57de7c189fcc9170320c7acedb38798562a48dbc9943b2a8cd3441d58431128, -0x513dac46017050f82751a07b6c890f14ec43cadf687f7d202d2369e35b1836b4, -0xfd967d9f805bb7e78f7b7caa7692fdd3d6b5109c41ad239a08ad0a38eeb0ac4c, -0xf2013e4da9abcc0f03ca505ed94ec097556dbfd659088cd24ec223e02ac43329, -0xe0dcfac50633f7417f36231df2c81fa1203d358d5f57e896e1ab4b512196556b, -0xf022848130e73fe556490754ef0ecfcdaaf3b9ff16ae1eda7d38c95c4f159ded, -0x2147163a3339591ec7831d2412fb2d0588c38da3cd074fa2a4d3e5d21f9f1d2d, -0x11ee2404731962bf3238dca0d9759e06d1a5851308b4e6321090886ec5190b69, -0xf7679ecd07143f8ac166b66790fa09aed39352c09c0b4766bbe500b1ebace5a5, -0xc7a0e95f09076472e101813a95e6ea463c35bd5ee9cfda3e5d5dbccb35888ef0, -0xde625d3b547eb71bea5325a0191a592fa92a72e4b718a499fdba32e245ddf37e, -0x7e5bdccd95df216e8c59665073249072cb3c9d0aef6b341afc0ca90456942639, -0xc27f65fd9f797ede374e06b4ddb6e8aa59c7d6f36301f18b42c48b1889552fe3, -0x8175730a52ea571677b035f8e2482239dda1cfbff6bc5cde00603963511a81af, -0x09e440f2612dad1259012983dc6a1e24a73581feb1bd69d8a356eea16ba5fd0e, -0x59dcc81d594cbe735a495e38953e8133f8b3825fd84767af9e4ea06c49dbabfa, -0x6c8480b59a1a958c434b9680edea73b1207077fb9a8a19ea5f9fbbf6f47c4124, -0x81f5c89601893b7a5a231a7d37d6ab9aa4c57f174fcfc6b40002fa808714c3a1, -0x41ba4d6b4da141fcc1ee0f4b47a209cfd143d34e74fc7016e9956cedeb2db329, -0x5e0b5b404c60e9892040feacfb4a84a09c2bc4a8a5f54f3dad5dca4acdc899dc, -0xe922eebf1f5f15000d8967d16862ed274390cde808c75137d2fb9c2c0a80e391, -0xbf49d31a59a20484f0c08990b2345dfa954509aa1f8901566ab9da052b826745, -0xb84e07da828ae668c95d6aa31d4087504c372dbf4b5f8a8e4ded1bcf279fd52b, -0x89288bf52d8c4a9561421ad199204d794038c5d19ae9fee765ee2b5470e68e7e, -0xf6f618be99b85ec9a80b728454a417c647842215e2160c6fe547dd5a69bd9302, -0xdd9adc002f98c9a47c7b704fc0ce0a5c7861a5e2795b6014749cde8bcb8a034b, -0xd119a4b2c0db41fe01119115bcc35c4b7dbfdb42ad3cf2cc3f01c83732acb561, -0x9c66bc84d416b9193bad9349d8c665a9a06b835f82dc93ae0cccc218f808aad0, -0xd4b50eefcd2b5df075f14716cf6f2d26dfc8ae02e3993d711f4a287313038fde, -0xaf72bfb346c2f336b8bc100bff4ba35d006a3dad1c5952a0adb40789447f2704, -0xc43ca166f01dc955e7b4330227635feb1b0e0076a9c5633ca5c614a620244e5b, -0x5efca76970629521cfa053fbbbda8d3679cadc018e2e891043b0f52989cc2603, -0x35c57de1c788947f187051ce032ad1e899d9887d865266ec6fcfda49a8578b2b, -0x56d4be8a65b257216eab7e756ee547db5a882b4edcd12a84ed114fbd4f5be1f1, -0x257e858f8a4c07a41e6987aabaa425747af8b56546f2a3406f60d610bcc1f269, -0x40bd9ee36d52717ab22f1f6b0ee4fb38b594f58399e0bf680574570f1b4b8c90, -0xcb6ac01c21fc288c12973427c5df6eb8f6aefe64b92a6420c6388acdf36bc096, -0xa5716441312151a5f0deb52993a293884c6c8f445054ce1e395c96adeee66c6d, -0xe15696477f90113a10e04ba8225c28ad338c3b6bdd7bdeb95c0722921115ec85, -0x8faeaa52ca2f1d791cd6843330d16c75eaf6257e4ba236e3dda2bc1a644aee00, -0xc847fe595713bf136637ce8b43f9de238762953fed16798878344da909cc76ae, -0xb5740dc579594dd110078ce430b9696e6a308078022dde2d7cfe0ef7647b904e, -0x551a06d0771fcd3c53aea15aa8bf700047138ef1aa22265bee7fb965a84c9615, -0x9a65397a5907d604030508d41477de621ce4a0d79b772e81112d634455e7a4da, -0x6462d4cc2262d7faf8856812248dc608ae3d197bf2ef410f00c3ae43f2040995, -0x6782b1bd319568e30d54b324ab9ed8fdeac6515e36b609e428a60785e15fb301, -0x8bcdcf82c7eb2a07e14db20d80d9d2efea8d40320e121923784c92bf38250a8e, -0x46ed84fa17d226d5895e44685747ab82a97246e97d6237014611aaaba65ed268, -0x147e87981673326c5a2bdb06f5e90eaaa9583857129451eed6dde0c117fb061f, -0x4141d6fe070104c29879523ba6669552f3d457c0929bb878d2751f4ff059b895, -0xd866ce4ef226d74841f950fc28cdf2235db21e0e3f07a0c8f807704464db2210, -0xa804f9118bf92558f684f90c2bda832a4f51ef771ffb2765cde3ec6f48124f32, -0xc436d4a65910124e00cded9a637178914a8fbc090400f3f031c03eac4d0295a5, -0x643fdb9243656512316528de04dcc7344ca33783580ad0c3debf8c4a6e7c8bc4, -0x7f4a345b41706b281b2de998e91ff62d908eb29fc333ee336221757753c96e23, -0x6bdc086a5b11de950cabea33b72d98db886b291c4c2f02d3e997edc36785d249, -0xfb10b5b47d374078c0a52bff7174bf1cd14d872c7d20b4a009e2afd3017a9a17, -0x1e07e605312db5380afad8f3d7bd602998102fdd39565b618ac177b13a6527e6, -0xc3161b5a7b93aabf05652088b0e5b4803a18be693f590744c42c24c7aaaeef48, -0xa47e4f25112a7d276313f153d359bc11268b397933a5d5375d30151766bc689a, -0xb24260e2eff88716b5bf5cb75ea171ac030f5641a37ea89b3ac45acb30aae519, -0x2bcacbebc0a7f34406db2c088390b92ee34ae0f2922dedc51f9227b9afb46636, -0xc78c304f6dbe882c99c5e1354ce6077824cd42ed876db6706654551c7472a564, -0x6e2ee19d3ee440c78491f4e354a84fa593202e152d623ed899e700728744ac85, -0x2a3f438c5dc012aa0997b66f661b8c10f4a0cd7aa5b6e5922b1d73020561b27f, -0xd804f755d93173408988b95e9ea0e9feae10d404a090f73d9ff84df96f081cf7, -0xe06fda941b6936b8b33f00ffa02c8b05fd78fbec953da61da2043f5644b30a50, -0x45ee279b465d53148850a16cc7f6bd33e7627aef554a9418ed012ca8f9717f80, -0x9c79348c1bcd6aa2135452491d73564413a247ea8cc38fa7dcc6c43f8a2d61d5, -0x7c91e056f89f2a77d3e3642e595bcf4973c3bca68dd2b10f51ca0d8945e4255e, -0x669f976ebe38cbd22c5b1f785e14b76809d673d2cb1458983dbda41f5adf966b, -0x8bc71e99ffcc119fd8bd604af54c0663b0325a3203a214810fa2c588089ed5a7, -0x36b3f1ffeae5d9855e0965eef33f4c5133d99685802ac5ce5e1bb288d308f889, -0x0aad33df38b3f31598e04a42ec22f20bf2e2e9472d02371eb1f8a06434621180, -0x38c5632b81f90efbc51a729dcae03626a3063aa1f0a102fd0e4326e86a08a732, -0x6ea721753348ed799c98ffa330d801e6760c882f720125250889f107915e270a, -0xe700dd57ce8a653ce4269e6b1593a673d04d3de8b79b813354ac7c59d1b99adc, -0xe9294a24b560d62649ca898088dea35a644d0796906d41673e29e4ea8cd16021, -0xf20bb60d13a498a0ec01166bf630246c2f3b7481919b92019e2cfccb331f2791, -0xf639a667209acdd66301c8e8c2385e1189b755f00348d614dc92da14e6866b38, -0x49041904ee65c412ce2cd66d35570464882f60ac4e3dea40a97dd52ffc7b37a2, -0xdb36b16d3a1010ad172fc55976d45df7c03b05eab5432a77be41c2f739b361f8, -0x71400cdd2ea78ac1bf568c25a908e989f6d7e2a3690bc869c7c14e09c255d911, -0xf0d920b2d8a00b88f78e7894873a189c580747405beef5998912fc9266220d98, -0x1a2baefbbd41aa9f1cc5b10e0a7325c9798ba87de6a1302cf668a5de17bc926a, -0x449538a20e52fd61777c45d35ff6c2bcb9d9165c7eb02244d521317f07af6691, -0x97006755b9050b24c1855a58c4f4d52f01db4633baff4b4ef3d9c44013c5c665, -0xe441363a27b26d1fff3288222fa8ed540f8ca5d949ddcc5ff8afc634eec05336, -0xed587aa8752a42657fea1e68bc9616c40c68dcbbd5cb8d781e8574043e29ef28, -0x47d896133ba81299b8949fbadef1c00313d466827d6b13598685bcbb8776c1d2, -0x7786bc2cb2d619d07585e2ea4875f15efa22110e166af87b29d22af37b6c047d, -0x956b76194075fe3daf3ca508a6fad161deb05d0026a652929e37c2317239cbc6, -0xec9577cb7b85554b2383cc4239d043d14c08d005f0549af0eca6994e203cb4e7, -0x0722d0c68d38b23b83330b972254bbf9bfcf32104cc6416c2dad67224ac52887, -0x532b19d54fb6d77d96452d3e562b79bfd65175526cd793f26054c5f6f965df39, -0x4d62e065e57cbf60f975134a360da29cabdcea7fcfc664cf2014d23c733ab3b4, -0x09be0ea6b363fd746b303e482cb4e15ef25f8ae57b7143e64cbd5c4a1d069ebe, -0x69dcddc3e05147860d8d0e90d602ac454b609a82ae7bb960ee2ecd1627d77777, -0xa5e2ae69d902971000b1855b8066a4227a5be7234ac9513b3c769af79d997df4, -0xc287d4bc953dcff359d707caf2ccba8cc8312156eca8aafa261fb72412a0ea28, -0xb27584fd151fb30ed338f9cba28cf570f7ca39ebb03eb2e23140423af940bd96, -0x7e02928194441a5047af89a6b6555fea218f1df78bcdb5f274911b48d847f5f8, -0x9ba611add61ea6ba0d6d494c0c4edd03df9e6c03cafe10738cee8b7f45ce9476, -0x62647ec3109ac3db3f3d9ea78516859f0677cdde3ba2f27f00d7fda3a447dd01, -0xfa93ff6c25bfd9e17d520addf5ed2a60f1930278ff23866216584853f1287ac1, -0x3b391c2aa79c2a42888102cd99f1d2760b74f772c207a39a8515b6d18e66888a, -0xcc9ae3c14cbfb40bf01a09bcde913a3ed208e13e4b4edf54549eba2c0c948517, -0xc2b8bce78dd4e876da04c54a7053ca8b2bedc8c639cee82ee257c754c0bea2b2, -0xdb186f42871f438dba4d43755c59b81a6788cb3b544c0e1a3e463f6c2b6f7548, -0xb7f8ba137c7783137c0729de14855e20c2ac4416c33f5cac3b235d05acbab634, -0x282987e1f47e254e86d62bf681b0803df61340fdc9a8cf625ef2274f67fc6b5a, -0x04aa195b1aa736bf8875777e0aebf88147346d347613b5ab77bef8d1b502c08c, -0x3f732c559aee2b1e1117cf1dec4216a070259e4fa573a7dcadfa6aab74aec704, -0x72699d1351a59aa73fcede3856838953ee90c6aa5ef5f1f7e21c703fc0089083, -0x6d9ce1b8587e16a02218d5d5bed8e8d7da4ac40e1a8b46eeb412df35755c372c, -0x4f9c19b411c9a74b8616db1357dc0a7eaf213cb8cd2455a39eb7ae4515e7ff34, -0x9163dafa55b2b673fa7770b419a8ede4c7122e07919381225c240d1e90d90470, -0x268ff4507b42e623e423494d3bb0bc5c0917ee24996fb6d0ebedec9ce8cd9d5c, -0xff6e6169d233171ddc834e572024586eeb5b1bda9cb81e5ad1866dbc53dc75fe, -0xb379a9c8279205e8753b6a5c865fbbf70eb998f9005cd7cbde1511f81aed5256, -0x3a6b145e35a592e037c0992c9d259ef3212e17dca81045e446db2f3686380558, -0x60fb781d7b3137481c601871c1c3631992f4e01d415841b7f5414743dcb4cfd7, -0x90541b20b0c2ea49bca847e2db9b7bba5ce15b74e1d29194a12780e73686f3dd, -0xe2b0507c13ab66b4b769ad1a1a86834e385b315da2f716f7a7a8ff35a9e8f98c, -0xeefe54bc9fa94b921b20e7590979c28a97d8191d1074c7c68a656953e2836a72, -0x8676e7f59d6f2ebb0edda746fc1589ef55e07feab00d7008a0f2f6f129b7bb3a, -0x78a3d93181b40152bd5a8d84d0df7f2adde5db7529325c13bc24a5b388aed3c4, -0xcc0e2d0cba7aaa19c874dbf0393d847086a980628f7459e9204fda39fad375c0, -0x6e46a52cd7745f84048998df1a966736d2ac09a95a1c553016fef6b9ec156575, -0x204ac2831d2376d4f9c1f5c106760851da968dbfc488dc8a715d1c764c238263, -0xbdb8cc7b7e5042a947fca6c000c10b9b584e965c3590f92f6af3fe4fb23e1358, -0x4a55e4b8a138e8508e7b11726f617dcf4155714d4600e7d593fd965657fcbd89, -0xdfe064bb37f28d97b16d58b575844964205e7606dce914a661f2afa89157c45b, -0x560e374fc0edda5848eef7ff06471545fcbdd8aefb2ecddd35dfbb4cb03b7ddf, -0x10a66c82e146da5ec6f48b614080741bc51322a60d208a87090ad7c7bf6b71c6, -0x62534c7dc682cbf356e6081fc397c0a17221b88508eaeff798d5977f85630d4f, -0x0138bba8de2331861275356f6302b0e7424bbc74d88d8c534479e17a3494a15b, -0x580c7768bf151175714b4a6f2685dc5bcfeb088706ee7ed5236604888b84d3e4, -0xd290adb1a5dfc69da431c1c0c13da3be788363238d7b46bc20185edb45ab9139, -0x1689879db6c78eb4d3038ed81be1bc106f8cfa70a7c6245bd4be642bfa02ebd7, -0x6064c384002c8b1594e738954ed4088a0430316738def62822d08b2285514918, -0x01fd23493f4f1cc3c5ff4e96a9ee386b2a144b50a428a6b5db654072bddadfe7, -0xd5d05bb7f23ab0fa2b82fb1fb14ac29c2477d81a85423d0a45a4b7d5bfd81619, -0xd72b9a73ae7b24db03b84e01106cea734d4b9d9850b0b7e9d65d6001d859c772, -0x156317cb64578db93fee2123749aff58c81eae82b189b0d6f466f91de02b59df, -0x5fba299f3b2c099edbac18d785be61852225890fc004bf6be0787d62926a79b3, -0x004154f28f685bdbf0f0d6571e7a962a4c29b6c3ebedaaaf66097dfe8ae5f756, -0x4b45816f9834c3b289affce7a3dc80056c2b7ffd3e3c250d6dff7f923e7af695, -0x6ca53bc37816fff82346946d83bef87860626bbee7fd6ee9a4aeb904d893a11f, -0xf48b2f43184358d66d5b5f7dd2b14a741c7441cc7a33ba3ebcc94a7b0192d496, -0x3cb98f4baa429250311f93b46e745174f65f901fab4eb8075d380908aaaef650, -0x343dfc26b4473b3a20e706a8e87e5202a4e6b96b53ed448afb9180c3f766e5f8, -0x1ace0e8a735073bcbaea001af75b681298ef3b84f1dbab46ea52cee95ab0e7f9, -0xd239b110dd71460cdbc41ddc99494a7531186c09da2a697d6351c116e667733b, -0x22d6955236bd275969b8a6a30c23932670a6067f68e236d2869b6a8b4b493b83, -0x53c1c01f8d061ac89187e5815ef924751412e6a6aa4dc8e3abafb1807506b4e0, -0x2f56dd20c44d7370b713e7d7a1bfb1a800cac33f8a6157f278e17a943806a1f7, -0xc99773d8a5b3e60115896a65ac1d6c15863317d403ef58b90cb89846f4715a7f, -0x9f4b6b77c254094621cd336da06fbc6cbb7b8b1d2afa8e537ceca1053c561ef5, -0x87944d0b210ae0a6c201cba04e293f606c42ebaed8b4a5d1c33f56863ae7e1b5, -0xa7d116d962d03ca31a455f9cda90f33638fb36d3e3506605aa19ead554487a37, -0x4042e32e224889efd724899c9edb57a703e63a404129ec99858048fbc12f2ce0, -0x36759f7a0faeea1cd4cb91e404e4bf09908de6e53739603d5f0db52b664158a3, -0xa4d50d005fb7b9fea8f86f1c92439cc9b8446efef7333ca03a8f6a35b2d49c38, -0x80cb7c3e20f619006542edbe71837cdadc12161890a69eea8f41be2ee14c08a3, -0xbb3c44e1df45f2bb93fb80e7f82cee886c153ab484c0095b1c18df03523629b4, -0x04cb749e70fac3ac60dea779fceb0730b2ec5b915b0f8cf28a6246cf6da5db29, -0x4f5189b8f650687e65a962ef3372645432b0c1727563777433ade7fa26f8a728, -0x322eddddf0898513697599b68987be5f88c0258841affec48eb17cf3f61248e8, -0x6416be41cda27711d9ec22b3c0ed4364ff6975a24a774179c52ef7e6de9718d6, -0x0622d31b8c4ac7f2e30448bdadfebd5baddc865e0759057a6bf7d2a2c8b527e2, -0x40f096513588cc19c08a69e4a48ab6a43739df4450b86d3ec2fb3c6a743b5485, -0x09fcf7d49290785c9ea2d54c3d63f84f6ea0a2e9acfcdbb0cc3a281ce438250e, -0x2000a519bf3da827f580982d449b5c70fcc0d4fa232addabe47bb8b1c471e62e, -0xf4f80008518e200c40b043f34fb87a6f61b82f8c737bd784292911af3740245e, -0x939eaab59f3d2ad49e50a0220080882319db7633274a978ced03489870945a65, -0xadcad043d8c753fb10689280b7670f313253f5d719039e250a673d94441ee17c, -0x58b7b75f090166b8954c61057074707d7e38d55ce39d9b2251bbc3d72be458f8, -0xf61031890c94c5f87229ec608f2a9aa0a3f455ba8094b78395ae312cbfa04087, -0x356a55def50139f94945e4ea432e7a9defa5db7975462ebb6ca99601c614ea1d, -0x65963bb743d5db080005c4db59e29c4a4e86f92ab1dd7a59f69ea7eaf8e9aa79] -lamport_1 = [0x9c0bfb14de8d2779f88fc8d5b016f8668be9e231e745640096d35dd5f53b0ae2, -0x756586b0f3227ab0df6f4b7362786916bd89f353d0739fffa534368d8d793816, -0x710108dddc39e579dcf0819f9ad107b3c56d1713530dd94325db1d853a675a37, -0x8862b5f428ce5da50c89afb50aa779bb2c4dfe60e6f6a070b3a0208a4a970fe5, -0x54a9cd342fa3a4bf685c01d1ce84f3068b0d5b6a58ee22dda8fbac4908bb9560, -0x0fa3800efeaddd28247e114a1cf0f86b9014ccae9c3ee5f8488168b1103c1b44, -0xbb393428b7ebfe2eda218730f93925d2e80c020d41a29f4746dcbb9138f7233a, -0x7b42710942ef38ef2ff8fe44848335f26189c88c22a49fda84a51512ac68cd5d, -0x90e99786a3e8b04db95ccd44d01e75558d75f3ddd12a1e9a2c2ce76258bf4813, -0x3f6f71e40251728aa760763d25deeae54dc3a9b53807c737deee219120a2230a, -0xe56081a7933c6eaf4ef2c5a04e21ab8a3897785dd83a34719d1b62d82cfd00c2, -0x76cc54fa15f53e326575a9a2ac0b8ed2869403b6b6488ce4f3934f17db0f6bee, -0x1cd9cd1d882ea3830e95162b5de4beb5ddff34fdbf7aec64e83b82a6d11b417c, -0xb8ca8ae36d717c448aa27405037e44d9ee28bb8c6cc538a5d22e4535c8befd84, -0x5c4492108c25f873a23d5fd7957b3229edc22858e8894febe7428c0831601982, -0x907bcd75e7465e9791dc34e684742a2c0dc7007736313a95070a7e6b961c9c46, -0xe7134b1511559e6b2440672073fa303ec3915398e75086149eb004f55e893214, -0x2ddc2415e4753bfc383d48733e8b2a3f082883595edc5515514ebb872119af09, -0xf2ad0f76b08ffa1eee62228ba76f4982fab4fbede5d4752c282c3541900bcd5b, -0x0a84a6b15abd1cbc2da7092bf7bac418b8002b7000236dfba7c8335f27e0f1d4, -0x97404e02b9ff5478c928e1e211850c08cc553ebac5d4754d13efd92588b1f20d, -0xfa6ca3bcff1f45b557cdec34cb465ab06ade397e9d9470a658901e1f0f124659, -0x5bd972d55f5472e5b08988ee4bccc7240a8019a5ba338405528cc8a38b29bc21, -0x52952e4f96c803bb76749800891e3bfe55f7372facd5b5a587a39ac10b161bcc, -0xf96731ae09abcad016fd81dc4218bbb5b2cb5fe2e177a715113f381814007314, -0xe7d79e07cf9f2b52623491519a21a0a3d045401a5e7e10dd8873a85076616326, -0xe4892f3777a4614ee6770b22098eaa0a3f32c5c44b54ecedacd69789d676dffe, -0x20c932574779e2cc57780933d1dc6ce51a5ef920ce5bf681f7647ac751106367, -0x057252c573908e227cc07797117701623a4835f4b047dcaa9678105299e48e70, -0x20bad780930fa2a036fe1dea4ccbf46ac5b3c489818cdb0f97ae49d6e2f11fbf, -0xc0d7dd26ffecdb098585a1694e45a54029bb1e31c7c5209289058efebb4cc91b, -0x9a8744beb1935c0abe4b11812fc02748ef7c8cb650db3024dde3c5463e9d8714, -0x8ce6eea4585bbeb657b326daa4f01f6aef34954338b3ca42074aedd1110ba495, -0x1c85b43f5488b370721290d2faea19d9918d094c99963d6863acdfeeca564363, -0xe88a244347e448349e32d0525b40b18533ea227a9d3e9b78a9ff14ce0a586061, -0x352ca61efc5b8ff9ee78e738e749142dd1606154801a1449bbb278fa6bcc3dbe, -0xa066926f9209220b24ea586fb20eb8199a05a247c82d7af60b380f6237429be7, -0x3052337ccc990bfbae26d2f9fe5d7a4eb8edfb83a03203dca406fba9f4509b6e, -0x343ce573a93c272688a068d758df53c0161aa7f9b55dec8beced363a38b33069, -0x0f16b5593f133b58d706fe1793113a10750e8111eadee65301df7a1e84f782d3, -0x808ae8539357e85b648020f1e9d255bc4114bee731a6220d7c5bcb5b85224e03, -0x3b2bd97e31909251752ac57eda6015bb05b85f2838d475095cfd146677430625, -0xe4f857c93b2d8b250050c7381a6c7c660bd29066195806c8ef11a2e6a6640236, -0x23d91589b5070f443ddcefa0838c596518d54928119251ecf3ec0946a8128f52, -0xb72736dfad52503c7f5f0c59827fb6ef4ef75909ff9526268abc0f296ee37296, -0x80a8c66436d86b8afe87dde7e53a53ef87e057a5d4995963e76d159286de61b6, -0xbec92c09ee5e0c84d5a8ba6ca329683ff550ace34631ea607a3a21f99cd36d67, -0x83c97c9807b9ba6d9d914ae49dabdb4c55e12e35013f9b179e6bc92d5d62222b, -0x8d9c79f6af3920672dc4cf97a297c186e75083d099aeb5c1051207bad0c98964, -0x2aaa5944a2bd852b0b1be3166e88f357db097b001c1a71ba92040b473b30a607, -0x46693d27ec4b764fbb516017c037c441f4558aebfe972cdcd03da67c98404e19, -0x903b25d9e12208438f203c9ae2615b87f41633d5ffda9cf3f124c1c3922ba08f, -0x3ec23dc8bc1b49f5c7160d78008f3f235252086a0a0fa3a7a5a3a53ad29ec410, -0xa1fe74ceaf3cccd992001583a0783d7d7b7a245ea374f369133585b576b9c6d8, -0xb2d6b0fe4932a2e06b99531232398f39a45b0f64c3d4ebeaaebc8f8e50a80607, -0xe19893353f9214eebf08e5d83c6d44c24bffe0eceee4dc2e840d42eab0642536, -0x5b798e4bc099fa2e2b4b5b90335c51befc9bbab31b4dd02451b0abd09c06ee79, -0xbab2cdec1553a408cac8e61d9e6e19fb8ccfb48efe6d02bd49467a26eeeca920, -0x1c1a544c28c38e5c423fe701506693511b3bc5f2af9771b9b2243cd8d41bebfc, -0x704d6549d99be8cdefeec9a58957f75a2be4af7bc3dc4655fa606e7f3e03b030, -0x051330f43fe39b08ed7d82d68c49b36a8bfa31357b546bfb32068712df89d190, -0xe69174c7b03896461cab2dfaab33d549e3aac15e6b0f6f6f466fb31dae709b9b, -0xe5f668603e0ddbbcde585ac41c54c3c4a681fffb7a5deb205344de294758e6ac, -0xca70d5e4c3a81c1f21f246a3f52c41eaef9a683f38eb7c512eac8b385f46cbcd, -0x3173a6b882b21cd147f0fc60ef8f24bbc42104caed4f9b154f2d2eafc3a56907, -0xc71469c192bf5cc36242f6365727f57a19f924618b8a908ef885d8f459833cc3, -0x59c596fc388afd8508bd0f5a1e767f3dda9ed30f6646d15bc59f0b07c4de646f, -0xb200faf29368581f551bd351d357b6fa8cbf90bdc73b37335e51cad36b4cba83, -0x275cede69b67a9ee0fff1a762345261cb20fa8191470159cc65c7885cfb8313c, -0x0ce4ef84916efbe1ba9a0589bed098793b1ea529758ea089fd79151cc9dc7494, -0x0f08483bb720e766d60a3cbd902ce7c9d835d3f7fdf6dbe1f37bcf2f0d4764a2, -0xb30a73e5db2464e6da47d10667c82926fa91fceb337d89a52db5169008bc6726, -0x6b9c50fed1cc404bf2dd6fffbfd18e30a4caa1500bfeb080aa93f78d10331aaf, -0xf17c84286df03ce175966f560600dd562e0f59f18f1d1276b4d8aca545d57856, -0x11455f2ef96a6b2be69854431ee219806008eb80ea38c81e45b2e58b3f975a20, -0x9a61e03e2157a5c403dfcde690f7b7d704dd56ea1716cf14cf7111075a8d6491, -0x30312c910ce6b39e00dbaa669f0fb7823a51f20e83eaeb5afa63fb57668cc2f4, -0x17c18d261d94fba82886853a4f262b9c8b915ed3263b0052ece5826fd7e7d906, -0x2d8f6ea0f5b9d0e4bc1478161f5ed2ad3d8495938b414dcaec9548adbe572671, -0x19954625f13d9bab758074bf6dee47484260d29ee118347c1701aaa74abd9848, -0x842ef2ad456e6f53d75e91e8744b96398df80350cf7af90b145fea51fbbcf067, -0x34a8b0a76ac20308aa5175710fb3e75c275b1ff25dba17c04e3a3e3c48ca222c, -0x58efcbe75f32577afe5e9ff827624368b1559c32fcca0cf4fd704af8ce019c63, -0x411b4d242ef8f14d92bd8b0b01cb4fa3ca6f29c6f9073cfdd3ce614fa717463b, -0xf76dbda66ede5e789314a88cff87ecb4bd9ca418c75417d4d920e0d21a523257, -0xd801821a0f87b4520c1b003fe4936b6852c410ee00b46fb0f81621c9ac6bf6b4, -0x97ad11d6a29c8cf3c548c094c92f077014de3629d1e9053a25dbfaf7eb55f72d, -0xa87012090cd19886d49521d564ab2ad0f18fd489599050c42213bb960c9ee8ff, -0x8868d8a26e758d50913f2bf228da0444a206e52853bb42dd8f90f09abe9c859a, -0xc257fb0cc9970e02830571bf062a14540556abad2a1a158f17a18f14b8bcbe95, -0xfe611ce27238541b14dc174b652dd06719dfbcda846a027f9d1a9e8e9df2c065, -0xc9b25ea410f420cc2d4fc6057801d180c6cab959bce56bf6120f555966e6de6d, -0x95437f0524ec3c04d4132c83be7f1a603e6f4743a85ede25aa97a1a4e3f3f8fc, -0x82a12910104065f35e983699c4b9187aed0ab0ec6146f91728901efecc7e2e20, -0x6622dd11e09252004fb5aaa39e283333c0686065f228c48a5b55ee2060dbd139, -0x89a2879f25733dab254e4fa6fddb4f04b8ddf018bf9ad5c162aea5c858e6faaa, -0x8a71b62075a6011fd9b65d956108fa79cc9ebb8f194d64d3105a164e01cf43a6, -0x103f4fe9ce211b6452181371f0dc4a30a557064b684645a4495136f4ebd0936a, -0x97914adc5d7ce80147c2f44a6b29d0b495d38dedd8cc299064abcc62ed1ddabc, -0x825c481da6c836a8696d7fda4b0563d204a9e7d9e4c47b46ded26db3e2d7d734, -0xf8c0637ba4c0a383229f1d730db733bc11d6a4e33214216c23f69ec965dcaaad, -0xaed3bdaf0cb12d37764d243ee0e8acdefc399be2cabbf1e51dc43454efd79cbd, -0xe8427f56cc5cec8554e2f5f586b57adccbea97d5fc3ef7b8bbe97c2097cf848c, -0xba4ad0abd5c14d526357fd0b6f8676ef6126aeb4a6d80cabe1f1281b9d28246c, -0x4cff20b72e2ab5af3fafbf9222146949527c25f485ec032f22d94567ff91b22f, -0x0d32925d89dd8fed989912afcbe830a4b5f8f7ae1a3e08ff1d3a575a77071d99, -0xe51a1cbeae0be5d2fdbc7941aea904d3eade273f7477f60d5dd6a12807246030, -0xfb8615046c969ef0fa5e6dc9628c8a9880e86a5dc2f6fc87aff216ea83fcf161, -0x64dd705e105c88861470d112c64ca3d038f67660a02d3050ea36c34a9ebf47f9, -0xb6ad148095c97528180f60fa7e8609bf5ce92bd562682092d79228c2e6f0750c, -0x5bae0cd81f3bd0384ca3143a72068e6010b946462a73299e746ca639c026781c, -0xc39a0fc7764fcfc0402b12fb0bbe78fe3633cbfb33c7f849279585a878a26d7c, -0x2b752fda1c0c53d685cc91144f78d371db6b766725872b62cc99e1234cca8c1a, -0x40ee6b9635d87c95a528757729212a261843ecb06d975de91352d43ca3c7f196, -0x75e2005d3726cf8a4bb97ea5287849a361e3f8fdfadc3c1372feed1208c89f6b, -0x0976f8ab556153964b58158678a5297da4d6ad92e284da46052a791ee667aee4, -0xdbeef07841e41e0672771fb550a5b9233ae8e9256e23fa0d34d5ae5efe067ec8, -0xa890f412ab6061c0c5ee661e80d4edc5c36b22fb79ac172ddd5ff26a7dbe9751, -0xb666ae07f9276f6d0a33f9efeb3c5cfcba314fbc06e947563db92a40d7a341e8, -0x83a082cf97ee78fbd7f31a01ae72e40c2e980a6dab756161544c27da86043528, -0xfa726a919c6f8840c456dc77b0fec5adbed729e0efbb9317b75f77ed479c0f44, -0xa8606800c54faeab2cbc9d85ff556c49dd7e1a0476027e0f7ce2c1dc2ba7ccbf, -0x2796277836ab4c17a584c9f6c7778d10912cb19e541fb75453796841e1f6cd1c, -0xf648b8b3c7be06f1f8d9cda13fd6d60f913e5048a8e0b283b110ca427eeb715f, -0xa21d00b8fdcd77295d4064e00fbc30bed579d8255e9cf3a9016911d832390717, -0xe741afcd98cbb3bb140737ed77bb968ac60d5c00022d722f9f04f56e97235dc9, -0xbeecc9638fac39708ec16910e5b02c91f83f6321f6eb658cf8a96353cfb49806, -0x912eee6cabeb0fed8d6e6ca0ba61977fd8e09ea0780ff8fbec995e2a85e08b52, -0xc665bc0bb121a1229bc56ecc07a7e234fd24c523ea14700aa09e569b5f53ad33, -0x39501621c2bdff2f62ab8d8e3fe47fe1701a98c665697c5b750ee1892f11846e, -0x03d32e16c3a6c913daefb139f131e1e95a742b7be8e20ee39b785b4772a50e44, -0x4f504eb46a82d440f1c952a06f143994bc66eb9e3ed865080cd9dfc6d652b69c, -0xad753dc8710a46a70e19189d8fc7f4c773e4d9ccc7a70c354b574fe377328741, -0xf7f5464a2d723b81502adb9133a0a4f0589b4134ca595a82e660987c6b011610, -0x216b60b1c3e3bb4213ab5d43e04619d13e1ecedbdd65a1752bda326223e3ca3e, -0x763664aa96d27b6e2ac7974e3ca9c9d2a702911bc5d550d246631965cf2bd4a2, -0x292b5c8c8431b040c04d631f313d4e6b67b5fd3d4b8ac9f2edb09d13ec61f088, -0x80db43c2b9e56eb540592f15f5900222faf3f75ce62e78189b5aa98c54568a5e, -0x1b5fdf8969bcd4d65e86a2cefb3a673e18d587843f4f50db4e3ee77a0ba2ef1c, -0x11e237953fff3e95e6572da50a92768467ffdfd0640d3384aa1c486357e7c24a, -0x1fabd4faa8dba44808cc87d0bc389654a98496745578f3d17d134adc7f7b10f3, -0x5eca4aa96f20a56197772ae6b600762154ca9d2702cab12664ea47cbff1a440c, -0x0b4234f5bb02abcf3b5ce6c44ea85f55ec7db98fa5a7b90abef6dd0df034743c, -0x316761e295bf350313c4c92efea591b522f1df4211ce94b22e601f30aefa51ef, -0xe93a55ddb4d7dfe02598e8f909ff34b3de40a1c0ac8c7fba48cb604ea60631fb, -0xe6e6c877b996857637f8a71d0cd9a6d47fdeb03752c8965766f010073332b087, -0xa4f95c8874e611eddd2c4502e4e1196f0f1be90bfc37db35f8588e7d81d34aeb, -0x9351710a5633714bb8b2d226e15ba4caa6f50f56c5508e5fa1239d5cc6a7e1aa, -0x8d0aef52ec7266f37adb572913a6213b8448caaf0384008373dec525ae6cdff1, -0x718e24c3970c85bcb14d2763201812c43abac0a7f16fc5787a7a7b2f37288586, -0x3600ce44cebc3ee46b39734532128eaf715c0f3596b554f8478b961b0d6e389a, -0x50dd1db7b0a5f6bd2d16252f43254d0f5d009e59f61ebc817c4bbf388519a46b, -0x67861ed00f5fef446e1f4e671950ac2ddae1f3b564f1a6fe945e91678724ef03, -0x0e332c26e169648bc20b4f430fbf8c26c6edf1a235f978d09d4a74c7b8754aad, -0x6c9901015adf56e564dfb51d41a82bde43fb67273b6911c9ef7fa817555c9557, -0x53c83391e5e0a024f68d5ade39b7a769f10664e12e4942c236398dd5dbce47a1, -0x78619564f0b2399a9fcb229d938bf1e298d62b03b7a37fe6486034185d7f7d27, -0x4625f15381a8723452ec80f3dd0293c213ae35de737c508f42427e1735398c3a, -0x69542425ddb39d3d3981e76b41173eb1a09500f11164658a3536bf3e292f8b6a, -0x82ac4f5bb40aece7d6706f1bdf4dfba5c835c09afba6446ef408d8ec6c09300f, -0x740f9180671091b4c5b3ca59b9515bd0fc751f48e488a9f7f4b6848602490e21, -0x9a04b08b4115986d8848e80960ad67490923154617cb82b3d88656ec1176c24c, -0xf9ffe528eccffad519819d9eef70cef317af33899bcaee16f1e720caf9a98744, -0x46da5e1a14b582b237f75556a0fd108c4ea0d55c0edd8f5d06c59a42e57410df, -0x098f3429c8ccda60c3b5b9755e5632dd6a3f5297ee819bec8de2d8d37893968a, -0x1a5b91af6025c11911ac072a98b8a44ed81f1f3c76ae752bd28004915db6f554, -0x8bed50c7cae549ed4f8e05e02aa09b2a614c0af8eec719e4c6f7aee975ec3ec7, -0xd86130f624b5dcc116f2dfbb5219b1afde4b7780780decd0b42694e15c1f8d8b, -0x4167aa9bc0075f624d25d40eb29139dd2c452ebf17739fab859e14ac6765337a, -0xa258ce5db20e91fb2ea30d607ac2f588bdc1924b21bbe39dc881e19889a7f5c6, -0xe5ef8b5ab3cc8894452d16dc875b69a55fd925808ac7cafef1cd19485d0bb50a, -0x120df2b3975d85b6dfca56bb98a82025ade5ac1d33e4319d2e0105b8de9ebf58, -0xc964291dd2e0807a468396ebba3d59cfe385d949f6d6215976fc9a0a11de209a, -0xf23f14cb709074b79abe166f159bc52b50de687464df6a5ebf112aa953c95ad5, -0x622c092c9bd7e30f880043762e26d8e9c73ab7c0d0806f3c5e472a4152b35a93, -0x8a5f090662731e7422bf651187fb89812419ab6808f2c62da213d6944fccfe9f, -0xfbea3c0d92e061fd2399606f42647d65cc54191fa46d57b325103a75f5c22ba6, -0x2babfbcc08d69b52c3747ddc8dcad4ea5511edabf24496f3ff96a1194d6f680e, -0x4d3d019c28c779496b616d85aee201a3d79d9eecf35f728d00bcb12245ace703, -0xe76fcee1f08325110436f8d4a95476251326b4827399f9b2ef7e12b7fb9c4ba1, -0x4884d9c0bb4a9454ea37926591fc3eed2a28356e0506106a18f093035638da93, -0x74c3f303d93d4cc4f0c1eb1b4378d34139220eb836628b82b649d1deb519b1d3, -0xacb806670b278d3f0c84ba9c7a68c7df3b89e3451731a55d7351468c7c864c1c, -0x8660fb8cd97e585ea7a41bccb22dd46e07eee8bbf34d90f0f0ca854b93b1ebee, -0x2fc9c89cdca71a1c0224d469d0c364c96bbd99c1067a7ebe8ef412c645357a76, -0x8ec6d5ab6ad7135d66091b8bf269be44c20af1d828694cd8650b5479156fd700, -0x50ab4776e8cabe3d864fb7a1637de83f8fbb45d6e49645555ffe9526b27ebd66, -0xbf39f5e17082983da4f409f91c7d9059acd02ccbefa69694aca475bb8d40b224, -0x3135b3b981c850cc3fe9754ec6af117459d355ad6b0915beb61e84ea735c31bf, -0xa7971dab52ce4bf45813223b0695f8e87f64b614c9c5499faac6f842e5c41be9, -0x9e480f5617323ab104b4087ac4ef849a5da03427712fb302ac085507c77d8f37, -0x57a6d474654d5e8d408159be39ad0e7026e6a4c6a6543e23a63d30610dc8dfc1, -0x09eb3e01a5915a4e26d90b4c58bf0cf1e560fdc8ba53faed9d946ad3e9bc78fa, -0x29c6d25da80a772310226b1b89d845c7916e4a4bc94d75aa330ec3eaa14b1e28, -0x1a1ccfee11edeb989ca02e3cb89f062612a22a69ec816a625835d79370173987, -0x1cb63dc541cf7f71c1c4e8cabd2619c3503c0ea1362dec75eccdf1e9efdbfcfc, -0xac9dff32a69e75b396a2c250e206b36c34c63b955c9e5732e65eaf7ccca03c62, -0x3e1b4f0c3ebd3d38cec389720147746774fc01ff6bdd065f0baf2906b16766a8, -0x5cc8bed25574463026205e90aad828521f8e3d440970d7e810d1b46849681db5, -0x255185d264509bd3a768bb0d50b568e66eb1fec96d573e33aaacc716d7c8fb93, -0xe81b86ba631973918a859ff5995d7840b12511184c2865401f2693a71b9fa07e, -0x61e67e42616598da8d36e865b282127c761380d3a56d26b8d35fbbc7641433c5, -0x60c62ffef83fe603a34ca20b549522394e650dad5510ae68b6e074f0cd209a56, -0x78577f2caf4a54f6065593535d76216f5f4075af7e7a98b79571d33b1822920c, -0xfd4cb354f2869c8650200de0fe06f3d39e4dbebf19b0c1c2677da916ea84f44d, -0x453769cef6ff9ba2d5c917982a1ad3e2f7e947d9ea228857556af0005665e0b0, -0xe567f93f8f88bf1a6b33214f17f5d60c5dbbb531b4ab21b8c0b799b6416891e0, -0x7e65a39a17f902a30ceb2469fe21cba8d4e0da9740fcefd5c647c81ff1ae95fa, -0x03e4a7eea0cd6fc02b987138ef88e8795b5f839636ca07f6665bbae9e5878931, -0xc3558e2b437cf0347cabc63c95fa2710d3f43c65d380feb998511903f9f4dcf0, -0xe3a615f80882fb5dfbd08c1d7a8b0a4d3b651d5e8221f99b879cb01d97037a9c, -0xb56db4a5fea85cbffaee41f05304689ea321c40d4c108b1146fa69118431d9b2, -0xab28e1f077f18117945910c235bc9c6f9b6d2b45e9ef03009053006c637e3e26, -0xefcabc1d5659fd6e48430dbfcc9fb4e08e8a9b895f7bf9b3d6c7661bfc44ada2, -0xc7547496f212873e7c3631dafaca62a6e95ac39272acf25a7394bac6ea1ae357, -0xc482013cb01bd69e0ea9f447b611b06623352e321469f4adc739e3ee189298eb, -0x5942f42e91e391bb44bb2c4d40da1906164dbb6d1c184f00fa62899baa0dba2c, -0xb4bcb46c80ad4cd603aff2c1baf8f2c896a628a46cc5786f0e58dae846694677, -0xd0a7305b995fa8c317c330118fee4bfef9f65f70b54558c0988945b08e90ff08, -0x687f801b7f32fdfa7d50274cc7b126efedbdae8de154d36395d33967216f3086, -0xeb19ec10ac6c15ffa619fa46792971ee22a9328fa53bd69a10ed6e9617dd1bbf, -0xa2bb3f0367f62abdb3a9fa6da34b20697cf214a4ff14fd42826da140ee025213, -0x070a76511f32c882374400af59b22d88974a06fbc10d786dd07ca7527ebd8b90, -0x8f195689537b446e946b376ec1e9eb5af5b4542ab47be550a5700fa5d81440d5, -0x10cc09778699fc8ac109e7e6773f83391eeba2a6db5226fbe953dd8d99126ca5, -0x8cc839cb7dc84fd3b8c0c7ca637e86a2f72a8715cc16c7afb597d12da717530b, -0xa32504e6cc6fd0ee441440f213f082fcf76f72d36b5e2a0f3b6bdd50cdd825a2, -0x8f45151db8878e51eec12c450b69fa92176af21a4543bb78c0d4c27286e74469, -0x23f5c465bd35bcd4353216dc9505df68324a27990df9825a242e1288e40a13bb, -0x35f409ce748af33c20a6ae693b8a48ba4623de9686f9834e22be4410e637d24f, -0xb962e5845c1db624532562597a99e2acc5e434b97d8db0725bdeddd71a98e737, -0x0f8364f99f43dd52b4cfa9e426c48f7b6ab18dc40a896e96a09eceebb3363afe, -0xa842746868da7644fccdbb07ae5e08c71a6287ab307c4f9717eadb414c9c99f4, -0xa59064c6b7fe7d2407792d99ed1218d2dc2f240185fbd8f767997438241b92e9, -0xb6ea0d58e8d48e05b9ff4d75b2ebe0bd9752c0e2691882f754be66cdec7628d3, -0xf16b78c9d14c52b2b5156690b6ce37a5e09661f49674ad22604c7d3755e564d1, -0xbfa8ef74e8a37cd64b8b4a4260c4fc162140603f9c2494b9cf4c1e13de522ed9, -0xf4b89f1776ebf30640dc5ec99e43de22136b6ef936a85193ef940931108e408a, -0xefb9a4555d495a584dbcc2a50938f6b9827eb014ffae2d2d0aae356a57894de8, -0x0627a466d42a26aca72cf531d4722e0e5fc5d491f4527786be4e1b641e693ac2, -0x7d10d21542de3d8f074dbfd1a6e11b3df32c36272891aae54053029d39ebae10, -0x0f21118ee9763f46cc175a21de876da233b2b3b62c6f06fa2df73f6deccf37f3, -0x143213b96f8519c15164742e2350cc66e814c9570634e871a8c1ddae4d31b6b5, -0x8d2877120abae3854e00ae8cf5c8c95b3ede10590ab79ce2be7127239507e18d, -0xaccd0005d59472ac04192c059ed9c10aea42c4dabec9e581f6cb10b261746573, -0x67bc8dd5422f39e741b9995e6e60686e75d6620aa0d745b84191f5dba9b5bb18, -0x11b8e95f6a654d4373cefbbac29a90fdd8ae098043d1969b9fa7885318376b34, -0x431a0b8a6f08760c942eeff5791e7088fd210f877825ce4dcabe365e03e4a65c, -0x704007f11bae513f428c9b0d23593fd2809d0dbc4c331009856135dafec23ce4, -0xc06dee39a33a05e30c522061c1d9272381bde3f9e42fa9bd7d5a5c8ef11ec6ec, -0x66b4157baaae85db0948ad72882287a80b286df2c40080b8da4d5d3db0a61bd2, -0xef1983b1906239b490baaaa8e4527f78a57a0a767d731f062dd09efb59ae8e3d, -0xf26d0d5c520cce6688ca5d51dee285af26f150794f2ea9f1d73f6df213d78338, -0x8b28838382e6892f59c42a7709d6d38396495d3af5a8d5b0a60f172a6a8940bd, -0x261a605fa5f2a9bdc7cffac530edcf976e7ea7af4e443b625fe01ed39dad44b6] -compressed_lamport_PK = 0xdd635d27d1d52b9a49df9e5c0c622360a4dd17cba7db4e89bce3cb048fb721a5 -child_SK = 20397789859736650942317412262472558107875392172444076792671091975210932703118 -``` - -## Implementation - -* [Python](https://github.com/ethereum/eth2.0-deposit-cli) - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2333.md diff --git a/EIPS/eip-2334.md b/EIPS/eip-2334.md index 76a9b198d3941d..10fbd2dcc9b792 100644 --- a/EIPS/eip-2334.md +++ b/EIPS/eip-2334.md @@ -1,104 +1 @@ ---- -eip: 2334 -title: BLS12-381 Deterministic Account Hierarchy -author: Carl Beekhuizen -discussions-to: https://github.com/ethereum/EIPs/issues/2338 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-09-30 -requires: 2333 ---- - -## Simple Summary - -This EIP defines the purpose of a given key, or family thereof, within a tree of keys. When combined with [EIP-2333](./eip-2333.md), the combination of a seed and knowledge of the desired purpose of a key is sufficient to determine a key pair. - -## Abstract - -A standard for allocating keys generated by [EIP-2333](./eip-2333.md) to a specific purpose. It defines a `path` which is a string that parses into the indices to be used when traversing the tree of keys that [EIP-2333](./eip-2333.md) generates. - -## A note on purpose - -This specification is designed not only to be an Ethereum 2.0 standard, but one that is adopted by the wider community who have adopted [BLS signatures over BLS12-381](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/). It is therefore important also to consider the needs of the wider industry along with those specific to Ethereum. As a part of these considerations, it is the intention of the author that this standard eventually migrate to a more neutral repository in the future. - -## Motivation - -Ethereum 2.0 alongside many other projects will use BLS signatures over BLS12-381, an [IETF proposed standard](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/). This new scheme requires a new key derivation mechanism, which is established within [EIP-2333](./eip-2333.md). This new scheme is incompatible with the current form of this specification ([BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)) due to the: exclusive use of hardened keys, the increased number of keys per level, not using [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) for key derivation. It is therefore necessary to establish a new *path* for traversing the [EIP-2333](./eip-2333.md) key-tree. - -The path structure specified in this EIP aims to be more general than [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) by not having UTXO-centric features [which gave rise to the 4 different types of wallet paths being used within Ethereum 1.0](https://github.com/ethereum/EIPs/issues/84#issuecomment-292324521) and gave rise to (draft) [EIP-600](./eip-600.md) & [EIP-601](./eip-601.md) - -## Specification - -### Path - -The path traversed through the tree of keys is defined by integers (which indicate the sibling index) separated by `/` which denote ancestor relations. There are 4 levels (plus the master node) in the path and at least 4 (5 including the master node) MUST be used. - -```text -m / purpose / coin_type / account / use -``` - -#### Notation - -The notation used within the path is specified within the [EIP-2333](./eip-2333.md), but is summarized again below for convenience. - -* `m` Denotes the master node (or root) of the tree -* `/` Separates the tree into depths, thus `i / j` signifies that `j` is a child of `i` - -### Purpose - -The `purpose` is set to `12381` which is the name of the new curve (BLS12-381). In order to be in compliance with this standard, the [EIP-2333](./eip-2333.md) MUST be implemented as the KDF and therefore, the purpose `12381` MAY NOT be used unless this is the case. - -### Coin Type - -The `coin_type` here reflects the coin number for an individual coin thereby acting as a means of separating the keys used for different chains. - -### Account - -`account` is a field that provides the ability for a user to have distinct sets of keys for different purposes, if they so choose. This is the level at which different accounts for a single user SHOULD to be implemented. - -### Use - -This level is designed to provide a set of related keys that can be used for any purpose. The idea being that a single account has many uses which are related yet should remain separate for security reasons. It is required to support this level in the tree, although, for many purposes it will remain `0`. - -### Eth2 Specific Parameters - -#### Coin type - -The coin type used for the BLS12-381 keys in Ethereum 2 is `3600`. - -#### Validator keys - -Each Eth2 validator has two keys, one for withdrawals and transfers (called the *withdrawal key*), and the other for performing their duties as a validator (henceforth referred to as the *signing key*). - -The path for withdrawal keys is `m/12381/3600/i/0` where `i` indicates the `i`th set of validator keys. - -The path for the signing key is `m/12381/3600/i/0/0` where again, `i` indicates the `i`th set of validator keys. Another way of phrasing this is that the signing key is the `0`th child of the associated withdrawal key for that validator. - -**Note:** If the above description of key paths is not feasible in a specific use case (eg. with secret-shared or custodial validators), then the affected keys may be omitted and derived via another means. Implementations of this EIP, must endeavour to use the appropriate keys for the given use case to the extent that is reasonably possible. (eg, in the case of custodial staking, the user making the deposits will follow this standard for their withdrawal keys which has no bearing on how the service provide derives the corresponding signing keys.) - -## Rationale - -`purpose`, `coin_type`, and `account` are widely-adopted terms as per [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki) and [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) and therefore reusing these terms and their associated meanings makes sense. - -The purpose needs to be distinct from these standards as the KDF and path are not inter-compatible and `12381` is an obvious choice. - -`account` separates user activity into distinct categories thereby allowing users to separate their concerns however they desire. - -`use` will commonly be determined at the application level providing distinct keys for non-intersecting use cases. - -### Eth2 Specific Parameters - -A new coin type is chosen for Eth2 keys to help ensure a clean separation between Eth2 and Eth1 keys. Although the distinction between Eth1 ETH and Eth2 ETH is subtle, they are distinct entities and there are services which only distinguish between coins by their coin name (eg. [ENS' multichain address resolution](./eip-2304.md)). `3600` is chosen specifically because it is the square of the Eth1's `coin_type` (`3600==60^2`) thereby signaling that it is second instantiation of Ether the currency. - -The primary reason validators have separate signing and withdrawal keys is to allow for the different security concerns of actions within Eth2. The signing key is given to the validator client where it signs messages as per the requirements of being a validator, it is therefore a "hot key". If this key is compromised, the worst that can happen (locally) is that a slashable message is signed, resulting in the validator being slashed and forcibly exited. The withdrawal key is only needed when a validator wishes to perform an action not related to validating and has access to the full funds at stake for that validator. The withdrawal key therefore has higher security concerns and should be handled as a "cold key". By having the signing key be a child of the withdrawal key, secure storage of the withdrawal key is sufficient to recover the signing key should the need arise. - -## Backwards Compatibility - -[BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki) and [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) are the commonly used standards for this purpose within Ethereum 1.0, however they have not been `Accepted` as standards as yet. Due to the use of a new KDF within [EIP-2333](./eip-2333.md), a new path standard is required. This EIP implements this, with minor changes. - -`purpose` `12381` paths do not support hardened keys and therefore the `'` character is invalid. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2334.md diff --git a/EIPS/eip-2335.md b/EIPS/eip-2335.md index 43ca6240e06a81..209e208b8cabff 100644 --- a/EIPS/eip-2335.md +++ b/EIPS/eip-2335.md @@ -1,301 +1 @@ ---- -eip: 2335 -title: BLS12-381 Keystore -author: Carl Beekhuizen -discussions-to: https://github.com/ethereum/EIPs/issues/2339 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-09-30 -requires: 2333, 2334 ---- - -## Simple Summary - -A JSON format for the storage and interchange of BLS12-381 private keys. - -## Abstract - -A keystore is a mechanism for storing private keys. It is a JSON file that encrypts a private key and is the standard for interchanging keys between devices as until a user provides their password, their key is safe. - -## A note on purpose - -This specification is designed not only to be an Ethereum 2.0 standard, but one that is adopted by the wider community who have adopted the BLS12-381 signature standard. It is therefore important also to consider the needs of the wider industry along with those specific to Ethereum. As a part of these considerations, it is the intention of the author that this standard eventually migrate to a more neutral repository in the future. - -## Motivation - -The secure storage and exchange of keys is a vital component of the user experience as people are expected to hold their own keys. It allows users to control access to individual keys and their use by applications. - -In Ethereum 1, [the Web3 Secret Storage Definition](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) fulfills these requirements, however it is not perfectly suitable for these purposes moving forward. Specifically the problems with the existing standard are: - -* __The use of Keccak256.__ Eth1 keystores use Keccak for their checksum, a sensible choice considering its usage within Ethereum 1. BLS12-381 [signatures](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00), [keys (EIP-2333)](./eip-2333.md), and key-storage are inter-chain standards, the establishment and proliferation of which hinges on them being neutral to all chains, something which Keccak is not. - -* __A lack of abstraction.__ Eth1 keystores are a result of an iterative design process whereby functionality was added and modified as needed without considering how abstractions could simplify the notion of different properties. - -## Specification - -The process of decrypting the secret held within a keystore can be broken down into 3 sub-processes: obtaining the decryption key, verifying the password and decrypting the secret. Each process has its own functions which can be selected from as well as parameters required for the function all of which are specified within the keystore file itself. - -### Password requirements - -The password is a string of arbitrary unicode characters. The password is first converted to its NFKD representation, then the control codes (specified below) are stripped from the password and finally it is UTF-8 encoded. - -#### Control codes removal - -The C0, C1, and `Delete` control codes are not valid characters in the password and should therefore be stripped from the password. C0 are the control codes between `0x00` - `0x1F` (inclusive) and C1 codes lie between `0x80` and `0x9F` (inclusive). `Delete`, commonly known as "backspace", is the UTF-8 character `7F` which must also be stripped. Note that space (`Sp` UTF-8 `0x20`) is a valid character in passwords despite it being a pseudo-control character. - -### Modules - -This standard makes use of the notion of a _module_ which serves to represent, in an abstract sense, the different  cryptographic constructions and corresponding parameters for each component of the keystore. The idea being that components can be swapped out without affecting the rest of the specification should the need arise. - -A module is comprised of a `function`, which defines which cryptographic construct is being used, `params`, the parameters required by the function, and `message` the primary input to the function. - -### Decryption key - -The decryption key is an intermediate key which is used both to verify the user-supplied password is correct, as well as for the final secret decryption. This key is simply derived from the password, the `function`, and the `params` specified by the`kdf` module as per the keystore file. - -| KDF | `"function"` | `"params"` | `"message"` | Definition | -|----------------|--------------|------------------------------------------------------------------------------------------|-------------|--------------------------------------------------| -| PBKDF2-SHA-256 | `"pbkdf2"` |
  • `"c"`
  • `"dklen"`
  • `"prf: "hmac-sha256"`
  • `"salt"`
| | [RFC 2898](https://www.ietf.org/rfc/rfc2898.txt) | -| scrypt | `"scrypt"` |
  • `"dklen"`
  • `"n"`
  • `"p"`
  • `"r"`
  • `"salt"`
| | [RFC 7914](https://tools.ietf.org/html/rfc7914) | - -### Password verification - -The password verification step verifies that the password is correct with respect to the `checksum.message`, `cipher.message`, and `kdf`. This is done by appending the `cipher.message` to the 2nd 16 bytes of the decryption key, obtaining its SHA256 hash and verifying whether it matches the `checksum.message`. - -#### Inputs - -* `decryption_key`, the octet string obtained from decryption key process -* `cipher_message`, the octet string obtained from keystore file from `crypto.cipher.message` -* `checksum_message`, the octet string obtained from keystore file from `crypto.checksum.message` - -#### Outputs - -* `valid_password`, a boolean value indicating whether the password is valid - -#### Definitions - -* `a[0:3]` returns a slice of `a` including octets 0, 1, 2 -* `a | b` is the concatenation of `a` with `b` - -#### Procedure - -```text -0. DK_slice = decryption_key[16:32] -1. pre_image = DK_slice | cipher_message -2. checksum = SHA256(pre_image) -3. valid_password = checksum == checksum_message -4. return valid_password -``` - -| Hash | `"function"` | `"params"` | `"message"` | Definition | -|------------|-----------------|------------|-------------|-------------------------------------------------| -| SHA-256 | `"sha256"` | | | [RFC 6234](https://tools.ietf.org/html/rfc6234) | - -### Secret decryption - -The `cipher.function` encrypts the secret using the decryption key, thus to decrypt it, the decryption key along with the `cipher.function` and `cipher.params` must be used. If the `decryption_key` is longer than the key size required by the cipher, it is truncated to the correct number of bits. In the case of aes-128-ctr, only the first 16 bytes of the `decryption_key` are used as the AES key. - -| Cipher | `"function"` | `"params"` | `"message"` | Definition | -|----------------------|-----------------|--------------------------|-------------|-------------------------------------------------| -| AES-128 Counter Mode | `"aes-128-ctr"` |
  • `"iv"`
| | [RFC 3686](https://tools.ietf.org/html/rfc3686) | - -## Description - -This field is an optional field to help explain the purpose and identify a particular keystores in a user-friendly manner. While this field can, and should, be used to help distinguish keystores from one-another, the `description` **is not necessarily unique**. - -## PubKey - -The `pubkey` is the public key associated with the the private key secured within the keystore. It is stored here to improve user experience and security which is achieved by not requiring users to enter their password just to obtain their public keys. This field is required if the secret being stored within the keystore is a private key. The encoding of the `pubkey` is specified in the in the appropriate signature standard (eg. [BLS12-381 signature standard](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-00)), but can be seen as a byte-string in the abstract and should be directly compatible with the appropriate signature library. - -## Path - -The `path` indicates where in the key-tree a key originates from. It is a string defined by [EIP-2334](./eip-2334.md), if no path is known or the path is not relevant, the empty string, `""` indicates this. The `path` can specify an arbitrary depth within the tree and the deepest node within the tree indicates the depth of the key stored within this file. - -## UUID - -The `uuid` provided in the keystore is a randomly generated UUID as specified by [RFC 4122](https://tools.ietf.org/html/rfc4122). It is used as a 128-bit proxy for referring to a particular set of keys or account. - -## Version - -The `version` is set to `4`. - -## JSON schema - -The keystore, at its core, is constructed with modules which allow for the configuration of the cryptographic constructions used password hashing, password verification and secret decryption. Each module is composed of: `function`, `params`, and `message` which corresponds with which construction is to be used, what the configuration for the construction is, and what the input is. - -```json -{ - "$ref": "#/definitions/Keystore", - "definitions": { - "Keystore": { - "type": "object", - "properties": { - "crypto": { - "type": "object", - "properties": { - "kdf": { - "$ref": "#/definitions/Module" - }, - "checksum": { - "$ref": "#/definitions/Module" - }, - "cipher": { - "$ref": "#/definitions/Module" - } - } - }, - "description": { - "type": "string" - }, - "pubkey": { - "type": "string" - }, - "path": { - "type": "string" - }, - "uuid": { - "type": "string", - "format": "uuid" - }, - "version": { - "type": "integer" - } - }, - "required": [ - "crypto", - "path", - "uuid", - "version" - ], - "title": "Keystore" - }, - "Module": { - "type": "object", - "properties": { - "function": { - "type": "string" - }, - "params": { - "type": "object" - }, - "message": { - "type": "string" - } - }, - "required": [ - "function", - "message", - "params" - ] - } - } -} -``` - -## Rationale - -The rationale behind the design of this specification is largely the same as that behind the [Ethereum 1 keystore definition](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) except for the lack of support for Keccak (explained in [motivation above](#motivation)) and the notion of modules. - -Modules provide a very useful level of abstraction which allow the Key-Derivation-Function, Checksum, and Cipher to be thought of as instances of the same thing allowing for their substitution with minimal effort. - -The `version` is set to 4 to prevent collisions with the existing Ethereum keystore standard. - -## Backwards Compatibility - -This specification is not backwards compatible with the [existing keystore standard](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) due to the lack of Keccak256 checksums as explained above. While this format is capable of supporting Keccak checksums via the Checksum module, it would defeat the purpose of this standard to include it as this standard could no longer be considered neutral with respect to other projects in the industry. - -## Test Cases - -### Scrypt Test Vector - -Password `"𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑"` -Encoded Password: `0x7465737470617373776f7264f09f9491` -Secret `0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f` - -```json -{ - "crypto": { - "kdf": { - "function": "scrypt", - "params": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": {}, - "message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f" - } - }, - "description": "This is a test keystore that uses scrypt to secure the secret.", - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "path": "m/12381/60/3141592653/589793238", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "version": 4 -} -``` - -### PBKDF2 Test Vector - -Password `"𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑"` -Encoded Password: `0x7465737470617373776f7264f09f9491` -Secret `0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f` - -```json -{ - "crypto": { - "kdf": { - "function": "pbkdf2", - "params": { - "dklen": 32, - "c": 262144, - "prf": "hmac-sha256", - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": {}, - "message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad" - } - }, - "description": "This is a test keystore that uses PBKDF2 to secure the secret.", - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "path": "m/12381/60/0/0", - "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", - "version": 4 -} -``` - -## Implementation - -Implementations exist in the following languages: - -* [Python3](https://github.com/ethereum/eth2.0-deposit-cli) -* [TypeScript](https://github.com/nodefactoryio/bls-keystore) -* [Go](https://github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4/) - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2335.md diff --git a/EIPS/eip-2386.md b/EIPS/eip-2386.md index f09e375c76fe54..e5d16a88068c67 100644 --- a/EIPS/eip-2386.md +++ b/EIPS/eip-2386.md @@ -1,201 +1 @@ ---- -eip: 2386 -title: Ethereum 2 Hierarchical Deterministic Walletstore -author: Jim McDonald -discussions-to: https://ethereum-magicians.org/t/eip-2386-walletstore/3792 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-11-21 -requires: 2334, 2335 ---- - -## Simple Summary - -A JSON format for the storage and retrieval of Ethereum 2 hierarchical deterministic (HD) wallet definitions. - -## Abstract - -Ethereum has the concept of keystores: pieces of data that define a key (see [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335) for details). This adds the concept of walletstores: stores that define wallets and how keys in said wallets are created. - -## Motivation - -Hierarchical deterministic wallets create keys from a _seed_ and a _path_. The seed needs to be accessible to create new keys, however it should also be protected to the same extent as private keys to stop it from becoming an easy attack vector. The path, or at least the variable part of it, needs to be stored to ensure that keys are not duplicated. Providing a standard method to do this can promote interoperability between wallets and similar software. - -Given that a wallet has an amount of data and metadata that is useful when accessing existing keys and creating new keys, standardizing this information and how it is stored allows it to be portable between different wallet providers with minimal effort. - -## Specification - -The elements of a hierarchical deterministic walletstore are as follows: - -### UUID - -The `uuid` provided in the walletstore is a randomly-generated type 4 UUID as specified by [RFC 4122](https://tools.ietf.org/html/rfc4122). It is intended to be used as a 128-bit proxy for referring to a particular wallet, used to uniquely identify wallets. - -This element MUST be present. It MUST be a string following the syntactic structure as laid out in [section 3 of RFC 4122](https://tools.ietf.org/html/rfc4122#section-3). - -### Name - -The `name` provided in the walletstore is a UTF-8 string. It is intended to serve as the user-friendly accessor. The only restriction on the name is that it MUST NOT start with the underscore (`_`) character. - -This element MUST be present. It MUST be a string. - -### Version - -The `version` provided is the version of the walletstore. - -This element MUST be present. It MUST be the integer `1`. - -### Type - -The `type` provided is the type of wallet. This informs mechanisms such as key generation. - -This element MUST be present. It MUST be the string `hierarchical deterministic`. - -### Crypto - -The `crypto` provided is the secure storage of a secret for wallets that require this information. For hierarchical deterministic wallets this is the seed from which they calculate individual private keys. - -This element MUST be present. It MUST be an object that follows the definition described in [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). - -### Next Account - -The `nextaccount` provided is the index to be supplied to the path `m/12381/60//0` when creating a new private key from the seed. The path follows [EIP-2334](https://eips.ethereum.org/EIPS/eip-2334). - -This element MUST be present if the wallet type requires it. It MUST be a non-negative integer. - -### JSON schema - -The walletstore follows a similar format to that of the keystore described in [EIP-2335](https://eips.ethereum.org/EIPS/eip-2335). - -```json -{ - "$ref": "#/definitions/Walletstore", - "definitions": { - "Walletstore": { - "type": "object", - "properties": { - "crypto": { - "type": "object", - "properties": { - "kdf": { - "$ref": "#/definitions/Module" - }, - "checksum": { - "$ref": "#/definitions/Module" - }, - "cipher": { - "$ref": "#/definitions/Module" - } - } - }, - "name": { - "type": "string" - }, - "nextaccount": { - "type": "integer" - }, - "type": { - "type": "string" - }, - "uuid": { - "type": "string", - "format": "uuid" - }, - "version": { - "type": "integer" - } - }, - "required": [ - "name", - "type", - "uuid", - "version" - "crypto" - "nextaccount" - ], - "title": "Walletstore" - }, - "Module": { - "type": "object", - "properties": { - "function": { - "type": "string" - }, - "params": { - "type": "object" - }, - "message": { - "type": "string" - } - }, - "required": [ - "function", - "message", - "params" - ] - } - } -} -``` - -## Rationale - -A standard for walletstores, similar to that for keystores, provides a higher level of compatibility between wallets and allows for simpler wallet and key interchange between them. - -## Test Cases - -### Test Vector - -Password `'testpassword'` -Seed `0x147addc7ec981eb2715a22603813271cce540e0b7f577126011eb06249d9227c` - -```json -{ - "crypto": { - "checksum": { - "function": "sha256", - "message": "8bdadea203eeaf8f23c96137af176ded4b098773410634727bd81c4e8f7f1021", - "params": {} - }, - "cipher": { - "function": "aes-128-ctr", - "message": "7f8211b88dfb8694bac7de3fa32f5f84d0a30f15563358133cda3b287e0f3f4a", - "params": { - "iv": "9476702ab99beff3e8012eff49ffb60d" - } - }, - "kdf": { - "function": "pbkdf2", - "message": "", - "params": { - "c": 16, - "dklen": 32, - "prf": "hmac-sha256", - "salt": "dd35b0c08ebb672fe18832120a55cb8098f428306bf5820f5486b514f61eb712" - } - } - }, - "name": "Test wallet 2", - "nextaccount": 0, - "type": "hierarchical deterministic", - "uuid": "b74559b8-ed56-4841-b25c-dba1b7c9d9d5", - "version": 1 -} -``` - -## Implementation - -A Go implementation of the hierarchical deterministic wallet can be found at [https://github.com/wealdtech/go-eth2-wallet-hd](https://github.com/wealdtech/go-eth2-wallet-hd). - -## Security Considerations - -The seed stored in the `crypto` section of the wallet can be used to generate any key along the derived path. As such, the security of all keys generated by HD wallets is reduced to the security of the passphrase and strength of the encryption used to protect the seed, regardless of the security of the passphrase and strength of the encryption used to protect individual keystores. - -It is possible to work with only the walletstore plus an index for each key, in which case stronger passphrases can be used as decryption only needs to take place once. It is also possible to use generated keystores without the walletstore, in which case a breach of security will expose only the keystore. - -An example high-security configuration may involve the walletstore existing on an offline computer, from which keystores are generated. The keystores can then be moved individually to an online computer to be used for signing. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2386.md diff --git a/EIPS/eip-2390.md b/EIPS/eip-2390.md index 4d87272c0810ad..57de681f55c2f6 100644 --- a/EIPS/eip-2390.md +++ b/EIPS/eip-2390.md @@ -1,316 +1 @@ ---- -eip: 2390 -title: Geo-ENS -author: James Choncholas (@james-choncholas) -discussions-to: https://github.com/ethereum/EIPs/issues/2959 -status: Stagnant -type: Standards Track -category: ERC -created: 2019-11-15 -requires: 137, 165, 1062, 1185 ---- - -## Simple Summary -GeoENS brings geographic split horizon capabilities to ENS. It's GeoDNS for ENS! - -## Abstract -This EIP specifies an ENS resolver interface for geographically split horizon DNS. -Geographic split horizon DNS returns resource records that are specific to an end -user's location. -This technique is commonly used by CDNs to direct traffic to content caches nearest users. -Geographic split horizon resolution is primarily geared towards ENS -resolvers storing DNS resource records [EIP-1185](./eip-1185.md), although the technique could be -used on other interfaces like IPFS content hash storage [EIP-1062](./eip-1062.md). - -## Motivation -There are many use cases for traditional GeoDNS systems, like Amazon's Route53, -in the centralized web. -These use cases include proximity-based load balancing and serving content -specific to the geographic location of the query. -Unfortunately the ENS specification does not provide a mechanism for -geo-specific resolution. -ENS can respond to queries with IP addresses (as described in [EIP-1185](./eip-1185.md)) -however there is no way to respond to geo-specific queries. -This EIP proposes a standard to give the ENS system geo-proximal awareness -to serve a similar purpose as GeoDNS. - -GeoENS can do more than DNS-based solutions. -In addition to geographic split horizon DNS, GeoENS can be used for the following: - - Locating digital resources (like smart contracts) that represent physical objects in the real world. - - Smart contract managing access to a physical object associated with a specific location. - - ENS + IPFS web hosting (as described in [EIP-1062](./eip-1062.md)) with content translated to the native language of the query source. - - Tokenizing objects with a physical location. - -Because of the decentralized nature of ENS, geo-specific resolution is different than traditional GeoDNS. -GeoDNS works as follows. DNS queries are identified by their source IP address. -This IP is looked up in a database like [GeoIP2](https://www.maxmind.com/en/geoip2-services-and-databases) -from MaxMind which maps the IP address to a location. -This method of locating the source of a query is error prone and unreliable. -If the GeoIP database is out of date, queried locations can be vastly different than their true location. -GeoENS does not rely on a database because the user includes a location in their query. - -It follows that queries can be made by users for any location, not just their location. -Traditional DNS will only return the resource assigned to a query's provenance. -GeoENS does not correlate a query's provinance with a location, allowing the -entire globe to be queried from a single location. - -An additional shortcoming of traditional DNS is the fact that there is no way to return a list of servers in a certain proximity. -This is paramount for uses cases that require discovering the resource with the lowest latency. -GeoENS allows a list of resources, like IP addresses, to be gathered within a specific location. -Then a client to determine themselves which resource has the lowest latency. - -Lastly, publicly facing GeoDNS services do not give fine granularity control -over geographic regions for GeoDNS queries. -Cloud based DNS services like [Amazon's Route 53](https://aws.amazon.com/route53/) -only allow specifying geographic regions at the granularity of a State in -the United States. -GeoENS on the other hand gives 8 characters of geohash resolution which -corresponds to +-20 meter accuracy. - -## Specification -This EIP proposes a new interface to ENS resolvers such that geo-spacial information -can be recorded and retrieved from the blockchain. -The interface changes are described below for "address resolvers" described in EIP137 -however the idea applies to any record described in EIP1185 and EIP1062, namely DNS -Resolvers, Text Resolvers, ABI Resolvers, etc. - -### What is a geohash? -A [Geohash](https://en.m.wikipedia.org/wiki/Geohash#Algorithm_and_example) -is an interleaving of latitude and longitude bits, whose -length determines it's precision. -Geohashes are typically encoded in base 32 characters. - -### function setGeoAddr(bytes32 node, string calldata geohash, address addr) external authorised(node) -Sets a resource (contract address, IP, ABI, TEXT, etc.) by node and geohash. -Geohashes must be unique per address and are exactly 8 characters long. -This leads to an accuracy of +-20 meters. -Write default initialized resource value, `address(0)`, to remove a resource from the resolver. - -### function geoAddr(bytes32 node, string calldata geohash) external view returns (address[] memory ret) -Query the resolver contract for a specific node and location. -All resources (contract addresses, IP addresses, ABIs, TEXT records, etc.) matching -the node and prefix geohash provided are returned. -This permits querying by exact geohash of 8 characters to return the content at that location, -or querying by geographic bounding box described by a geohash of less than 8 character precision. - -Any type of geohash can be used including [Z-order](https://en.wikipedia.org/wiki/Z-order_curve) -[Hilbert](https://en.wikipedia.org/wiki/Hilbert_curve) or the more accurate -[S2 Geometry](https://s2geometry.io/devguide/s2cell_hierarchy.html) library -from Google. -There are also ways to search the geographic data using geohashes without -always ending up with a rectangular query region. -[Searching circular shaped regions](https://github.com/ashwin711/proximityhash) is -slightly more complex as it requires multiple queries. - -## Rationale -The proposed implementation uses a sparse [Quadtree](https://dl.acm.org/doi/10.1007/BF00288933) trie as an index for -resource records as it has low storage overhead and good search performance. -The leaf nodes of the tree store resource records while non-leaves represent one geohash character. -Each node in the tree at depth d corresponds to a geohash of precision d. -The tree has depth 8 because the maximum precision of a geohash is 8 characters. -The tree has fanout 32 because the radix of a geohash character is 32. -The path to get to a leaf node always has depth 8 and the leaf contains the content (like IP address) -of the geohash represented by the path to the leaf. -The tree is sparse as 71% of the Earth's surface is covered by water. -The tree facilitates common traversal algorithms (DFS, BFS) to return -lists of resource records within a geographic bounding box. - -## Backwards Compatibility -This EIP does not introduce issues with backwards compatibility. - -## Test Cases -See https://github.com/james-choncholas/resolvers/blob/master/test/TestPublicResolver.js - -## Implementation -This address resolver, written in Solidity, implements the specifications outlined above. -The same idea presented here can be applied to other resolver interfaces as specified in EIP137. -Note that geohashes are passed and stored using 64 bit unsigned integers. -Using integers instead of strings for geohashes is more performant, especially in the `geomap` mapping. -For comparison purposes, see https://github.com/james-choncholas/geoens/tree/master/contracts/StringOwnedGeoENSResolver.sol for the inefficient string implementation. - - -```solidity -pragma solidity ^0.5.0; - -import "../ResolverBase.sol"; - -contract GeoENSResolver is ResolverBase { - bytes4 constant ERC2390 = 0x8fbcc5ce; - uint constant MAX_ADDR_RETURNS = 64; - uint constant TREE_VISITATION_QUEUESZ = 64; - uint8 constant ASCII_0 = 48; - uint8 constant ASCII_9 = 57; - uint8 constant ASCII_a = 97; - uint8 constant ASCII_b = 98; - uint8 constant ASCII_i = 105; - uint8 constant ASCII_l = 108; - uint8 constant ASCII_o = 111; - uint8 constant ASCII_z = 122; - - struct Node { - address data; // 0 if not leaf - uint256 parent; - uint256[] children; // always length 32 - } - - // A geohash is 8, base-32 characters. - // A geomap is stored as tree of fan-out 32 (because - // geohash is base 32) and height 8 (because geohash - // length is 8 characters) - mapping(bytes32=>Node[]) private geomap; - - event GeoENSRecordChanged(bytes32 indexed node, bytes8 geohash, address addr); - - // only 5 bits of ret value are used - function chartobase32(byte c) pure internal returns (uint8 b) { - uint8 ascii = uint8(c); - require( (ascii >= ASCII_0 && ascii <= ASCII_9) || - (ascii > ASCII_a && ascii <= ASCII_z)); - require(ascii != ASCII_a); - require(ascii != ASCII_i); - require(ascii != ASCII_l); - require(ascii != ASCII_o); - - if (ascii <= (ASCII_0 + 9)) { - b = ascii - ASCII_0; - - } else { - // base32 b = 10 - // ascii 'b' = 0x60 - // note base32 skips the letter 'a' - b = ascii - ASCII_b + 10; - - // base32 also skips the following letters - if (ascii > ASCII_i) - b --; - if (ascii > ASCII_l) - b --; - if (ascii > ASCII_o) - b --; - } - require(b < 32); // base 32 can't be larger than 32 - return b; - } - - function geoAddr(bytes32 node, bytes8 geohash, uint8 precision) external view returns (address[] memory ret) { - bytes32(node); // single node georesolver ignores node - assert(precision <= geohash.length); - - ret = new address[](MAX_ADDR_RETURNS); - if (geomap[node].length == 0) { return ret; } - uint ret_i = 0; - - // walk into the geomap data structure - uint pointer = 0; // not actual pointer but index into geomap - for(uint8 i=0; i < precision; i++) { - - uint8 c = chartobase32(geohash[i]); - uint next = geomap[node][pointer].children[c]; - if (next == 0) { - // nothing found for this geohash. - // return early. - return ret; - } else { - pointer = next; - } - } - - // pointer is now node representing the resolution of the query geohash. - // DFS until all addresses found or ret[] is full. - // Do not use recursion because blockchain... - uint[] memory indexes_to_visit = new uint[](TREE_VISITATION_QUEUESZ); - indexes_to_visit[0] = pointer; - uint front_i = 0; - uint back_i = 1; - - while(front_i != back_i) { - Node memory cur_node = geomap[node][indexes_to_visit[front_i]]; - front_i ++; - - // if not a leaf node... - if (cur_node.data == address(0)) { - // visit all the chilins - for(uint i=0; i MAX_ADDR_RETURNS) break; - } - } - - return ret; - } - - // when setting, geohash must be precise to 8 digits. - function setGeoAddr(bytes32 node, bytes8 geohash, address addr) external authorised(node) { - bytes32(node); // single node georesolver ignores node - - // create root node if not yet created - if (geomap[node].length == 0) { - geomap[node].push( Node({ - data: address(0), - parent: 0, - children: new uint256[](32) - })); - } - - // walk into the geomap data structure - uint pointer = 0; // not actual pointer but index into geomap - for(uint i=0; i < geohash.length; i++) { - - uint8 c = chartobase32(geohash[i]); - - if (geomap[node][pointer].children[c] == 0) { - // nothing found for this geohash. - // we need to create a path to the leaf - geomap[node].push( Node({ - data: address(0), - parent: pointer, - children: new uint256[](32) - })); - geomap[node][pointer].children[c] = geomap[node].length - 1; - } - pointer = geomap[node][pointer].children[c]; - } - - Node storage cur_node = geomap[node][pointer]; // storage = get reference - cur_node.data = addr; - - emit GeoENSRecordChanged(node, geohash, addr); - } - - function supportsInterface(bytes4 interfaceID) public pure returns (bool) { - return interfaceID == ERC2390 || super.supportsInterface(interfaceID); - } -} -``` - -## Security Considerations -This contract has similar functionality to ENS Resolvers - refer there for security considerations. -Additionally, this contract has a dimension of data privacy. -Users query via the geoAddr function specifying a geohash of less than 8 characters -which defines the query region. -Users who run light clients leak the query region to their connected full-nodes. -Users who rely on nodes run by third parties (like Infura) will also leak -the query region. -Users who run their own full node or have access to a trusted full node do -not leak any location data. - -Given the way most location services work, the query region is likely to contain -the user's actual location. -The difference between API access, light, and full nodes has always had -an impact on privacy but now the impact is underscored by the involvement -of coarse granularity user location. - - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2390.md diff --git a/EIPS/eip-2400.md b/EIPS/eip-2400.md index 1978fd56b03e0b..43b8ac36ec8710 100644 --- a/EIPS/eip-2400.md +++ b/EIPS/eip-2400.md @@ -1,100 +1 @@ ---- -eip: 2400 -title: Transaction Receipt URI -description: URI format for submitted transactions with complete information for transaction decoding -author: Ricardo Guilherme Schmidt (@3esmit), Eric Dvorsak (@yenda) -discussions-to: https://ethereum-magicians.org/t/eip-2400-transaction-receipt-uri/ -status: Stagnant -type: Standards Track -category: ERC -created: 2019-11-05 -requires: 155, 681 ---- -## Abstract - -A transaction hash is not very meaningful on its own, because it looks just like any other hash, and it might lack important information for reading a transaction. - -This standard includes all needed information for displaying a transaction and its details, such as `chainId`, `method` signature called, and `events` signatures emitted. - -## Motivation - -Interoperability between ethereum clients, allowing different systems to agree on a standard way of representing submitted transactions hashes, optionally with necessary information for decoding transaction details. - -### Use-cases - -Transaction Receipt URIs embedded in QR-codes, hyperlinks in web-pages, emails or chat messages provide for robust cross-application signaling between very loosely coupled applications. A standardized URI format allows for instant invocation of the user’s preferred transaction explorer application. Such as: - -- In web3 (dapps, mining pools, exchanges), links would automatically open user's preferred transaction explorer; -- In wallets, for users sharing transaction receipts easier; -- In chat applications, as a reply to an [EIP-681] transaction request; -- In crypto vending machines, a QRCode can be displayed when transactions are submitted; -- Anywhere transaction receipts are presented to users. - -## Specification - -### Syntax - -Transaction receipt URLs contain "ethereum" in their schema (protocol) part and are constructed as follows: - - receipt = schema_part transaction_hash [ "@" chain_id ] [ "?" parameters ] - schema_part = "ethereum:tx-" - transaction_hash = "0x" 64*HEXDIG - chain_id = 1*DIGIT - parameters = parameter *( "&" parameter ) - parameter = key "=" value - key = "method" / "events" - value = function_signature / event_list - function_signature = function_name "(" TYPE *( "," TYPE) ")" - function_name = STRING - event_list = event_signature *( ";" event_signature ) - event_signature = event_name "(" event_type *( "," event_type) ")" - event_name = STRING - event_type = ["!"] TYPE - - -Where `TYPE` is a standard ABI type name, as defined in Ethereum Contract ABI specification. `STRING` is a URL-encoded unicode string of arbitrary length. - -The exclamation symbol (`!`), in `event_type`, is used to identify indexed event parameters. - -### Semantics - -`transaction_hash` is mandatory. The hash must be looked up in the corresponding `chain_id` transaction history, if not found it should be looked into the pending transaction queue and rechecked until is found. If not found anequivalent error as "transaction not found error" should be shown instead of the transaction. When the transaction is pending, it should keep checking until the transaction is included in a block and becomes "unrevertable" (usually 12 blocks after transaction is included). - - -`chain_id` is specified by [EIP-155] optional and contains the decimal chain ID, such that transactions on various test and private networks can be represented as well. If no `chain_id` is present, the $ETH/mainnet (`1`) is considered. - -If `method` is not present, this means that the transaction receipt URI does not specify details, or that it was a transaction with no calldata. When present it needs to be validated by comparing the first 4 bytes of transaction calldata with the first 4 bytes of the keccak256 hash of `method`, if invalid, an equivalent error as "method validation error" must be shown instead of the transaction. - -If `events` is not present, this means that the transaction receipt URI does not specify details, or that the transaction did not raised any events. Pending and failed transactions don't validate events, however, when transaction is successful (or changes from pending to success) and events are present in URI, each event in the `event_list` must occur at least once in the transaction receipt event logs, otherwise an equivalent error as "event validation error: {event(s) [$event_signature, ...] not found}" should be shown instead of the transaction. A URI might contain the event signature for all, some or none of the raised events. - -#### Examples - -##### Simple ETH transfer: -`ethereum:tx-0x1143b5e38fe3cf585fb026fb9b5ce35c85a691786397dc8a23a07a62796d8172@1` - -##### Standard Token transfer: - -`ethereum:tx-0x5375e805b0c6afa20daab8d37352bf09a533efb03129ba56dee869e2ce4f2f92@1?method="transfer(address,uint256)"&events="Transfer(!address,!address,uint256)"` - -##### Complex contract transaction: - -`ethereum:tx-0x4465e7cce3c784f264301bfe26fc17609855305213ec74c716c7561154b76fec@1?method="issueAndActivateBounty(address,uint256,string,uint256,address,bool,address,uint256)"&events="Transfer(!address,!address,uint256);BountyIssued(uint256);ContributionAdded(uint256,!address,uint256);BountyActivated(uint256,address)"` - -## Rationale - -The goal of this standard envolves only the transport of submitted transactions, and therefore transaction data must be loaded from blockchain or pending transaction queue, which also serves as a validation of the transaction existence. - -Transaction hash not found is normal in fresh transactions, but can also mean that effectively a transaction was never submitted or have been replaced (through "higher gasPrice" nonce override or through an uncle/fork). - -In order to decode transaction parameters and events, a part of the ABI is required. The transaction signer have to know the ABI to sign a transaction, and is also who is creating a transaction receipt, so the transaction receipt can optionally be shared with the information needed to decode the transaction call data and it's events. - -## Backwards Compatibility - -Future upgrades that are partially or fully incompatible with this proposal must use a prefix other than `tx-` that is separated by a dash (-) character from whatever follows it. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - -[EIP-155]: ./eip-155.md -[EIP-681]: ./eip-681.md +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2400.md diff --git a/EIPS/eip-2470.md b/EIPS/eip-2470.md index 406c749f587cc7..0afbd104a5f727 100644 --- a/EIPS/eip-2470.md +++ b/EIPS/eip-2470.md @@ -1,197 +1 @@ ---- -eip: 2470 -title: Singleton Factory -author: Ricardo Guilherme Schmidt (@3esmit) -discussions-to: https://ethereum-magicians.org/t/erc-2470-singleton-factory/3933 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-01-15 -requires: 1014 ---- - -## Simple Summary - -Some DApps needs one, and only one, instance of an contract, which have the same address on any chain. - -A permissionless factory for deploy of keyless deterministic contracts addresses based on its bytecode. - -## Abstract - -Some contracts are designed to be Singletons which have the same address no matter what chain they are, which means that should exist one instance for all, such as [EIP-1820] and [EIP-2429]. These contracts are usually deployed using a method known as [Nick]'s method, so anyone can deploy those contracts on any chain and they have a deterministic address. -This standard proposes the creation of a CREATE2 factory using this method, so other projects requiring this feature can use this factory in any chain with the same setup, even in development chains. - -## Motivation - -Code reuse, using the factory becomes easier to deploy singletons. - -## Specification - -### [ERC-2470] Singleton Factory - -> This is an exact copy of the code of the [ERC2470 factory smart contract]. - -```solidity -pragma solidity 0.6.2; - - -/** - * @title Singleton Factory (EIP-2470) - * @notice Exposes CREATE2 (EIP-1014) to deploy bytecode on deterministic addresses based on initialization code and salt. - * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) - */ -contract SingletonFactory { - /** - * @notice Deploys `_initCode` using `_salt` for defining the deterministic address. - * @param _initCode Initialization code. - * @param _salt Arbitrary value to modify resulting address. - * @return createdContract Created contract address. - */ - function deploy(bytes memory _initCode, bytes32 _salt) - public - returns (address payable createdContract) - { - assembly { - createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt) - } - } -} -// IV is a value changed to generate the vanity address. -// IV: 6583047 -``` - -### Deployment Transaction - -Below is the raw transaction which MUST be used to deploy the smart contract on any chain. - -``` -0xf9016c8085174876e8008303c4d88080b90154608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634af63f0214602d575b600080fd5b60cf60048036036040811015604157600080fd5b810190602081018135640100000000811115605b57600080fd5b820183602082011115606c57600080fd5b80359060200191846001830284011164010000000083111715608d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925060eb915050565b604080516001600160a01b039092168252519081900360200190f35b6000818351602085016000f5939250505056fea26469706673582212206b44f8a82cb6b156bfcc3dc6aadd6df4eefd204bc928a4397fd15dacf6d5320564736f6c634300060200331b83247000822470 -``` - -The strings of `2470`'s at the end of the transaction are the `r` and `s` of the signature. -From this deterministic pattern (generated by a human), anyone can deduce that no one knows the private key for the deployment account. - -### Deployment Method - -This contract is going to be deployed using the keyless deployment method---also known as [Nick]'s method---which relies on a single-use address. -(See [Nick's article] for more details). This method works as follows: - -1. Generate a transaction which deploys the contract from a new random account. - - This transaction MUST NOT use [EIP-155] in order to work on any chain. - - This transaction MUST have a relatively high gas price to be deployed on any chain. In this case, it is going to be 100 Gwei. - -2. Forge a transaction with the following parameters: - ```js - { - nonce: 0, - gasPrice: 100000000000, - value: 0, - data: '0x608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634af63f0214602d575b600080fd5b60cf60048036036040811015604157600080fd5b810190602081018135640100000000811115605b57600080fd5b820183602082011115606c57600080fd5b80359060200191846001830284011164010000000083111715608d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925060eb915050565b604080516001600160a01b039092168252519081900360200190f35b6000818351602085016000f5939250505056fea26469706673582212206b44f8a82cb6b156bfcc3dc6aadd6df4eefd204bc928a4397fd15dacf6d5320564736f6c63430006020033', - gasLimit: 247000, - v: 27, - r: '0x247000', - s: '0x2470' - } - ``` - > The `r` and `s` values, made of starting `2470`, are obviously a human determined value, instead of a real signature. - -3. We recover the sender of this transaction, i.e., the single-use deployment account. - - > Thus we obtain an account that can broadcast that transaction, but we also have the warranty that nobody knows the private key of that account. - -4. Send exactly 0.0247 ether to this single-use deployment account. - -5. Broadcast the deployment transaction. - - > Note: 247000 is the double of gas needed to deploy the smart contract, this ensures that future changes in OPCODE pricing are unlikely to cause this deploy transaction to fail out of gas. A left over will sit in the address of about 0.01 ETH will be forever locked in the single use address. - -The resulting transaction hash is `0x803351deb6d745e91545a6a3e1c0ea3e9a6a02a1a4193b70edfcd2f40f71a01c`. - -This operation can be done on any chain, guaranteeing that the contract address is always the same and nobody can use that address with a different contract. - - -### Single-use Factory Deployment Account - -![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAB0UlEQVR4nO3asW1CQRBAQdpyCa6CIpxTjgujDGTJNEC2QqvjTbDx33c3P7vL79f1fzLf98dobn8/o5nuP53p/tPzm+5/AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4CMBnH6B0/23L2AbEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJ8JYPsCtw+w3g9AvB+AeD8A8X4A4v0AxPsBiPcDEO8HIN4PQLwfgHg/APF+AOL9AMT7AYj3AxDvP/5ByOkApt/PvwgCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgJYDtA9w+gO0fYHsAAGB/CQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAdALYfNExnun+9H4B4PwDxfgDi/QDE+wGI9wMQ7wcg3g9AvB+AeD8A8X4A4v0AxPsBiPcDEO8HIN4/fhCy/aDidADb5wcAAGcHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO8CsH2ApwPY/j4Ah+8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPB6nlegoDNgrfyiAAAAAElFTkSuQmCC) - -`0xBb6e024b9cFFACB947A71991E386681B1Cd1477D` - -This account is generated by reverse engineering it from its signature for the transaction. -This way no one knows the private key, but it is known that it is the valid signer of the deployment transaction. - -> To deploy the registry, 0.0247 ether MUST be sent to this account *first*. - -### Factory Contract Address -![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAABn0lEQVR4nO3coRECMRRF0S2GutCUQzd4WqAMLB4qQGWYP+EecXXeZo/OcTrf35Ndbq+l7F/rmB6w+wXuvh+A+H4A4vsBiO8HIL4fgPh+AOL7AYjvByC+H4D4fgDi+wGI7wcgvh+A+H4A4vuXAUxfwPX5GG33+wMAgL0/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgGYHrA9A+cbhoQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wlgesD0+bvvXz0fgM33AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8ATB9gZoNgHgAxAMgHgDxAIgHQDwA4gEQD4B4AMQDIB4A8QCIB0A8AOId0w8caK3V/wfA5gEQD4B4AMQDIB4A8QCIB0A8AOIBEA+AeADEAyAeAPEAiAdAPADiARAPgHgAxAMgHgDxAIgHQDwA4gEQD4B4AMQDIB4A8QCItwxg+oECDT8QMT1AAAgAASAABIAAEAACQAAIAAEgAASAANAv+gDxVDRR1CVqRAAAAABJRU5ErkJggg==) - -`0xce0042B868300000d44A59004Da54A005ffdcf9f` - -The contract has the address above for every chain on which it is deployed. -### ABI for SingletonFactory: -```json -[ - { - "constant": false, - "inputs": [ - { - "internalType": "bytes", - "name": "_initCode", - "type": "bytes" - }, - { - "internalType": "bytes32", - "name": "_salt", - "type": "bytes32" - } - ], - "name": "deploy", - "outputs": [ - { - "internalType": "address payable", - "name": "createdContract", - "type": "address" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - } -] -``` - -## Rationale - -SingletonFactory does not allow sending value on create2, this was done to prevent different results on the created object. -SingletonFactory allows user defined salt to facilitate the creation of vanity addresses for other projects. If vanity address is not necessary, salt `bytes(0)` should be used. -Contracts that are constructed by the SingletonFactory MUST not use `msg.sender` in their constructor, all variables must came through initialization data. This is intentional, as if allowing a callback after creation to aid initialization state would lead to contracts with same address (but different chains) to have the same address but different initial state. -The resulting address can be calculated in chain by any contract using this formula: `address(keccak256(bytes1(0xff), 0xce0042B868300000d44A59004Da54A005ffdcf9f, _salt, keccak256(_code)) << 96)` or in javascript using https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/README.md#const-generateaddress2. - -## Backwards Compatibility - -Does not apply as there are no past versions of Singleton Factory being used. - -## Test Cases - -TBD - -## Implementation - -https://github.com/3esmit/ERC2470 - -## Security Considerations - -Some contracts can possibly not support being deployed on any chain, or require a different address per chain, that can be safely done by using comparison in [EIP-1344] in constructor. -Account contracts are singletons in the point of view of each user, when wallets want to signal what chain id is intended, [EIP-1191] should be used. -Contracts deployed on factory must not use `msg.sender` in constructor, instead use constructor parameters, otherwise the factory would end up being the controller/only owner of those. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[EIP-155]: ./eip-155.md -[EIP-1191]: ./eip-1191.md -[EIP-1344]: ./eip-1344.md -[EIP-1820]: ./eip-1820.md -[EIP-2429]: https://gitlab.com/status-im/docs/EIPs/blob/secret-multisig-recovery/EIPS/eip-2429.md -[Nick's article]: https://medium.com/@weka/how-to-send-ether-to-11-440-people-187e332566b7 -[Nick]: https://github.com/Arachnid/ - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2470.md diff --git a/EIPS/eip-2477.md b/EIPS/eip-2477.md index 6a76dc903eb23b..21525dfa901593 100644 --- a/EIPS/eip-2477.md +++ b/EIPS/eip-2477.md @@ -1,325 +1 @@ ---- -eip: 2477 -title: Token Metadata Integrity -author: Kristijan Sedlak (@xpepermint), William Entriken , Witek Radomski -discussions-to: https://github.com/ethereum/EIPs/issues/2483 -type: Standards Track -category: ERC -status: Stagnant -created: 2020-01-02 -requires: 165, 721, 1155 ---- - -## Simple Summary - -This specification defines a mechanism by which clients may verify that a fetched token metadata document has been delivered without unexpected manipulation. - -This is the Web3 counterpart of the W3C Subresource Integrity (SRI) specification. - -## Abstract - -An interface `ERC2477` with two functions `tokenURIIntegrity` and `tokenURISchemaIntegrity` are specified for smart contracts and a narrative is provided to explain how this improves the integrity of the token metadata documents. - -## Motivation - -Tokens are being used in many applications to represent, trace and provide access to assets off-chain. These assets include in-game digital items in mobile apps, luxury watches and products in our global supply chain, among many other creative uses. - -Several token standards allow attaching metadata to specific tokens using a URI (RFC 3986) and these are supported by the applications mentioned above. These metadata standards are: - -* ERC-721 metadata extension (`ERC721Metadata`) -* ERC-1155 metadata extension (`ERC1155Metadata_URI`) -* ERC-1046 (DRAFT) ERC-20 Metadata Extension - -Although all these standards allow storing the metadata entirely on-chain (using the "data" URI, RFC 2397), or using a content-addressable system (e.g. IPFS's Content IDentifiers [sic]), nearly every implementation we have found is using Uniform Resource Locators (the exception is The Sandbox which uses IPFS URIs). These URLs provide no guarantees of content correctness or immutability. This standard adds such guarantees. - -## Design - -**Approach A:** A token contract may reference metadata by using its URL. This provides no integrity protection because the referenced metadata and/or schema could change at any time if the hosted content is mutable. This is the world before EIP-2477: - -``` -┌───────────────────────┐ ┌────────┐ ┌────────┐ -│ TokenID │──────▶│Metadata│─────▶│ Schema │ -└───────────────────────┘ └────────┘ └────────┘ -``` - -Note: according to the JSON Schema project, a metadata document referencing a schema using a URI in the `$schema` key is a known approach, but it is not standardized. - -**Approach B:** EIP-2477 provides mechanisms to establish integrity for these references. In one approach, there is integrity for the metadata document. Here, the on-chain data includes a hash of the metadata document. The metadata may or may not reference a schema. In this approach, changing the metadata document will require updating on-chain `tokenURIIntegrity`: - -``` -┌───────────────────────┐ ┌────────┐ ┌ ─ ─ ─ ─ -│ TokenID │──────▶│Metadata│─ ─ ─▶ Schema │ -└───────────────────────┘ └────────┘ └ ─ ─ ─ ─ -┌───────────────────────┐ ▲ -│ tokenURIIntegrity │════════════╝ -└───────────────────────┘ -``` - -**Approach C:** In a stronger approach, the schema is referenced by the metadata using an extension to JSON Schema, providing integrity. In this approach, changing the metadata document or the schema will require updating on-chain `tokenURIIntegrity` and the metadata document, additionally changing the schema requires updating the on-chain `tokenURISchemaIntegrity`: - -``` -┌───────────────────────┐ ┌────────┐ ┌────────┐ -│ TokenID │──────▶│Metadata│═════▶│ Schema │ -└───────────────────────┘ └────────┘ └────────┘ -┌───────────────────────┐ ▲ -│ tokenURIIntegrity │════════════╝ -└───────────────────────┘ -``` - -**Approach D:** Equally strong, the metadata can make a normal reference (no integrity protection) to the schema and on-chain data also includes a hash of the schema document. In this approach, changing the metadata document will require updating on-chain `tokenURIIntegrity` and updating the schema document will require updating the `tokenURISchemaIntegrity`: - -``` -┌───────────────────────┐ ┌────────┐ ┌────────┐ -│ TokenID │──────▶│Metadata│─────▶│ Schema │ -└───────────────────────┘ └────────┘ └────────┘ -┌───────────────────────┐ ▲ ▲ -│ tokenURIIntegrity │════════════╝ ║ -└───────────────────────┘ ║ -┌───────────────────────┐ ║ -│tokenURISchemaIntegrity│════════════════════════════╝ -└───────────────────────┘ -``` - -**Approach E:** Lastly, the schema can be referenced with integrity from the metadata and also using on-chain data. In this approach, changing the metadata document or the schema will require updating on-chain `tokenURIIntegrity` and the metadata document, additionally changing the schema requires updating the on-chain `tokenURISchemaIntegrity`: - -``` -┌───────────────────────┐ ┌────────┐ ┌────────┐ -│ TokenID │──────▶│Metadata│═════▶│ Schema │ -└───────────────────────┘ └────────┘ └────────┘ -┌───────────────────────┐ ▲ ▲ -│ tokenURIIntegrity │════════════╝ ║ -└───────────────────────┘ ║ -┌───────────────────────┐ ║ -│tokenURISchemaIntegrity│════════════════════════════╝ -└───────────────────────┘ -``` - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Smart contracts - -**Smart contracts implementing the ERC-2477 standard MUST implement the `ERC2477` interface.** - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.7; - -/// @title ERC-2477 Token Metadata Integrity -/// @dev See https://eips.ethereum.org/EIPS/eip-2477 -/// @dev The ERC-165 identifier for this interface is 0x832a7e0e -interface ERC2477 /* is ERC165 */ { - /// @notice Get the cryptographic hash of the specified tokenID's metadata - /// @param tokenId Identifier for a specific token - /// @return digest Bytes returned from the hash algorithm, or "" if not available - /// @return hashAlgorithm The name of the cryptographic hash algorithm, or "" if not available - function tokenURIIntegrity(uint256 tokenId) external view returns(bytes memory digest, string memory hashAlgorithm); - - /// @notice Get the cryptographic hash for the specified tokenID's metadata schema - /// @param tokenId Identifier for a specific token - /// @return digest Bytes returned from the hash algorithm, or "" if not available - /// @return hashAlgorithm The name of the cryptographic hash algorithm, or "" if not available - function tokenURISchemaIntegrity(uint256 tokenId) external view returns(bytes memory digest, string memory hashAlgorithm); -} -``` - -The returned cryptographic hashes correspond to the token's metadata document and that metadata document's schema, respectively. - -For example, with ERC-721 `tokenURIIntegrity(21)` would correspond to `tokenURI(21)`. With ERC-1155, `tokenURIIntegrity(16)` would correspond to `uri(16)`. In both cases, `tokenURISchemaIntegrity(32)` would correspond to the schema of the document matched by `tokenURIIntegrity(32)`. - -**Smart contracts implementing the ERC-2477 standard MUST implement the ERC-165 standard, including the interface identifiers above.** - -Smart contracts implementing the ERC-2477 standard MAY use any hashing or content integrity scheme. - -Smart contracts implementing the ERC-2477 standard MAY use or omit a mechanism to notify when the integrity is updated (e.g. an Ethereum logging operation). - -Smart contracts implementing the ERC-2477 standard MAY use any mechanism to provide schemas for metadata documents and SHOULD use JSON-LD on the metadata document for this purpose (i.e. `"@schema":...`). - -### Metadata - -A metadata document MAY conform to this schema to provide referential integrity to its schema document. - -```json -{ - "title": "EIP-2477 JSON Object With Refererential Integrity to Schema", - "type": "object", - "properties": { - "$schema": { - "type": "string", - "format": "uri" - }, - "$schemaIntegrity": { - "type": "object", - "properties": { - "digest": { - "type": "string" - }, - "hashAlgorithm": { - "type": "string" - } - }, - "required": ["digest", "hashAlgorithm"] - } - }, - "required": ["$schema", "$schemaIntegrity"] -} -``` - -### Clients - -A client implementing the ERC-2477 standard MUST support at least the `sha256` hash algorithm and MAY support other algorithms. - -### Caveats - -* This EIP metadata lists ERC-721 and ERC-1155 as "required" for implementation, due to a technical limitation of EIP metadata. In actuality, this standard is usable with any token implementation that has a `tokenURI(uint id)` or similar function. - -## Rationale - -**Function and parameter naming** - -The W3C Subresource Integrity (SRI) specification uses the attribute "integrity" to perform integrity verification. This ERC-2477 standard provides a similar mechanism and reuses the integrity name so as to be familiar to people that have seen SRI before. - -**Function return tuple** - -The SRI integrity attribute encodes elements of the tuple $$(cryptographic\ hash\ function, digest, options)$$. This ERC-2477 standard returns a digest and hash function name and omits forward-compatibility options. - -Currently, the SRI specification does not make use of options. So we cannot know what format they might be when implemented. This is the motivation to exclude this parameter. - -The digest return value is first, this is an optimization because we expect on-chain implementations will be more likely to use this return value if they will only be using one of the two. - -**Function return types** - -The digest is a byte array and supports various hash lengths. This is consistent with SRI. Whereas SRI uses base64 encoding to target an HTML document, we use a byte array because Ethereum already allows this encoding. - -The hash function name is a string. Currently there is no universal taxonomy of hash function names. SRI recognizes the names `sha256`, `sha384` and `sha512` with case-insensitive matching. We are aware of two authorities which provide taxonomies and canonical names for hash functions: ETSI Object Identifiers and NIST Computer Security Objects Register. However, SRI's approach is easier to follow and we have adopted this here. - -**Function return type — hash length** - -Clients must support the SHA-256 algorithm and may optionally support others. This is a departure from the SRI specification where SHA-256, SHA-384 and SHA-512 are all required. The rationale for this less-secure requirement is because we expect some clients to be on-chain. Currently SHA-256 is simple and cheap to do on Ethereum whereas SHA-384 and SHA-512 are more expensive and cumbersome. - -The most popular hash function size below 256 bits in current use is SHA-1 at 160 bits. Multiple collisions (the "Shattered" PDF file, the 320 byte file, the chosen prefix) have been published and a recipe is given to generate infinitely more collisions. SHA-1 is broken. The United States National Institute of Standards and Technology (NIST) has first deprecated SHA-1 for certain use cases in November 2015 and has later further expanded this deprecation. - -The most popular hash function size above 256 bits in current use is SHA-384 as specified by NIST. - -The United States National Security Agency requires a hash length of 384 or more bits for the SHA-2 (CNSA Suite Factsheet) algorithm suite for use on TOP SECRET networks. (No unclassified documents are currently available to specify use cases at higher classification networks.) - -We suspect that SHA-256 and the 0xcert Asset Certification will be popular choices to secure token metadata for the foreseeable future. - -**In-band signaling** - -One possible way to achieve strong content integrity with the existing token standards would be to include, for example, a `?integrity=XXXXX` at the end of all URLs. This approach is not used by any existing implementations we know about. There are a few reasons we have not chosen this approach. The strongest reason is that the World Wide Web has the same problem and they chose to use the Sub-Resource Integrity approach, which is a separate data field than the URL. - -Other supplementary reasons are: - -* For on-chain consumers of data, it is easier to parse a direct hash field than to perform string operations. - -* Maybe there are some URIs which are not amenable to being modified in that way, therefore limiting the generalizability of that approach. - -This design justification also applies to `tokenURISchemaIntegrity`. The current JSON-LD specification allows a JSON document to link to a schema document. But it does not provide integrity. Rather than changing how JSON-LD works, or changing JSON Schemas, we have the `tokenURISchemaIntegrity` property to just provide the integrity. - -## Backwards Compatibility - -Both ERC-721 and ERC-1155 provide compatible token metadata specifications that use URIs and JSON schemas. The ERC-2477 standard is compatible with both, and all specifications are additive. Therefore, there are no backward compatibility regressions. - -ERC-1523 Standard for Insurance Policies as ERC-721 Non Fungible Tokens (DRAFT) proposes an extension to ERC-721 which also tightens the requirements on metadata. Because it is wholly an extension of ERC-721, ERC-1523 is automatically supported by ERC-2477 (since this standard already supports ERC-721). - -ERC-1046 (DRAFT) ERC-20 Metadata Extension proposes a comparate extension for ERC-20. Such a concept is outside the scope of this ERC-2477 standard. Should ERC-1046 (DRAFT) be finalized, we will welcome a new ERC which copies ERC-2477 and removes the `tokenId` parameter. - -Similarly, ERC-918 (DRAFT) Mineable Token Standard proposes an extension for ERC-20 and also includes metadata. The same comment applies here as ERC-1046. - -## Test Cases - -Following is a token metadata document which is simultaneously compatible with ERC-721, ERC-1155 and ERC-2477 standards. - -```json -{ - "$schema": "https://URL_TO_SCHEMA_DOCUMENT", - "name": "Asset Name", - "description": "Lorem ipsum...", - "image": "https://s3.amazonaws.com/your-bucket/images/{id}.png" -} -``` - -This above example shows how JSON-LD is employed to reference the schema document (`$schema`). - -Following is a corresponding schema document which is accessible using the URI `"https://URL_TO_SCHEMA_DOCUMENT"` above. - -```json -{ - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - } - } -} -``` - -Assume that the metadata and schema above apply to a token with identifier 1234. (In ERC-721 this would be a specific token, in ERC-1155 this would be a token type.) Then these two function calls MAY have the following output: - -* `function tokenURIIntegrity(1234)` - * `bytes digest `: `3fc58b72faff20684f1925fd379907e22e96b660` - * `string hashAlgorithm`: `sha256` -* `function tokenURISchemaIntegrity(1234)` - * `bytes digest `: `ddb61583d82e87502d5ee94e3f2237f864eeff72` - * `string hashAlgorithm`: `sha256` - -To avoid doubt: the previous paragraph specifies "MAY" have that output because other hash functions are also acceptable. - -## Implementation - -0xcert Framework supports ERC-2477. - -## Reference - -Normative standard references - -1. RFC 2119 Key words for use in RFCs to Indicate Requirement Levels. https://www.ietf.org/rfc/rfc2119.txt -2. ERC-165 Standard Interface Detection. ./eip-165.md -3. ERC-721 Non-Fungible Token Standard. ./eip-721.md -4. ERC-1155 Multi Token Standard. ./eip-1155.md -5. JSON-LD. https://www.w3.org/TR/json-ld/ -6. Secure Hash Standard (SHS). https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf - -Other standards - -1. ERC-1046 ERC-20 Metadata Extension (DRAFT). ./eip-1046.md -2. ERC-918 Mineable Token Standard (DRAFT). ./eip-918.md -3. ERC-1523 Standard for Insurance Policies as ERC-721 Non Fungible Tokens (DRAFT). ./eip-1523.md -4. W3C Subresource Integrity (SRI). https://www.w3.org/TR/SRI/ -5. The "data" URL scheme. https://tools.ietf.org/html/rfc2397 -6. Uniform Resource Identifier (URI): Generic Syntax. https://tools.ietf.org/html/rfc3986 -7. CID [Specification] (DRAFT). https://github.com/multiformats/cid - -Discussion - -1. JSON-LD discussion of referential integrity. https://lists.w3.org/Archives/Public/public-json-ld-wg/2020Feb/0003.html -2. JSON Schema use of `$schema` key for documents. https://github.com/json-schema-org/json-schema-spec/issues/647#issuecomment-417362877 - -Other - -1. [0xcert Framework supports ERC-2477]. https://github.com/0xcert/framework/pull/717 -2. [Shattered] The first collision for full SHA-1. https://shattered.io/static/shattered.pdf -3. [320 byte file] The second SHA Collision. https://privacylog.blogspot.com/2019/12/the-second-sha-collision.html -4. [Chosen prefix] https://sha-mbles.github.io -5. Transitions: Recommendation for Transitioning the Use of Cryptographic Algorithms and Key Lengths. (Rev. 1. Superseded.) https://csrc.nist.gov/publications/detail/sp/800-131a/rev-1/archive/2015-11-06 -6. Commercial National Security Algorithm (CNSA) Suite Factsheet. https://apps.nsa.gov/iaarchive/library/ia-guidance/ia-solutions-for-classified/algorithm-guidance/commercial-national-security-algorithm-suite-factsheet.cfm -7. ETSI Assigned ASN.1 Object Identifiers. https://portal.etsi.org/pnns/oidlist -8. Computer Security Objects Register. https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration -9. The Sandbox implementation. https://github.com/pixowl/sandbox-smart-contracts/blob/7022ce38f81363b8b75a64e6457f6923d91960d6/src/Asset/ERC1155ERC721.sol - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2477.md diff --git a/EIPS/eip-2494.md b/EIPS/eip-2494.md index 0fa1a28e990a16..684eba99e6741c 100644 --- a/EIPS/eip-2494.md +++ b/EIPS/eip-2494.md @@ -1,382 +1 @@ ---- -eip: 2494 -title: Baby Jubjub Elliptic Curve -author: Barry WhiteHat (@barryWhiteHat), Marta Bellés (@bellesmarta), Jordi Baylina (@jbaylina) -discussions-to: https://ethereum-magicians.org/t/eip-2494-baby-jubjub-elliptic-curve/3968 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-01-29 ---- - -## Simple Summary - -This proposal defines Baby Jubjub, an elliptic curve designed to work inside zk-SNARK circuits in Ethereum. - -## Abstract - -Two of the main issues behind why blockchain technology is not broadly used by individuals and industry are scalability and privacy guarantees. With a set of cryptographic tools called zero-knowledge proofs (ZKP) it is possible to address both of these problems. More specifically, the most suitable protocols for blockchain are called zk-SNARKs (zero-knowledge Succinct Non-interactive ARguments of Knowledge), as they are non-interactive, have succinct proof size and sublinear verification time. These types of protocols allow proving generic computational statements that can be modelled with arithmetic circuits defined over a finite field (also called zk-SNARK circuits). - -To verify a zk-SNARK proof, it is necessary to use an elliptic curve. In Ethereum, the curve is alt_bn128 (also referred as BN254), which has primer order `r`. With this curve, it is possible to generate and validate proofs of any `F_r`-arithmetic circuit. This EIP describes *Baby Jubjub*, an elliptic curve defined over the finite field `F_r` which can be used inside any zk-SNARK circuit, allowing for the implementation of cryptographic primitives that make use of elliptic curves, such as the Pedersen Hash or the Edwards Digital Signature Algorithm (EdDSA). - -## Motivation - -A [zero knowledge proof](https://en.wikipedia.org/wiki/Zero-knowledge_proof) (ZKP) is a protocol that enables one party, the prover, to convince another, the verifier, that a statement is true without revealing any information beyond the veracity of the statement. [Non-Interactive ZKPs](https://people.csail.mit.edu/silvio/Selected%20Scientific%20Papers/Zero%20Knowledge/Noninteractive_Zero-Knowkedge.pdf) (NIZK) are a particular type of zero-knowledge proofs in which the prover can generate the proof without interaction with the verifier. NIZK protocols are very suitable for Ethereum applications, because they allow a smart contract to act as a verifier. This way, anyone can generate a proof and send it as part of a transaction to the smart contract, which can perform some action depending on whether the proof is valid or not. In this context, the most preferable NIZK are [zk-SNARK](https://eprint.iacr.org/2013/279.pdf) (Zero-knowledge Succinct Non Interactive ARgument of Knowledge), a set of non-interactive zero-knowledge protocols that have succinct proof size and sublinear verification time. The importance of these protocols is double: on the one hand, they help improve privacy guarantees, and on the other, they are a possible solution to scalability issues (e.g. see [zk-Rollup](https://github.com/barryWhiteHat/roll_up) project). - -Like most ZKPs, zk-SNARKs permit proving computational statements. For example, one can prove things like: the knowledge of a private key associated with a certain public key, the correct computation of a transaction, or the knowledge of the preimage of a particular hash. Importantly, one can do these things without leaking any information about the statements in question. In other words, without leaking any information about the private key, the transaction details, or the value of the preimage. More specifically, zk-SNARKs permit proving any computational statement that can be modelled with an `F_r`-arithmetic circuit, a circuit consisting of set of wires that carry values from the field `F_r` and connect them to addition and multiplication gates `mod r`. This type of circuits are often called zk-SNARK circuits. - -The implementation of most zk-SNARK protocols (e.g. [[Pinnochio]](https://eprint.iacr.org/2013/279.pdf) and [[Groth16]](https://eprint.iacr.org/2016/260.pdf)) make use of an elliptic curve for validating a proof. In Ethereum, the curve used is alt_bn128 (also referred as BN254), which has prime order `r`. While it is possible to generate and validate proofs of `F_r`-arithmetic circuits with BN254, it is not possible to use BN254 to implement elliptic-curve cryptography within these circuits. To implement functions that require the use of elliptic curves inside a zk-SNARK circuit -- such as the [Pedersen Hash](https://github.com/zcash/zips/blob/master/protocol/protocol.pdf) or the [Edwards Digital Signature Algorithm](https://tools.ietf.org/html/rfc8032) (EdDSA) -- a new curve with coordinates in `F_r` is needed. To this end, we propose in this EIP *Baby Jubjub*, an elliptic curve defined over `F_r` that can be used inside any `F_r`-arithmetic circuit. In the next sections we describe in detail the characteristics of the curve, how it was generated, and which security considerations were taken. - -``` - inputs zk-SNARK (alt_bn128) output - +--------------------------------------------+ - | +--------------------+ | - --->| | EdDSA (Baby Jubjub)| | - | +--------------------+ | - --->| |---> - | +-----------------------------+ | - --->| | Pedersen Hash (Baby Jubjub) | | - | +-----------------------------+ | - +--------------------------------------------+ -``` - -## Specification - -### Definitions -Let `F_r` be the prime finite field with `r` elements, where -``` -r = 21888242871839275222246405745257275088548364400416034343698204186575808495617 -``` - -Let `E` be the twisted Edwards elliptic curve defined over `F_r` described by equation -``` -ax^2 + y^2 = 1 + dx^2y^2 -``` -with parameters -``` -a = 168700 -d = 168696 -``` -We call **Baby Jubjub** the curve `E(F_r)`, that is, the subgroup of `F_r`-rational points of `E`. - -### Order - -Baby Jubjub has order - -``` -n = 21888242871839275222246405745257275088614511777268538073601725287587578984328 -``` - -which factors in -``` -n = h x l -``` -where -``` -h = 8 -l = 2736030358979909402780800718157159386076813972158567259200215660948447373041 -``` -The parameter `h` is called *cofactor* and `l` is a prime number of 251 bits. - -### Generator Point - -The point `G = (x,y)` with coordinates -``` -x = 995203441582195749578291179787384436505546430278305826713579947235728471134 -y = 5472060717959818805561601436314318772137091100104008585924551046643952123905 -``` -generates all `n` points of the curve. - -### Base Point - -The point `B = (x,y)` with coordinates - -``` -x = 5299619240641551281634865583518297030282874472190772894086521144482721001553 -y = 16950150798460657717958625567821834550301663161624707787222815936182638968203 -``` -generates the subgroup of points `P` of Baby Jubjub satisfying `l * P = O`. That is, it generates the set of points of order `l` and origin `O`. - -### Arithmetic - -Let `P1 = (x1, y1)` and `P2 = (x2, y2)` be two arbitrary points of Baby Jubjub. Then `P1 + P2 = (x3, y3)` is calculated in the following way: -``` -x3 = (x1*y2 + y1*x2)/(1 + d*x1*x2*y1*y2) -y3 = (y1*y2 - a*x1*x2)/(1 - d*x1*x2*y1*y2) -``` -Note that both addition and doubling of points can be computed using a single formula. - -## Rationale - -The search for Baby Jubjub was motivated by the need for an elliptic curve that allows the implementation of elliptic-curve cryptography in `F_r`-arithmetic circuits. The curve choice was based on three main factors: type of curve, generation process and security criteria. This section describes how these factors were addressed. - -**Form of the Curve** - -Baby Jubjub is a **twisted Edwards** curve birationally equivalent to a **Montgomery** curve. The choice of this form of curve was based on the following facts: -1. The Edwards-curve Digital Signature Scheme is based on twisted Edwards curves. -2. Twisted Edwards curves have a single complete formula for addition of points, which makes the implementation of the group law inside circuits very efficient [[Crypto08/013, Section 6]](https://eprint.iacr.org/2008/013.pdf). -3. As a twisted Edwards curve is generally birationally equivalent to a Montgomery curve [[Crypto08/13,Theorem 3.2]](https://eprint.iacr.org/2008/013.pdf), the curve can be easily converted from one form to another. As addition and doubling of points in a Montgomery curve can be performed very efficiently, computations outside the circuit can be done faster using this form and sped up inside circuits by combining it with twisted Edwards form (see [here](http://hyperelliptic.org/EFD/g1p/index.html)) for more details). - -**Generation of the Curve** - -Baby Jubjub was conceived as a solution to the circuit implementation of cryptographic schemes that require elliptic curves. As with any cryptographic protocol, it is important to reduce the possibility of a backdoor being present. As a result, we designed the generation process to be **transparent** and **deterministic** -- in order to make it clear that no external considerations were taken into account, and to ensure that the process can be reproduced and followed by anyone who wishes to do so. - -The algorithm chosen for generating Baby Jubjub is based in the criteria defined in [[RFC7748, Appendix A.1]](https://tools.ietf.org/html/rfc7748) and can be found in [this github repository](https://github.com/barryWhiteHat/baby_jubjub). Essentially, the algorithm takes a prime number `p = 1 mod 4` and returns the lowest `A>0` such that `A-2` is a multiple of 4 and such that the set of solutions in `F_p` of `y^2 = x^3 + Ax^2 + x` defines a Montgomery curve with cofactor 8. - -Baby Jubjub was generated by running the algorithm with the prime - -`r = 21888242871839275222246405745257275088548364400416034343698204186575808495617`, - -which is the order of alt_bn128, the curve used to verify zk-SNARK proofs in Ethereum. The output of the algorithm was `A=168698`. Afterwards, the corresponding Montgomery curve was transformed into twisted Edwards form. Using SAGE libraries for curves, the order `n` of the curve and its factorization `n = 8*l` was calculated. - -- **Choice of generator** : the generator point `G` is the point of order `n` with smallest positive `x`-coordinate in `F_r`. -- **Choice of base point**: the base point `B` is chosen to be `B = 8*G`, which has order `l`. - -**Security Criteria** - -It is crucial that Baby Jubjub be safe against well-known attacks. To that end, we decided that the curve should pass [SafeCurves](https://safecurves.cr.yp.to/) security tests, as they are known for gathering the best known attacks against elliptic curves. Supporting evidence that Baby Jubjub satisfies the SafeCurves criteria can be found [here](https://github.com/barryWhiteHat/baby_jubjub). - - -## Backwards Compatibility - -Baby Jubjub is a twisted Edwards elliptic curve birational to different curves. So far, the curve has mainly been used in its original form, in Montomgery form, and in another (different representation) twisted Edwards form -- which we call the reduced twisted Edwards form. - -Below are the three representations and the birational maps that make it possible to map points from one form of the curve to another. In all cases, the generator and base points are written in the form **`(x,y)`.** - -### Forms of the Curve - -All generators and base points are written in the form (x,y). - -**Twisted Edwards Form** (standard) - -- Equation: ``ax^2 + y^2 = 1 + dx^2y^2`` -- Parameters: ``a = 168700, d = 168696`` -- Generator point: - ``` - (995203441582195749578291179787384436505546430278305826713579947235728471134, 5472060717959818805561601436314318772137091100104008585924551046643952123905) - ``` -- Base point: - ``` - (5299619240641551281634865583518297030282874472190772894086521144482721001553, 16950150798460657717958625567821834550301663161624707787222815936182638968203) - ``` - -**Montgomery Form** - -- Equation: ``By^2 = x^3 + A x^2 + x`` -- Parameters: ``A = 168698, B = 1`` -- Generator point: - ``` - (7, 4258727773875940690362607550498304598101071202821725296872974770776423442226) - ``` -- Base point: - ``` - (7117928050407583618111176421555214756675765419608405867398403713213306743542, 14577268218881899420966779687690205425227431577728659819975198491127179315626) - ``` - -**Reduced Twisted Edwards Form** - -- Equation: ``a' x^2 + y^2 = 1 + d' x^2y^2`` -- Parameters: - ``` - a' = -1 - d' = 12181644023421730124874158521699555681764249180949974110617291017600649128846 - ``` -- Generator point: - ``` - (4986949742063700372957640167352107234059678269330781000560194578601267663727, 5472060717959818805561601436314318772137091100104008585924551046643952123905) - ``` -- Base point: - ``` - (9671717474070082183213120605117400219616337014328744928644933853176787189663, 16950150798460657717958625567821834550301663161624707787222815936182638968203) - ``` - -### Conversion of Points - -Following formulas allow to convert points from one form of the curve to another. We will denote the coordinates - -* ``(u, v)`` for points in the Montomgery form, -* ``(x, y)`` for points in the Twisted Edwards form and -* ``(x', y')`` for points in reduced Twisted Edwards form. - -Note that in the last conversion -- from Twisted Edwards to Reduced Twisted Edwards and back -- we also use the scaling factor `f`, where: -``` -f = 6360561867910373094066688120553762416144456282423235903351243436111059670888 -``` -In the expressions one can also use directly `-f`, where: -``` --f = 15527681003928902128179717624703512672403908117992798440346960750464748824729 -``` - -**Montgomery --> Twisted Edwards** -``` -(u, v) --> (x, y) - -x = u/v -y = (u-1)/(u+1) -``` - -**Twisted Edwards --> Montgomery** -``` -(x, y) --> (u, v) - -u = (1+y)/(1-y) -v = (1+y)/((1-y)x) -``` - -**Montgomery --> Reduced Twisted Edwards** -``` -(u, v) --> (x', y') - -x' = u*(-f)/v -y' = (u-1)/(u+1) -``` - -**Reduced Twisted Edwards --> Montgomery** -``` -(x', y') --> (u, v) - -u = (1+y')/(1-y') -v = (-f)*(1+y')/((1-y')*x') -``` - -**Twisted Edwards --> Reduced Twisted Edwards** -``` -(x, y) --> (x', y') - -x' = x*(-f) -y' = y -``` - -**Reduced Twisted Edwards --> Twisted Edwards** -``` -(x', y') --> (x, y) - -x = x'/(-f) -y = y' -``` -## Security Considerations - -This section specifies the safety checks done on Baby Jubjub. The choices of security parameters are based on [SafeCurves criteria](https://safecurves.cr.yp.to), and supporting evidence that Baby Jubjub satisfies the following requisites can be found [here](https://github.com/barryWhiteHat/baby_jubjub). - -**Curve Parameters** - -Check that all parameters in the specification of the curve describe a well-defined elliptic curve over a prime finite field. - -- The number `r` is prime. -- Parameters `a` and `d` define an equation that corresponds to an elliptic curve. -- The product of `h` and `l` results into the order of the curve and the `G` point is a generator. -- The number `l` is prime and the `B` point has order `l`. - -**Elliptic Curve Discrete Logarithm Problem** - -Check that the discrete logarithm problem remains difficult in the given curve. We checked Baby Jubjub is resistant to the following known attacks. - -- *Rho method* [[Blake-Seroussi-Smart, Section V.1]](https://www.cambridge.org/core/books/elliptic-curves-in-cryptography/16A2B60636EFA7EBCC3D5A5D01F28546): we require the cost for the rho method, which takes on average around `0.886*sqrt(l)` additions, to be above `2^100`. -- *Additive and multiplicative transfers* [[Blake-Seroussi-Smart, Section V.2]](https://www.cambridge.org/core/books/elliptic-curves-in-cryptography/16A2B60636EFA7EBCC3D5A5D01F28546): we require the embedding degree to be at least `(l − 1)/100`. -- *High discriminant* [[Blake-Seroussi-Smart, Section IX.3]](https://www.cambridge.org/core/books/elliptic-curves-in-cryptography/16A2B60636EFA7EBCC3D5A5D01F28546): we require the complex-multiplication field discriminant `D` to be larger than `2^100`. - -**Elliptic Curve Cryptography** - -- *Ladders* [[Montgomery]](https://wstein.org/edu/Fall2001/124/misc/montgomery.pdf): check the curve supports the Montgomery ladder. -- *Twists* [[SafeCurves, twist]](https://safecurves.cr.yp.to/twist.html): check it is secure against the small-subgroup attack, invalid-curve attacks and twisted-attacks. -- *Completeness* [[SafeCurves, complete]](https://safecurves.cr.yp.to/complete.html): check if the curve has complete single-scalar and multiple-scalar formulas. -- *Indistinguishability* [[IACR2013/325]](https://eprint.iacr.org/2013/325): check availability of maps that turn elliptic-curve points indistinguishable from uniform random strings. - -## Test Cases - -**Test 1 (Addition)** - -Consider the points ``P1 = (x1, y1)`` and ``P2 = (x2, y2)`` with the following coordinates: -``` -x1 = 17777552123799933955779906779655732241715742912184938656739573121738514868268 -y1 = 2626589144620713026669568689430873010625803728049924121243784502389097019475 - -x2 = 16540640123574156134436876038791482806971768689494387082833631921987005038935 -y2 = 20819045374670962167435360035096875258406992893633759881276124905556507972311 -``` -Then their sum `` P1+P2 = (x3, y3)`` is equal to: -``` -x3 = 7916061937171219682591368294088513039687205273691143098332585753343424131937 -y3 = 14035240266687799601661095864649209771790948434046947201833777492504781204499 -``` - -**Test 2 (Doubling)** - -Consider the points ``P1 = (x1, y1)`` and ``P2 = (x2, y2)`` with the following coordinates: -``` -x1 = 17777552123799933955779906779655732241715742912184938656739573121738514868268, -y1 = 2626589144620713026669568689430873010625803728049924121243784502389097019475 - -x2 = 17777552123799933955779906779655732241715742912184938656739573121738514868268 -y2 = 2626589144620713026669568689430873010625803728049924121243784502389097019475 -``` -Then their sum `` P1+P2 = (x3, y3)`` is equal to: -``` -x3 = 6890855772600357754907169075114257697580319025794532037257385534741338397365 -y3 = 4338620300185947561074059802482547481416142213883829469920100239455078257889 -``` - -**Test 3 (Doubling the identity)** - -Consider the points ``P1 = (x1, y1)`` and ``P2 = (x2, y2)`` with the following coordinates: -``` -x1 = 0 -y1 = 1 - -x2 = 0 -y2 = 1 -``` -Then their sum `` P1+P2 = (x3, y3)`` results in the same point: -``` -x3 = 0 -y3 = 1 -``` - -**Test 4 (Curve membership)** - -Point ``(0,1)`` is a point on Baby Jubjub. - -Point ``(1,0)`` is not a point on Baby Jubjub. - -**Test 5 (Base point choice)** - -Check that the base point `` B = (Bx, By)`` with coordinates - -``` -Bx = 5299619240641551281634865583518297030282874472190772894086521144482721001553 -By = 16950150798460657717958625567821834550301663161624707787222815936182638968203 -``` -is 8 times the generator point ``G = (Gx, Gy)``, where -``` -Gx = 995203441582195749578291179787384436505546430278305826713579947235728471134 -Gy = 5472060717959818805561601436314318772137091100104008585924551046643952123905 -``` -That is, check that ``B = 8 x G``. - -**Test 6 (Base point order)** - -Check that the base point `` B = (Bx, By)`` with coordinates - -``` -Bx = 5299619240641551281634865583518297030282874472190772894086521144482721001553 -By = 16950150798460657717958625567821834550301663161624707787222815936182638968203 -``` -multiplied by `l`, where -``` -l = 2736030358979909402780800718157159386076813972158567259200215660948447373041 -``` -results in the origin point `O = (0, 1)`. This test checks that the base point `B` has order `l`. - -## Implementation - -Arithmetic of Baby Jubjub and some cryptographic primitives using the curve have already been implemented in different languages. Here are a few such implementations: - -- Python: https://github.com/barryWhiteHat/baby_jubjub_ecc -- JavaScript: https://github.com/iden3/circomlib/blob/master/src/babyjub.js -- Circuit (circom): https://github.com/iden3/circomlib/blob/master/circuits/babyjub.circom -- Rust: https://github.com/arnaucube/babyjubjub-rs -- Solidity: https://github.com/yondonfu/sol-baby-jubjub -- Go: https://github.com/iden3/go-iden3-crypto/tree/master/babyjub - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2494.md diff --git a/EIPS/eip-2520.md b/EIPS/eip-2520.md index d72c1e920bf93e..8c1b12e94cd6dc 100644 --- a/EIPS/eip-2520.md +++ b/EIPS/eip-2520.md @@ -1,75 +1 @@ ---- -eip: 2520 -title: Multiple contenthash records for ENS -author: Filip Štamcar (@filips123) -discussions-to: https://github.com/ethereum/EIPs/issues/2393 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-02-18 -requires: 1577 ---- - -## Simple Summary -ENS support for multiple `contenthash` records on a single ENS name. - -## Motivation -Many applications are resolving ENS names to content hosted on distributed systems. To do this, they use `contenthash` record from ENS domain to know how to resolve names and which distributed system should be used. - -However, the domain can store only one `contenthash` record which means that the site owner needs to decide which hosting system to use. Because there are many ENS-compatible hosting systems available (IPFS, Swarm, recently Onion and ZeroNet), and there will probably be even more in the future, lack of support for multiple records could become problematic. Instead, domains should be able to store multiple `contenthash` records to allow applications to resolve to multiple hosting systems. - -## Specification -Setting and getting functions **MUST** have the same public interface as specified in EIP 1577. Additionally, they **MUST** also have new public interfaces introduced by this EIP: - -* For setting a `contenthash` record, the `setContenthash` **MUST** provide additional `proto` parameter and use it to save the `contenthash`. When `proto` is not provided, it **MUST** save the record as default record. - - ```solidity - function setContenthash(bytes32 node, bytes calldata proto, bytes calldata hash) external authorised(node); - ``` - -* For getting a `contenthash` record, the `contenthash` **MUST** provide additional `proto` parameter and use it to get the `contenthash` for requested type. When `proto` is not provided, it **MUST** return the default record. - - ```solidity - function contenthash(bytes32 node, bytes calldata proto) external view returns (bytes memory); - ``` - -* Resolver that supports multiple `contenthash` records **MUST** return `true` for `supportsInterface` with interface ID `0x6de03e07`. - -Applications that are using ENS `contenthash` records **SHOULD** handle them in the following way: - -* If the application only supports one hosting system (like directly handling ENS from IPFS/Swarm gateways), it **SHOULD** request `contenthash` with a specific type. The contract **MUST** then return it and application **SHOULD** correctly handle it. - -* If the application supports multiple hosting systems (like MetaMask), it **SHOULD** request `contenthash` without a specific type (like in EIP 1577). The contract **MUST** then return the default `contenthash` record. - -## Rationale -The proposed implementation was chosen because it is simple to implement and supports all important requested features. However, it doesn't support multiple records for the same type and priority order, as they don't give much advantage and are harder to implement properly. - -## Backwards Compatibility -The EIP is backwards-compatible with EIP 1577, the only differences are additional overloaded methods. Old applications will still be able to function correctly, as they will receive the default `contenthash` record. - -## Implementation -```solidity -contract ContentHashResolver { - bytes4 constant private MULTI_CONTENT_HASH_INTERFACE_ID = 0x6de03e07; - mapping(bytes32=>mapping(bytes=>bytes)) hashes; - - function setContenthash(bytes32 node, bytes calldata proto, bytes calldata hash) external { - hashes[node][proto] = hash; - emit ContenthashChanged(node, hash); - } - - function contenthash(bytes32 node, bytes calldata proto) external view returns (bytes memory) { - return hashes[node][proto]; - } - - function supportsInterface(bytes4 interfaceID) public pure returns(bool) { - return interfaceID == MULTI_CONTENT_HASH_INTERFACE_ID; - } -} -``` - -## Security Considerations -TBD - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2520.md diff --git a/EIPS/eip-2525.md b/EIPS/eip-2525.md index 71a285511154ec..5cc036f2d7898c 100644 --- a/EIPS/eip-2525.md +++ b/EIPS/eip-2525.md @@ -1,171 +1 @@ ---- -eip: 2525 -title: ENSLogin -author: Hadrien Croubois (@amxx) -discussions-to: https://ethereum-magicians.org/t/discussion-ens-login/3569 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-02-19 -requires: 137, 634, 1193, 2304 ---- - -## 1. Abstract - -This presents a method to improve a universal method of login to the ethereum blockchain, leveraging the metadata storage provided by the ENS. We consider a user to be logged in when we have an [EIP-1193](./eip-1193.md) provider that can sign transaction and messages on his behalf. This method is inspired by [Alex Van de Sande's work](https://www.youtube.com/watch?v=1LVwWknE-NQ) and [Web3Connect](https://web3connect.com). In the future, the approach described here-after should be extended to work with any blockchain. - -## 2. Motivation - -Multiple wallet solutions can be used to interact with the Ethereum blockchain. Some (metamask, gnosis, ...) are compatible as they inject a standardized wallet object in the browser without requiring any effort from the Dapp developers, but they require an effort on the user side (user has to install the plugin). Other solutions (Portis, Authereum, Torus, Universal Login, ...) propose a more seamless flow to non-crypto-aware users but require an integration effort from the Dapp developers. Hardware wallet (ledger, trezor, keepkey, ...) also require integration effort from the Dapp developers. - -When Dapps integrate login with multiple solutions, they rely on the user choosing the correct wallet-provider. This could prove increasingly difficult as the number of wallet-provider increases, particularly for novice users. Additionally, if decentralized applications pick and choose only a handful of wallets to support, the current incumbent wallets will have a distinct advantage and new wallets will struggle to find adoption. This will create a less competitive environment and stifle innovation. Rather than relying on the user choosing which wallet-provider to connect with (as does Web3Connect), ENSLogin proposes to use user-owned ENS domain as entry points. Metadata attached to these ENS domains is used to detect which wallet-provider if used by the corresponding account. - -That way, ENSLogin would allow any user to connect to any Dapp with any wallet, using a simple domain as a login. - -## 3. Description - -### 3.1. Overview - -The ENSLogin works as follow: - -* Request an ENS domain from the user -* Resolve the ENS domain to retrieve (see [EIP-137](./eip-137.md)) - * An address (see [EIP-137](./eip-137.md)) - * A text entry (see [EIP-634](./eip-634.md)) -* Interpret the text entry and download the file it points to -* Evaluate the content of the downloaded file -* Return the corresponding object to the Dapp - -At this point, the app should process like with any web3 provider. Calling the `enable()` functions should ask the users for wallet specific credentials is needed. - -This workflow is to be implemented by an SDK that Dapp could easily import. The SDK would contain the resolution mechanism and support for both centralized and decentralized storage solution. Wallet-provider specific code should NOT be part of SDK. Wallet-provider specific code should only be present in the external file used to generate the web3 provider. - -### 3.2. Details - -* **Text entry resolution:** A pointer to the code needed to instantiate the wallet-provider is recorded using the ENS support for text entries (see [EIP-634](./eip-634.md)). The corresponding key is `enslogin` (**subject to change**). If no value is associated with the key `enslogin` at the targeted domain, we fallback to metadata store on the parent's node with the key `enslogin-default` (**subject to change**). -**Example:** for the ens domain `username.domain.eth`, the resolution would look for (in order): - * `resolver.at(ens.owner(nodehash("username.domain.eth"))).text(nodehash("username.domain.eth"), 'enslogin')` - * `resolver.at(ens.owner(nodehash("domain.eth"))).text(nodehash("domain.eth"), 'enslogin-default')` - -* **Provider link:** Code for instantiating the wallet-provider must be pointed to in a standardized manner. **This is yet not specified.** The current approach uses a human-readable format `scheme://path` such as: - - * `ipfs://Qm12345678901234567890123456789012345678901234` - * `https://server.com/enslogin-module-someprovider` - - And adds a suffix depending on the targeted blockchain type (see [SLIP 44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md)) and language. Canonical case is a webapp using ethereum so the target would be: - - * `ipfs://Qm12345678901234567890123456789012345678901234/60/js` - * `https://server.com/enslogin-module-someprovider/60/js` - - Note that this suffix mechanism is compatible with http/https as well as IPFS. It is a constraint on the storage layer as some may not be able to do this kind of resolution. - -* **Provider instantiation:** - * [JAVASCRIPT/ETHEREUM] The file containing the wallet-provider's code should inject a function `global.provider: (config) => Promise` that returns a promise to a standardized provider object. For EVM blockchains, the object should follow [EIP-1193](./eip-1193.md). - * Other blockchain types/langages should be detailed in the future. - - -* **Configuration object:** In addition to the username (ENS domain), the Dapp should have the ability to pass a configuration object that could be used by the wallet-provider instantiating function. This configuration should include: - * A body (common to all provider) that specify details about the targeted chain (network name / node, address of the ens entrypoint ...). If any of these are missing, a fallback can be used (mainnet as a default network, bootstrapping an in-browser IPFS node, ...). - * Wallet provider-specific fields (**optional**, starting with one underscore `_`) can be added to pass additional, wallet-provider specific, parameters / debugging flags. - * SDK specific fields (**optional**, starting with two underscores `__`) can be used to pass additional arguments. - - Minimal configuration: - ``` - { - provider: { - network: 'goerli' - } - } - ``` - Example of advanced configuration object: - ``` - { - provider: { - network: 'goerli', - ens: '0x112234455c3a32fd11230c42e7bccd4a84e02010' - }, - ipfs: { - host: 'ipfs.infura.io', - port: 5001, - protocol: 'https' - }, - _authereum: {...}, - _portis: {...}, - _unilogin: {...}, - _torus: {...}, - __callbacks: { - resolved: (username, addr, descr) => { - console.log(`[CALLBACKS] resolved: ${username} ${addr} ${descr}`); - }, - loading: (protocol, path) => { - console.log(`[CALLBACKS] loading: ${protocol} ${path}`); - }, - loaded: (protocol, path) => { - console.log(`[CALLBACKS] loaded: ${protocol} ${path}`); - } - } - } - ``` - -**TODO** *(maybe move that part to section 6.1)*: -Add [SLIP 44](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) compliant blockchain description to the config for better multichain support. This will require a additional field `ENS network` to know which ethereum network to use for resolution when the targeted blockchain/network is not ethereum (could also be used for cross chain resolution on ethereum, for example xDAI login with metadata stored on mainnet) - -### 3.3. Decentralization - -Unlike solution like Web3Connect, ENSLogin proposes a modular approach that is decentralized by nature. -The code needed for a Dapp to use ENSLogin (hereafter referred to as the SDK) only contains lookup mechanism for the ethereum blockchain and the data storages solutions. The solution is limited by the protocols (https / ipfs / ...) that the SDK can interact with. Beyond that, any wallet-provider that follows the expected structure and that is available through one of the supported protocol is automatically compatible with all the Dapps proposing ENSLogin support. There is no need to go through a centralized approval process. Furthermore, deployed SDK do not need to be upgraded to benefit from the latest wallet updates. The only permissioned part of the protocol is in the ENS control of the users over the metadata that describes their wallet-provider implementation. Users could also rely on the fallback mechanism to have the wallet-provider update it for them. - -### 3.4. Incentives - -We believe ENSLogin's biggest strength is the fact that it aligns the incentives of Dapp developers and wallet-providers to follow this standard. - -* A wallet-provider that implements the required file and make them available will ensure the compatibility of its wallet with all Dapps using ENSLogin. This will remove the burden of asking all Dapps to integrate their solutions, which Dapps are unlikely to do until the wallet as strong userbase. Consequently, ENSLogin will improve the competition between wallet-providers and encourage innovation in that space -* A Dapp that uses ENSLogin protocol, either by including the ENSLogin's SDK or by implementing compatible behaviour, will make itself available to all the users of all the compatible wallet. At some point, being compatible with ENSLogin will be the easiest to reach a large user-base. -* ENSLogin should be mostly transparent for the users. Most wallet provider will set up the necessary entries without requiring any effort from the user. Advanced users can take control over the wallet resolution process, which will be simple once the right tooling is available. - -### 3.5. Drawbacks - -While ENSLogin allows dapps to support any wallet for logging in, dapps still must choose which wallets they suggest to users for registration. This can be done through a component like Web3Connect or BlockNative's - -## 4. Prototype - -**TODO** - -## 5. Support by the community - -### 5.1. Adoption - -| Name | Live | Module | Assigns ENS names | support by default | -| -------------- | ---- | ------ | ----------------- | ------------------ | -| Argent | yes | no | yes | no | -| Authereum | yes | yes | yes | no | -| Fortmatic | yes | no | no | no | -| Gnosis Safe | yes | yes\* | no | no | -| Ledger | yes | beta | no | no | -| KeepKey | yes | no | no | no | -| Metamask | yes | yes | no | no | -| Opera | yes | yes\* | no | no | -| Portis | yes | yes | no | no | -| SquareLink | yes | no | no | no | -| Shipl | no | no | no | no | -| Torus | yes | yes | no | no | -| Trezor | yes | no | no | no | -| UniLogin | beta | beta | yes | no | - -\*use the metamask module - -## 6. Possible evolutions - -### 6.1. Multichain support - -**TODO** - -## 7. FAQ - -### 7.1. Can anyone connect with my login? Where are my private keys stored? - -ENSLogin only has access to what is recorded on the ENS, namely your address and the provider you use. Private key management is a is handled by the provider and is outside ENSLogin's scope. Some might store the key on disk. Other might rely on custodial keys stored on a remote (hopefully secure) server. Others might use a dedicated hardware component to handle signature and never directly have access to the private key. - -### 7.2. How do I get an ENS Login? - -**TODO** (this might need a separate ERC) +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2525.md diff --git a/EIPS/eip-2535.md b/EIPS/eip-2535.md index d49098f35bcd6b..f4d9c6b4384f6d 100644 --- a/EIPS/eip-2535.md +++ b/EIPS/eip-2535.md @@ -1,435 +1 @@ ---- -eip: 2535 -title: Diamonds, Multi-Facet Proxy -description: Create modular smart contract systems that can be extended after deployment. -author: Nick Mudge (@mudgen) -discussions-to: https://ethereum-magicians.org/t/discussion-for-eip2535-diamonds/10459/ -status: Final -type: Standards Track -category: ERC -created: 2020-02-22 ---- - -## Abstract - -Diamonds contract structure - -This proposal standardizes diamonds, which are modular smart contract systems that can be upgraded/extended after deployment, and have virtually no size limit. More technically, a **diamond** is a contract with external functions that are supplied by contracts called **facets**. Facets are separate, independent contracts that can share internal functions, libraries, and state variables. - -## Motivation - -There are a number of different reasons to use diamonds. Here are some of them: - -1. **A single address for unlimited contract functionality.** Using a single address for contract functionality makes deployment, testing and integration with other smart contracts, software and user interfaces easier. -1. **Your contract exceeds the 24KB maximum contract size.** You may have related functionality that it makes sense to keep in a single contract, or at a single contract address. A diamond does not have a max contract size. -1. **A diamond provides a way to organize contract code and data.** You may want to build a contract system with a lot of functionality. A diamond provides a systematic way to isolate different functionality and connect them together and share data between them as needed in a gas-efficient way. -1. **A diamond provides a way to upgrade functionality.** Upgradeable diamonds can be upgraded to add/replace/remove functionality. Because diamonds have no max contract size, there is no limit to the amount of functionality that can be added to diamonds over time. Diamonds can be upgraded without having to redeploy existing functionality. Parts of a diamond can be added/replaced/removed while leaving other parts alone. -1. **A diamond can be immutable.** It is possible to deploy an immutable diamond or make an upgradeable diamond immutable at a later time. -1. **A diamond can reuse deployed contracts.** Instead of deploying contracts to a blockchain, existing already deployed, onchain contracts can be used to create diamonds. Custom diamonds can be created from existing deployed contracts. This enables the creation of on-chain smart contract platforms and libraries. - -This standard is an improvement of [EIP-1538](./eip-1538.md). The same motivations of that standard apply to this standard. - -A deployed facet can be used by any number of diamonds. - -The diagram below shows two diamonds using the same two facets. - -- `FacetA` is used by `Diamond1` -- `FacetA` is used by `Diamond2` -- `FacetB` is used by `Diamond1` -- `FacetB` is used by `Diamond2` - -Facet reuse - -### Upgradeable Diamond vs. Centralized Private Database - -Why have an upgradeable diamond instead of a centralized, private, mutable database? - -1. Decentralized Autonomous Organizations (DAOs) and other governance systems can be used to upgrade diamonds. -1. Wide interaction and integration with the Ethereum ecosystem. -1. With open storage data and verified source code it is possible to show a provable history of trustworthiness. -1. With openness bad behavior can be spotted and reported when it happens. -1. Independent security and domain experts can review the change history of contracts and vouch for their history of trustworthiness. -1. It is possible for an upgradeable diamond to become immutable and trustless. - -### Some Diamond Benefits - -1. A stable contract address that provides needed functionality. -1. A single address with the functionality of multiple contracts (facets) that are independent from each other but can share internal functions, libraries and state variables. -1. Emitting events from a single address can simplify event handling. -1. A way to add, replace and remove multiple external functions atomically (in the same transaction). -1. Fine-grained upgrades, so you can change just the parts of a diamond that need to be changed. -1. Have greater control over when and what functions exist. -1. Decentralized Autonomous Organizations (DAOs), multisig contracts and other governance systems can be used to upgrade diamonds. -1. An event that shows what functions are added, replaced and removed. -1. The ability to show all changes made to a diamond. -1. Increase trust over time by showing all changes made to a diamond. -1. A way to look at a diamond to see its current facets and functions. -1. Have an immutable, trustless diamond. -1. Solves the 24KB maximum contract size limitation. Diamonds can be any size. -1. Separate functionality can be implemented in separate facets and used together in a diamond. -1. Diamonds can be created from already deployed, existing onchain contracts. -1. Larger contracts have to reduce their size by removing error messages and other things. You can keep your full functionality that you need by implementing a diamond. -1. Enables zero, partial or full diamond immutability as desired, and when desired. -1. The ability to develop and improve an application over time with an upgradeable diamond and then make it immutable and trustless if desired. -1. Develop incrementally and let your diamond grow with your application. -1. Upgrade diamonds to fix bugs, add functionality and implement new standards. -1. Organize your code with a diamond and facets. -1. Diamonds can be large (have many functions) but still be modular because they are compartmented with facets. -1. Contract architectures that call multiple contracts in a single transaction can save gas by condensing those contracts into a single diamond and accessing state variables directly. -1. Save gas by converting external functions to internal functions. This done by sharing internal functions between facets. -1. Save gas by creating external functions for gas-optimized specific use cases, such as bulk transfers. -1. Diamonds are designed for tooling and user-interface software. - - -## Specification - -### Terms - -1. A **diamond** is a facade smart contract that `delegatecall`s into its facets to execute function calls. A diamond is stateful. Data is stored in the contract storage of a diamond. -1. A **facet** is a stateless smart contract or Solidity library with external functions. A facet is deployed and one or more of its functions are added to one or more diamonds. A facet does not store data within its own contract storage but it can define state and read and write to the storage of one or more diamonds. The term facet comes from the diamond industry. It is a side, or flat surface of a diamond. -1. A **loupe facet** is a facet that provides introspection functions. In the diamond industry, a loupe is a magnifying glass that is used to look at diamonds. -1. An **immutable function** is an external function that cannot be replaced or removed (because it is defined directly in the diamond, or because the diamond's logic does not allow it to be modified). -1. A **mapping** for the purposes of this EIP is an association between two things and does not refer to a specific implementation. - -The term **contract** is used loosely to mean a smart contract or deployed Solidity library. - -When this EIP uses **function** without specifying internal or external, it means external function. - -In this EIP the information that applies to external functions also applies to public functions. - -### Overview - -A diamond calls functions from its facets using `delegatecall`. - -In the diamond industry diamonds are created and shaped by being cut, creating facets. In this standard diamonds are cut by adding, replacing or removing functions from facets. - -### A Note on Implementing Interfaces - -Because of the nature of diamonds, a diamond can implement an interface in one of two ways: directly (`contract Contract is Interface`), or by adding functions to it from one or more facets. For the purposes of this proposal, when a diamond is said to implement an interface, either method of implementation is permitted. - -### Fallback Function - -When an external function is called on a diamond its fallback function is executed. The fallback function determines which facet to call based on the first four bytes of the call data (known as the function selector) and executes that function from the facet using `delegatecall`. - -A diamond's fallback function and `delegatecall` enable a diamond to execute a facet's function as if it was implemented by the diamond itself. The `msg.sender` and `msg.value` values do not change and only the diamond's storage is read and written to. - -Here is an illustrative example of how a diamond's fallback function might be implemented: - -```solidity -// Find facet for function that is called and execute the -// function if a facet is found and return any value. -fallback() external payable { - // get facet from function selector - address facet = selectorTofacet[msg.sig]; - require(facet != address(0)); - // Execute external function from facet using delegatecall and return any value. - assembly { - // copy function selector and any arguments - calldatacopy(0, 0, calldatasize()) - // execute function call using the facet - let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) - // get any return value - returndatacopy(0, 0, returndatasize()) - // return any return value or error back to the caller - switch result - case 0 {revert(0, returndatasize())} - default {return (0, returndatasize())} - } -} -``` - -This diagram shows the structure of a diamond: - -Mapping facets and storage - -### Storage - -A state variable or storage layout organizational pattern is needed because Solidity's builtin storage layout system doesn't support proxy contracts or diamonds. The particular layout of storage is not defined in this EIP, but may be defined by later proposals. Examples of storage layout patterns that work with diamonds are [Diamond Storage](../assets/eip-2535/storage-examples/DiamondStorage.sol) and [AppStorage](../assets/eip-2535/storage-examples/AppStorage.sol). - -Facets can share state variables by using the same structs at the same storage positions. Facets can share internal functions and libraries by inheriting the same contracts or using the same libraries. In these ways facets are separate, independent units but can share state and functionality. - -The diagram below shows facets with their own data and data shared between them. - -Notice that all data is stored in the diamond's storage, but different facets have different access to data. - -In this diagram - -- Only `FacetA` can access `DataA` -- Only `FacetB` can access `DataB` -- Only the diamond's own code can access `DataD`. -- `FacetA` and `FacetB` share access to `DataAB`. -- The diamond's own code, `FacetA` and `FacetB` share access to `DataABD`. - -Mapping code, data, and facets - -### Solidity Libraries as Facets - -Smart contracts or deployed Solidity libraries can be facets of diamonds. - -Only Solidity libraries that have one or more external functions can be deployed to a blockchain and be a facet. - -Solidity libraries that contain internal functions only cannot be deployed and cannot be a facet. Internal functions from Solidity libraries are included in the bytecode of facets and contracts that use them. Solidity libraries with internal functions only are useful for sharing internal functions between facets. - -Solidity library facets have a few properties that match their use as facets: -* They cannot be deleted. -* They are stateless. They do not have contract storage. -* Their syntax prevents declaring state variables outside Diamond Storage. - -### Adding/Replacing/Removing Functions - -#### `IDiamond` Interface - -All diamonds must implement the `IDiamond` interface. - -During the deployment of a diamond any immutable functions and any external functions added to the diamond must be emitted in the `DiamondCut` event. - -**A `DiamondCut` event must be emitted any time external functions are added, replaced, or removed.** This applies to all upgrades, all functions changes, at any time, whether through `diamondCut` or not. - -```solidity -interface IDiamond { - enum FacetCutAction {Add, Replace, Remove} - // Add=0, Replace=1, Remove=2 - - struct FacetCut { - address facetAddress; - FacetCutAction action; - bytes4[] functionSelectors; - } - - event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); -} -``` - -The `DiamondCut` event records all function changes to a diamond. - -#### `IDiamondCut` Interface - -A diamond contains within it a mapping of function selectors to facet addresses. Functions are added/replaced/removed by modifying this mapping. - -Diamonds should implement the `IDiamondCut` interface if after their deployment they allow modifications to their function selector mapping. - -The `diamondCut` function updates any number of functions from any number of facets in a single transaction. Executing all changes within a single transaction prevents data corruption which could occur in upgrades done over multiple transactions. - -`diamondCut` is specified for the purpose of interoperability. Diamond tools, software and user-interfaces should expect and use the standard `diamondCut` function. - -```solidity -interface IDiamondCut is IDiamond { - /// @notice Add/replace/remove any number of functions and optionally execute - /// a function with delegatecall - /// @param _diamondCut Contains the facet addresses and function selectors - /// @param _init The address of the contract or facet to execute _calldata - /// @param _calldata A function call, including function selector and arguments - /// _calldata is executed with delegatecall on _init - function diamondCut( - FacetCut[] calldata _diamondCut, - address _init, - bytes calldata _calldata - ) external; -} -``` - -The `_diamondCut` argument is an array of `FacetCut` structs. - -Each `FacetCut` struct contains a facet address and array of function selectors that are updated in a diamond. - -For each `FacetCut` struct: - - * If the `action` is `Add`, update the function selector mapping for each `functionSelectors` item to the `facetAddress`. If any of the `functionSelectors` had a mapped facet, revert instead. - * If the `action` is `Replace`, update the function selector mapping for each `functionSelectors` item to the `facetAddress`. If any of the `functionSelectors` had a value equal to `facetAddress` or the selector was unset, revert instead. - * If the `action` is `Remove`, remove the function selector mapping for each `functionSelectors` item. If any of the `functionSelectors` were previously unset, revert instead. - -Any attempt to replace or remove an immutable function must revert. - -Being intentional and explicit about adding/replacing/removing functions helps catch and prevent upgrade mistakes. - -##### Executing `_calldata` - -After adding/replacing/removing functions the `_calldata` argument is executed with `delegatecall` on `_init`. This execution is done to initialize data or setup or remove anything needed or no longer needed after adding, replacing and/or removing functions. - -If the `_init` value is `address(0)` then `_calldata` execution is skipped. In this case `_calldata` can contain 0 bytes or custom information. - -### Inspecting Facets & Functions - -> A loupe is a small magnifying glass used to look at diamonds. - -Diamonds must support inspecting facets and functions by implementing the `IDiamondLoupe` interface. - -#### `IDiamondLoupe` Interface - -```solidity -// A loupe is a small magnifying glass used to look at diamonds. -// These functions look at diamonds -interface IDiamondLoupe { - struct Facet { - address facetAddress; - bytes4[] functionSelectors; - } - - /// @notice Gets all facet addresses and their four byte function selectors. - /// @return facets_ Facet - function facets() external view returns (Facet[] memory facets_); - - /// @notice Gets all the function selectors supported by a specific facet. - /// @param _facet The facet address. - /// @return facetFunctionSelectors_ - function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); - - /// @notice Get all the facet addresses used by a diamond. - /// @return facetAddresses_ - function facetAddresses() external view returns (address[] memory facetAddresses_); - - /// @notice Gets the facet that supports the given selector. - /// @dev If facet is not found return address(0). - /// @param _functionSelector The function selector. - /// @return facetAddress_ The facet address. - function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); -} -``` - -See a [reference implementation](#reference-implementation) to see how this can be implemented. - -The loupe functions can be used in user-interface software. A user interface calls these functions to provide information about and visualize diamonds. - -The loupe functions can be used in deployment functionality, upgrade functionality, testing and other software. - -### Implementation Points - -A diamond must implement the following: - -1. A diamond contains a fallback function and zero or more immutable functions that are defined within it. -1. A diamond associates function selectors with facets. -1. When a function is called on a diamond it executes immediately if it is an "immutable function" defined directly in the diamond. Otherwise the diamond's fallback function is executed. The fallback function finds the facet associated with the function and executes the function using `delegatecall`. If there is no facet for the function then optionally a default function may be executed. If there is no facet for the function and no default function and no other mechanism to handle it then execution reverts. -1. Each time functions are added, replaced or removed a `DiamondCut` event is emitted to record it. -1. A diamond implements the DiamondLoupe interface. -1. All immutable functions must be emitted in the `DiamondCut` event as new functions added. And the loupe functions must return information about immutable functions if they exist. The facet address for an immutable function is the diamond's address. Any attempt to delete or replace an immutable function must revert. - -A diamond may implement the following: - -1. [EIP-165](./eip-165.md)'s `supportsInterface`. If a diamond has the `diamondCut` function then the interface ID used for it is `IDiamondCut.diamondCut.selector`. The interface ID used for the diamond loupe interface is `IDiamondLoupe.facets.selector ^ IDiamondLoupe.facetFunctionSelectors.selector ^ IDiamondLoupe.facetAddresses.selector ^ IDiamondLoupe.facetAddress.selector`. - -The diamond address is the address that users interact with. The diamond address does not change. Only facet addresses can change by using the `diamondCut` function, or other function. - -## Rationale - -### Using Function Selectors - -User interface software can be used to retrieve function selectors and facet addresses from a diamond in order show what functions a diamond has. - -This standard is designed to make diamonds work well with user-interface software. Function selectors with the ABI of a contract provide enough information about functions to be useful for user-interface software. - -### Gas Considerations - -Delegating function calls does have some gas overhead. This is mitigated in several ways: - -1. Because diamonds do not have a max size limitation it is possible to add gas optimizing functions for use cases. For example someone could use a diamond to implement the [EIP-721](./eip-721.md) standard and implement batch transfer functions to reduce gas (and make batch transfers more convenient). -1. Some contract architectures require calling multiple contracts in one transaction. Gas savings can be realized by condensing those contracts into a single diamond and accessing contract storage directly. -1. Facets can contain few external functions, reducing gas costs. Because it costs more gas to call a function in a contract with many functions than a contract with few functions. -1. The Solidity optimizer can be set to a high setting causing more bytecode to be generated but the facets will use less gas when executed. - -### Versions of Functions - -Software or a user can verify what version of a function is called by getting the facet address of the function. This can be done by calling the `facetAddress` function from the `IDiamondLoupe` interface. This function takes a function selector as an argument and returns the facet address where it is implemented. - -### Default Function - -Solidity provides the `fallback` function so that specific functionality can be executed when a function is called on a contract that does not exist in the contract. This same behavior can optionally be implemented in a diamond by implementing and using a default function, which is a function that is executed when a function is called on a diamond that does not exist in the diamond. - -A default function can be implemented a number of ways and this standard does not specify how it must be implemented. - -### Loupe Functions & `DiamondCut` Event - -To find out what functions a regular contract has it is only necessary to look at its verified source code. - -The verified source code of a diamond does not include what functions it has so a different mechanism is needed. - -A diamond has four standard functions called the loupe functions that are used to show what functions a diamond has. - -The loupe functions can be used for many things including: -1. To show all functions used by a diamond. -1. To query services like Etherscan or files to retrieve and show all source code used by a diamond. -1. To query services like Etherscan or files to retrieve ABI information for a diamond. -1. To test or verify that a transaction that adds/replaces/removes functions on a diamond succeeded. -1. To find out what functions a diamond has before calling functions on it. -1. To be used by tools and programming libraries to deploy and upgrade diamonds. -1. To be used by user interfaces to show information about diamonds. -1. To be used by user interfaces to enable users to call functions on diamonds. - -Diamonds support another form of transparency which is a historical record of all upgrades on a diamond. This is done with the `DiamondCut` event which is used to record all functions that are added, replaced or removed on a diamond. - -### Sharing Functions Between Facets - -In some cases it might be necessary to call a function defined in a different facet. Here are ways to do this: - -1. Copy internal function code in one facet to the other facet. -1. Put common internal functions in a contract that is inherited by multiple facets. -1. Put common internal functions in a Solidity library and use the library in facets. -1. A type safe way to call an external function defined in another facet is to do this: `MyOtherFacet(address(this)).myFunction(arg1, arg2)` -1. A more gas-efficient way to call an external function defined in another facet is to use delegatecall. Here is an example of doing that: -```solidity -DiamondStorage storage ds = diamondStorage(); -bytes4 functionSelector = bytes4(keccak256("myFunction(uint256)")); -// get facet address of function -address facet = ds.selectorToFacet[functionSelector]; -bytes memory myFunctionCall = abi.encodeWithSelector(functionSelector, 4); -(bool success, bytes memory result) = address(facet).delegatecall(myFunctionCall); -``` -6. Instead of calling an external function defined in another facet you can instead create an internal function version of the external function. Add the internal version of the function to the facet that needs to use it. - -### Facets can be Reusable and Composable - -A deployed facet can be used by any number of diamonds. - -Different combinations of facets can be used with different diamonds. - -It is possible to create and deploy a set of facets that are reused by different diamonds over time. - -The ability to use the same deployed facets for many diamonds reduces deployment costs. - -It is possible to implement facets in a way that makes them usable/composable/compatible with other facets. It is also possible to implement facets in a way that makes them not usable/composable/compatible with other facets. - -A function signature is the name of a function and its parameter types. Example function signature: `myfunction(uint256)`. A limitation is that two external functions with the same function signature can’t be added to the same diamond at the same time because a diamond, or any contract, cannot have two external functions with the same function signature. - -All the functions of a facet do not have to be added to a diamond. Some functions in a facet can be added to a diamond while other functions in the facet are not added to the diamond. - -## Backwards Compatibility - -This standard makes upgradeable diamonds compatible with future standards and functionality because new functions can be added and existing functions can be replaced or removed. - -## Reference Implementation - -All the Solidity code for a complete reference implementation has been put in a single file here: [Diamond.sol](../assets/eip-2535/reference/Diamond.sol) - -The same reference implementation has been organized into multiple files and directories and also includes a deployment script and tests. Download it as a zip file: [`EIP2535-Diamonds-Reference-Implementation.zip`](../assets/eip-2535/reference/EIP2535-Diamonds-Reference-Implementation.zip) - -## Security Considerations - -### Ownership and Authentication - -> **Note:** The design and implementation of diamond ownership/authentication is **not** part of this standard. The examples given in this standard and in the reference implementation are just **examples** of how it could be done. - -It is possible to create many different authentication or ownership schemes with this proposal. Authentication schemes can be very simple or complex, fine grained or coarse. This proposal does not limit it in any way. For example ownership/authentication could be as simple as a single account address having the authority to add/replace/remove functions. Or a decentralized autonomous organization could have the authority to only add/replace/remove certain functions. - -Consensus functionality could be implemented such as an approval function that multiple different people call to approve changes before they are executed with the `diamondCut` function. These are just examples. - -The development of standards and implementations of ownership, control and authentication of diamonds is encouraged. - -### Arbitrary Execution with `diamondCut` - -The `diamondCut` function allows arbitrary execution with access to the diamond's storage (through `delegatecall`). Access to this function must be restricted carefully. - -### Do Not Self Destruct -Use of `selfdestruct` in a facet is heavily discouraged. Misuse of it can delete a diamond or a facet. - -### Function Selector Clash - -A function selector clash occurs when two different function signatures hash to the same four-byte hash. This has the unintended consequence of replacing an existing function in a diamond when the intention was to add a new function. This scenario is not possible with a properly implemented `diamondCut` function because it prevents adding function selectors that already exist. - -### Transparency - -Diamonds emit an event every time one or more functions are added, replaced or removed. All source code can be verified. This enables people and software to monitor changes to a contract. If any bad acting function is added to a diamond then it can be seen. - -Security and domain experts can review the history of change of a diamond to detect any history of foul play. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2535.md diff --git a/EIPS/eip-2544.md b/EIPS/eip-2544.md index 4df16b5dfb930f..c2890a00893d6f 100644 --- a/EIPS/eip-2544.md +++ b/EIPS/eip-2544.md @@ -1,126 +1 @@ ---- -eip: 2544 -title: ENS Wildcard Resolution -description: Adds support for "wildcard" resolution of subdomains in ENS. -author: Nick Johnson (@arachnid), 0age (@0age) -discussions-to: https://ethereum-magicians.org/t/eip-2544-ens-wildcard-resolution -status: Stagnant -type: Standards Track -category: ERC -created: 2020-02-28 -requires: 137 ---- - -## Abstract - -The Ethereum Name Service Specification (EIP-137) establishes a two-step name resolution process. First, an ENS client performs the namehash algorithm on the name to determine the associated "node", and supplies that node to the ENS Registry contract to determine the resolver. Then, if a resolver has been set on the Registry, the client supplies that same node to the resolver contract, which will return the associated address or other record. - -As currently specified, this process terminates if a resolver is not set on the ENS Registry for a given node. This EIP changes the name resolution process by adding an additional step if a resolver is not set for a domain. This step strips out the leftmost label from the name, derives the node of the new fragment, and supplies that node to the ENS Registry. If a resolver is located for that node, the client supplies the original, complete node to that resolver contract to derive the relevant records. This step is repeated until a node with a resolver is found. - -Further, this specification defines a new way for resolvers to resolve names, using a unified `resolve()` method that permits more flexible handling of name resolution. - -## Motivation - -Many applications such as wallet providers, exchanges, and dapps have expressed a desire to issue ENS names for their users via custom subdomains on a shared parent domain. However, the cost of doing so is currently prohibitive for large user bases, as a distinct record must be set on the ENS Registry for each subdomain. - -Furthermore, users cannot immediately utilize these subdomains upon account creation, as the transaction to assign a resolver for the node of the subdomain must first be submitted and mined on-chain. This adds unnecessary friction when onboarding new users, who coincidentally would often benefit greatly from the usability improvements afforded by an ENS name. - -Enabling wildcard support allows for the design of more advanced resolvers that deterministically generate addresses and other records for unassigned subdomains. The generated addresses could map to counterfactual contract deployment addresses (i.e. `CREATE2` addresses), to designated "fallback" addresses, or other schemes. Additionally, individual resolvers would still be assignable to any given subdomain, which would supersede the wildcard resolution using the parent resolver. - -Another critical motivation with EIP-2544 is to enable wildcard resolution in a backwards-compatible fashion. It does not require modifying the current ENS Registry contract or any existing resolvers, and continues to support existing ENS records — legacy ENS clients would simply fail to resolve wildcard records. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Let: - - `namehash` be the algorithm defined in EIP 137. - - `dnsencode` be the process for encoding DNS names specified in section 3.1 of RFC1035, with the exception that there is no limit on the total length of the encoded name. The empty string is encoded identically to the name '.', as a single 0-octet. - - `parent` be a function that removes the first label from a name (eg, `parent('foo.eth') = 'eth'`). `parent('tld')` is defined as the empty string ''. - - `ens` is the ENS registry contract for the current network. - -EIP-2544-compliant ENS resolvers MAY implement the following function interface: - -``` -interface ExtendedResolver { - function resolve(bytes calldata name, bytes calldata data) external view returns(bytes); -} -``` - -If a resolver implements this function, it MUST return true when `supportsInterface()` is called on it with the interface's ID, 0xTBD. - -ENS clients will call `resolve` with the DNS-encoded name to resolve and the encoded calldata for a resolver function (as specified in EIP-137 and elsewhere); the function MUST either return valid return data for that function, or revert if it is not supported. - -EIP-2544-compliant ENS clients MUST perform the following procedure when determining the resolver for a given name: - -1. Set `currentname = name` -2. Set `resolver = ens.resolver(namehash(currentname))` -3. If `resolver` is not the zero address, halt and return `resolver`. -4. If `name` is the empty name ('' or '.'), halt and return null. -5. Otherwise, set `currentname = parent(currentname)` and go to 2. - -If the procedure above returns null, name resolution MUST terminate unsuccessfully. Otherwise, EIP-2544-compliant ENS clients MUST perform the following procedure when resolving a record: - -1. Set `calldata` to the ABI-encoded call data for the resolution function required - for example, the ABI encoding of `addr(namehash(name))` when resolving the `addr` record. -2. Set `supports2544 = resolver.supportsInterface(0xTBD)`. -3. If `supports2544` is true, set `result = resolver.resolve(dnsencode(name), calldata)` -4. Otherwise, set `result` to the result of calling `resolver` with `calldata`. -5. Return `result` after decoding it using the return data ABI of the corresponding resolution function (eg, for `addr()`, ABI-decode the result of `resolver.resolve()` as an `address`). - -Note that in all cases the resolution function (`addr()` etc) and the `resolve` function are supplied the original `name`, *not* the `currentname` found in the first stage of resolution. - -### Pseudocode -``` -function getResolver(name) { - for(let currentname = name; currentname !== ''; currentname = parent(currentname)) { - const node = namehash(currentname); - const resolver = ens.resolver(node); - if(resolver != '0x0000000000000000000000000000000000000000') { - return resolver; - } - } - return null; -} - -function resolve(name, func, ...args) { - const resolver = getResolver(name); - if(resolver === null) { - return null; - } - const supports2544 = resolver.supportsInterface('0xTBD'); - let result; - if(supports2544) { - const calldata = resolver[func].encodeFunctionCall(namehash(name), ...args); - result = resolver.resolve(dnsencode(name), calldata); - return resolver[func].decodeReturnData(result); - } else { - return resolver[func](...args); - } -} -``` - -## Rationale - -The proposed implementation supports wildcard resolution in a manner that minimizes the impact to existing systems. It also reuses existing algorithms and procedures to the greatest possible extent, thereby easing the burden placed on authors and maintainers of various ENS clients. - -It also recognizes an existing consensus concerning the desirability of wildcard resolution for ENS, enabling more widespread adoption of the original specification by solving for a key scalability obstacle. - -While introducing an optional `resolve` function for resolvers, taking the unhashed name and calldata for a resolution function increases implementation complexity, it provides a means for resolvers to obtain plaintext labels and act accordingly, which enables many wildcard-related use-cases that would otherwise not be possible - for example, a wildcard resolver could resolve `id.nifty.eth` to the owner of the NFT with id `id` in some collection. With only namehashes to work with, this is not possible. Resolvers with simpler requirements can continue to simply implement resolution functions directly and omit support for the `resolve` function entirely. - -The DNS wire format is used for encoding names as it permits quick and gas-efficient hashing of names, as well as other common operations such as fetching or removing individual labels; in contrast, dot-separated names require iterating over every character in the name to find the delimiter. - -## Backwards Compatibility - -Existing ENS clients that are compliant with EIP-137 will fail to resolve wildcard records and refuse to interact with them, while those compliant with EIP-2544 will continue to correctly resolve, or reject, existing ENS records. Resolvers wishing to implement the new `resolve` function for non-wildcard use-cases (eg, where the resolver is set directly on the name being resolved) should consider what to return to legacy clients that call the individual resolution functions for maximum compatibility. - -## Security Considerations - -While compliant ENS clients will continue to refuse to resolve records without a resolver, there is still the risk that an improperly-configured client will refer to an incorrect resolver, or will not reject interactions with the null address when a resolver cannot be located. - -Additionally, resolvers supporting completely arbitrary wildcard subdomain resolution will increase the likelihood of funds being sent to unintended recipients as a result of typos. Applications that implement such resolvers should consider making additional name validation available to clients depending on the context, or implementing features that support recoverability of funds. - -There is also the possibility that some applications might require that no resolver be set for certain subdomains. For this to be problematic, the parent domain would need to successfully resolve the given subdomain node — to the knowledge of the authors, no application currently supports this feature or expects that subdomains should not resolve to a record. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2544.md diff --git a/EIPS/eip-2569.md b/EIPS/eip-2569.md index 8ed6b119c29551..712c541dd6944d 100644 --- a/EIPS/eip-2569.md +++ b/EIPS/eip-2569.md @@ -1,353 +1 @@ ---- -eip: 2569 -title: Saving and Displaying Image Onchain for Universal Tokens -description: A set of interfaces to save an SVG image in Ethereum, and to retrieve the image file from Ethereum for universal tokens. -author: Hua Zhang (@dgczhh), Yuefei Tan (@whtyfhas), Derek Zhou (@zhous), Ran Xing (@lemontreeran) -discussions-to: https://ethereum-magicians.org/t/erc-2569-saving-and-displaying-image-onchain-for-universal-tokens/4167 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-03-28 ---- - -## Abstract -This set of interfaces allow a smart contract to save an SVG image in Ethereum and to retrieve an SVG image from Ethereum for fungible tokens, non-fungible tokens and tokens based on standards that will be developed in the future. - -The interface set has two interfaces: one to save an SVG file in Ethereum and the other to retrieve an SVG file from Ethereum. - -Typical applications include but not limited to: -* A solution for storage of a fungible token's icon. -* A solution for storage of a non-fungible token's icon. -* A solution for storage of the icon/logo of a DAO's reputation token. - -## Motivation -The ERC-721 token standard is a popular standard to define a non-fungible token in Ethereum. This standard is widely used to specify a crypto gift, crypto medal, crypto collectible etc. The most famous use case is the [cryptokitty](https://www.cryptokitties.co/). - -In most of these applications an image is attached to an ERC-721 token. For example, in the cryptokitty case each kitty has a unique image. While the token's code is saved in Ethereum permanently, the image attached to the token is not. - -The existing solutions still keep such an image in a centralized server instead of Ethereum. When these applications display an image for a token they retrieve the token's information from Ethereum and search the centralized server for the token's associated image by using the token's information. - -Although this is an applicable way to display an image for a token, the image is still vulnerable to risks of being damaged or lost when saved in a centralized server. - -Hence we propose a set of interfaces to save an image for a universal token in Ethereum to keep the image permanent and tamper-resistant, and to retrieve an image for a universal token from Ethereum. - -## Specification - -An EIP-2569 compatible contract MUST have a method with the signature getTokenImageSvg(uint256) view returns (string memory) and a method with the signature setTokenImageSvg(uint256 tokenId, string memory imagesvg) internal. - -These methods define how a smart contract saves an image for a universal token in Ethereum which keeps the image permanent and tamper-resistant, and how a smart contract retrieves an image from Ethereum for a universal token. - -By calling the methods users should access an SVG image. - -* getTokenImageSvg(uint256 tokenId) external view returns (string memory): for an ERC-721 or ERC-1155 token or a token implemented by a contract which has a member "ID" to specify its token type or token index we define an interface to get an SVG image by using the token's ID number. For an ERC-20 token or a token implemented by a contract which doesn't have a member "ID" to specify its token type or token index we define an interface to get an SVG image for it if the token has a member variable string to save the image. - -It has the following parameter: - -tokenId: for a non-fungible token such as an ERC-721 token or a multi-token such as an ERC-1155 token which has a member "ID" to specify its token type or token index our proposed interface assigns an SVG image's file content to a string variable of the token's contract and associates the SVG image to this "ID" number. This unique ID is used to access its SVG image in both a "set" operation and a "get" operation. -For a fungible token such as an ERC-20 token no such an ID is needed and our proposed interface just assigns an SVG image's file content to a string variable of the token's contract. - -* setTokenImageSvg(uint256 tokenId, string memory imagesvg) internal: for an ERC-721 or ERC-1155 token or a token implemented by a contract which has a member "ID" to specify its token type or token index we define an interface to associate an SVG image to the token's ID number. For an ERC-20 token or a token implemented by a contract which doesn't have a member "ID" to specify its token type or token index we define an interface to assign an SVG image to a member variable string of this token's contract. - -It has the following two parameters: - -tokenId: for a non-fungible token such as an ERC-721 token or a multi-token such as an ERC-1155 token which has a member "ID" to specify its token type or token index our proposed interface assigns an SVG image's file content to a string variable of the token's contract and associates the SVG image to this "ID" number. This unique ID is used to access its SVG image in both a "set" operation and a "get" operation. -For a fungible token such as an ERC-20 token no such an ID is needed and our proposed interface just assigns an SVG image's file content to a string variable of the token's contract. - -imageSvg: we use a string variable to save an SVG image file's content. -An SVG image that will be saved in the imageSvg string should include at least two attributes:"name", "desc"(description). - -The procedure to save an image for a token in Ethereum is as follows: - -**Step1:** define a string variable or an array of strings to hold an image or an array of images. - -**Step 2:** define a function to set an (SVG) image's file content or an array of image file's contents to the string variable or the array of strings. - -Step 1: for a token such as an ERC-721 or ERC-1155 token which has a member variable "ID" to specify a token type or index and a member variable string to keep an (SVG) image associated with the "ID", retrieve the (SVG) image from Ethereum by calling our proposed "get" interface with the token's ID; -for a token which doesn't have a member variable "ID" to specify a token type of index but has a member variable string to keep an (SVG) image, retrieve the (SVG) image from Ethereum by calling our proposed "get" without an "ID". - -## Rationale -After Bitcoin was created people have found ways to keep information permanent and tamper-resistant by encoding text messages they want to preserve permanently and tamper-resistantly in blockchain transactions. However existing applications only do this for text information and there are no solutions to keep an image permanent and tamper-resistant. - -One of the most significant reasons for not doing so is that in general the size of an image is much bigger than the size of a text file, thus the gas needed to save an image in Ethereum would exceed a block's gas limit. - -However this changed a lot after the SVG(Scalable Vector Graphics) specification was developed by W3C since 1999. - -The SVG specification offers several advantages (for more details about the advantages please refer to a reference link:https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) over raster images. One of these advantages is its compact file-size. - -"Compact file-size – Pixel-based images are saved at a large size from the start because you can only retain the quality when you make the image smaller, but not when you make it larger. This can impact a site’s download speed. Since SVGs are scalable, they can be saved at a minimal file size". - -This feature well fixes the painpoint of saving an image file in Ethereum, therefore we think saving an SVG image in Ethereum is a good solution for keep the image permanent and tamper-resistant. - -In most ERC-721 related DAPPs they display an image for a non-fungible token. In most ERC-20 related DAPPs they don't have an image for a fungible token. We think displaying an image for a token either based on existing token standards such as ERC-20, ERC-721, ERC-1155 or based on future standards is needed in many use cases. Therefore those DAPPs which currently don't display an image for a token will eventually need such a function. - -However with regard to most of the existing DAPPs which can display an image for a token they save such an image in a centralized server which, we think, is just a compromised solution. By utilizing the SVG specification we think converting a token's image to an SVG image and saving it in Ethereum provides a better solution for DAPPs to access an image for a token. - -This solution not only works for tokens based on ERC-721, ERC-1155 and ERC-20 but will work for tokens based on future standards. - -## Backwards Compatibility -There are no backward compatibility issues. - -## Reference Implementation -`tokenId`: a token index in an ERC-721 token or a token type/index in an ERC-1155 token. It is a uint256 variable. - -`imageSvg`: an SVG image's file content. It is a string variable. Note: the SVG image should include at least three attributes:"name", "description" and "issuer". - -`setTokenImageSvg`: interface to set an SVG image to a token with or without an ID number. - -`getTokenImageSvg`: interface to get an SVG image for a token with or without an ID number. - -We propose to add three sol files in the existing ERC-721 implementation. -Here are the details for the proposed sol files. - -```solidity -// ----- IERC721GetImageSvg.sol ------------------------- - -pragma solidity ^0.5.0; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/** - * @title ERC-721 Non-Fungible Token Standard, optional retrieving SVG image extension - * @dev See https://eips.ethereum.org/EIPS/eip-721 - */ -contract IERC721GetImageSvg is IERC721 { - function getTokenImageSvg(uint256 tokenId) external view returns (string memory); -} - - -// ----- ERC721GetImageSvg.sol ------------------------- - -pragma solidity ^0.5.0; - -import "@openzeppelin/contracts/GSN/Context.sol"; -import "@openzeppelin/contracts/token/ERC721/./ERC721.sol"; -import "@openzeppelin/contracts/introspection/ERC165.sol"; -import "./IERC721GetImageSvg.sol"; - -contract ERC721GetImageSvg is Context, ERC165, ERC721, IERC721GetImageSvg { - // Mapping for token Images - mapping(uint256 => string) private _tokenImageSvgs; - - /* - * bytes4(keccak256('getTokenImageSvg(uint256)')) == 0x87d2f48c - * - * => 0x87d2f48c == 0x87d2f48c - */ - bytes4 private constant _INTERFACE_ID_ERC721_GET_TOKEN_IMAGE_SVG = 0x87d2f48c; - - /** - * @dev Constructor function - */ - constructor () public { - // register the supported interfaces to conform to ERC721 via ERC165 - _registerInterface(_INTERFACE_ID_ERC721_GET_TOKEN_IMAGE_SVG); - } - - /** - * @dev Returns an SVG Image for a given token ID. - * Throws if the token ID does not exist. May return an empty string. - * @param tokenId uint256 ID of the token to query - */ - function getTokenImageSvg(uint256 tokenId) external view returns (string memory) { - require(_exists(tokenId), "ERC721GetImageSvg: SVG Image query for nonexistent token"); - return _tokenImageSvgs[tokenId]; - } - - /** - * @dev Internal function to set the token SVG image for a given token. - * Reverts if the token ID does not exist. - * @param tokenId uint256 ID of the token to set its SVG image - * @param imagesvg string SVG to assign - */ - function setTokenImageSvg(uint256 tokenId, string memory imagesvg) internal { - require(_exists(tokenId), "ERC721GetImageSvg: SVG image set of nonexistent token"); - _tokenImageSvgs[tokenId] = imagesvg; - } - -} - - -// ----- ERC721ImageSvgMintable.sol ------------------------- - -pragma solidity ^0.5.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721Metadata.sol"; -import "@openzeppelin/contracts/access/roles/MinterRole.sol"; -import "./ERC721GetImageSvg.sol"; - -/** - * @title ERC721ImageSvgMintable - * @dev ERC721 minting logic with imagesvg. - */ -contract ERC721ImageSvgMintable is ERC721, ERC721Metadata, ERC721GetImageSvg, MinterRole { - /** - * @dev Function to mint tokens. - * @param to The address that will receive the minted tokens. - * @param tokenId The token id to mint. - * @param tokenImageSvg The token SVG image of the minted token. - * @return A boolean that indicates if the operation was successful. - */ - function mintWithTokenImageSvg(address to, uint256 tokenId, string memory tokenImageSvg) public onlyMinter returns (bool) { - _mint(to, tokenId); - setTokenImageSvg(tokenId, tokenImageSvg); - return true; - } -} - - -We propose to add three sol files in the existing ERC-1155 implementation. -Here are the details for the proposed sol files. - -// ----- IERC1155GetImageSvg.sol ------------------------- - -pragma solidity ^0.5.0; - -import "./IERC1155.sol"; - -/** - * @title ERC-1155 Multi Token Standard, retrieving SVG image for a token - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md - */ -contract IERC1155GetImageSvg is IERC1155 { - function getTokenImageSvg(uint256 tokenId) external view returns (string memory); -} - - -// ----- ERC1155GetImageSvg.sol ------------------------- - -pragma solidity ^0.5.0; - -import "./ERC1155.sol"; -import "./IERC1155GetImageSvg.sol"; - -contract ERC1155GetImageSvg is ERC165, ERC1155, IERC1155GetImageSvg { - // Mapping for token Images - mapping(uint256 => string) private _tokenImageSvgs; - - /* - * bytes4(keccak256('getTokenImageSvg(uint256)')) == 0x87d2f48c - * - * => 0x87d2f48c == 0x87d2f48c - */ - bytes4 private constant _INTERFACE_ID_ERC1155_GET_TOKEN_IMAGE_SVG = 0x87d2f48c; - - /** - * @dev Constructor function - */ - constructor () public { - // register the supported interfaces to conform to ERC1155 via ERC165 - _registerInterface(_INTERFACE_ID_ERC1155_GET_TOKEN_IMAGE_SVG); - } - - - /** - * @dev Returns an SVG Image for a given token ID. - * Throws if the token ID does not exist. May return an empty string. - * @param tokenId uint256 ID of the token to query - */ - function getTokenImageSvg(uint256 tokenId) external view returns (string memory) { - require(_exists(tokenId), "ERC1155GetImageSvg: SVG Image query for nonexistent token"); - return _tokenImageSvgs[tokenId]; - } - - /** - * @dev Internal function to set the token SVG image for a given token. - * Reverts if the token ID does not exist. - * @param tokenId uint256 ID of the token to set its SVG image - * @param imagesvg string SVG to assign - */ - function setTokenImageSvg(uint256 tokenId, string memory imagesvg) internal { - require(_exists(tokenId), "ERC1155GetImageSvg: SVG image set of nonexistent token"); - _tokenImageSvgs[tokenId] = imagesvg; - } - -} - - - -// ----- ERC1155MixedFungibleWithSvgMintable.sol ------------------------- - -pragma solidity ^0.5.0; - -import "./ERC1155MixedFungibleMintable.sol"; -import "./ERC1155GetImageSvg.sol"; - -/** - @dev Mintable form of ERC1155 with SVG images - Shows how easy it is to mint new items with SVG images -*/ - -contract ERC1155MixedFungibleWithSvgMintable is ERC1155, ERC1155MixedFungibleMintable, ERC1155GetImageSvg { - /** - * @dev Function to mint non-fungible tokens. - * @param _to The address that will receive the minted tokens. - * @param _type The token type to mint. - * @param tokenImageSvg The token SVG image of the minted token. - */ - function mintNonFungibleWithImageSvg(uint256 _type, address[] calldata _to, string memory tokenImageSvg) external creatorOnly(_type) { - mintNonFungible(_type, _to); - setTokenImageSvg(_type, tokenImageSvg); - } - - - /** - * @dev Function to mint fungible tokens. - * @param _to The address that will receive the minted tokens. - * @param _id The token type to mint. - * @param _quantities The number of tokens for a type to mint. - * @param tokenImageSvg The token SVG image of the minted token. - */ - function mintFungibleWithImageSvg(uint256 _id, address[] calldata _to, uint256[] calldata _quantities, string memory tokenImageSvg) external creatorOnly(_id) { - mintFungible(_id, _to, _quantities, tokenImageSvg) { - setTokenImageSvg(_id, tokenImageSvg); - } -} - - - -We propose to add three sol files in the existing ERC-20 implementation. -Here are the details for the proposed sol files. - - -// ----- IERC20GetImageSvg.sol ------------------------- - -pragma solidity ^0.5.0; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/** - * @title ERC-20 Fungible Token Standard, retrieving SVG image for a token - * @dev See https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol - */ -contract IERC20GetImageSvg is IERC20 { - function getTokenImageSvg() external view returns (string memory); -} - - -// ----- ERC20GetImageSvg.sol ------------------------- - -pragma solidity ^0.5.0; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "./IERC20GetImageSvg.sol"; - -contract ERC20GetImageSvg is ERC20, IERC20GetImageSvg { - string private _tokenImageSvg; -//将图片实现写在构造器中 - constructor(string calldata svgCode) public { -_tokenImageSvg = svgCode -} - - /** - * @dev Returns an SVG Image. - */ - function getTokenImageSvg() external view returns (string memory) { - return _tokenImageSvg; - } - -} - - -``` - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2569.md diff --git a/EIPS/eip-2612.md b/EIPS/eip-2612.md index 90e47d67bf12ee..0fd0e0d2fbc9be 100644 --- a/EIPS/eip-2612.md +++ b/EIPS/eip-2612.md @@ -1,198 +1 @@ ---- -eip: 2612 -title: Permit Extension for EIP-20 Signed Approvals -description: EIP-20 approvals via EIP-712 secp256k1 signatures -author: Martin Lundfall (@Mrchico) -discussions-to: https://github.com/ethereum/EIPs/issues/2613 -status: Final -type: Standards Track -category: ERC -created: 2020-04-13 -requires: 20, 712 ---- - -## Abstract - -Arguably one of the main reasons for the success of [EIP-20](./eip-20.md) tokens lies in the interplay between `approve` and `transferFrom`, which allows for tokens to not only be transferred between externally owned accounts (EOA), but to be used in other contracts under application specific conditions by abstracting away `msg.sender` as the defining mechanism for token access control. - -However, a limiting factor in this design stems from the fact that the EIP-20 `approve` function itself is defined in terms of `msg.sender`. This means that user's _initial action_ involving EIP-20 tokens must be performed by an EOA (_but see Note below_). If the user needs to interact with a smart contract, then they need to make 2 transactions (`approve` and the smart contract call which will internally call `transferFrom`). Even in the simple use case of paying another person, they need to hold ETH to pay for transaction gas costs. - -This ERC extends the EIP-20 standard with a new function `permit`, which allows users to modify the `allowance` mapping using a signed message, instead of through `msg.sender`. - -For an improved user experience, the signed data is structured following [EIP-712](./eip-712.md), which already has wide spread adoption in major RPC providers. - -**_Note:_** EIP-20 must be performed by an EOA unless the address owning the token is actually a contract wallet. Although contract wallets solves many of the same problems that motivates this EIP, they are currently only scarcely adopted in the ecosystem. Contract wallets suffer from a UX problem -- since they separate the EOA `owner` of the contract wallet from the contract wallet itself (which is meant to carry out actions on the `owner`s behalf and holds all of their funds), user interfaces need to be specifically designed to support them. The `permit` pattern reaps many of the same benefits while requiring little to no change in user interfaces. - -## Motivation - -While EIP-20 tokens have become ubiquitous in the Ethereum ecosystem, their status remains that of second class tokens from the perspective of the protocol. The ability for users to interact with Ethereum without holding any ETH has been a long outstanding goal and the subject of many EIPs. - -So far, many of these proposals have seen very little adoption, and the ones that have been adopted (such as [EIP-777](./eip-777.md)), introduce a lot of additional functionality, causing unexpected behavior in mainstream contracts. - -This ERC proposes an alternative solution which is designed to be as minimal as possible and to only address _one problem_: the lack of abstraction in the EIP-20 `approve` method. - -While it may be tempting to introduce `*_by_signature` counterparts for every EIP-20 function, they are intentionally left out of this EIP-20 for two reasons: - -- the desired specifics of such functions, such as decision regarding fees for `transfer_by_signature`, possible batching algorithms, varies depending on the use case, and, -- they can be implemented using a combination of `permit` and additional helper contracts without loss of generality. - -## Specification - -Compliant contracts must implement 3 new functions in addition to EIP-20: - -```sol -function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external -function nonces(address owner) external view returns (uint) -function DOMAIN_SEPARATOR() external view returns (bytes32) -``` - -The semantics of which are as follows: - -For all addresses `owner`, `spender`, uint256s `value`, `deadline` and `nonce`, uint8 `v`, bytes32 `r` and `s`, -a call to `permit(owner, spender, value, deadline, v, r, s)` will set -`approval[owner][spender]` to `value`, -increment `nonces[owner]` by 1, -and emit a corresponding `Approval` event, -if and only if the following conditions are met: - -- The current blocktime is less than or equal to `deadline`. -- `owner` is not the zero address. -- `nonces[owner]` (before the state update) is equal to `nonce`. -- `r`, `s` and `v` is a valid `secp256k1` signature from `owner` of the message: - -If any of these conditions are not met, the `permit` call must revert. - -```sol -keccak256(abi.encodePacked( - hex"1901", - DOMAIN_SEPARATOR, - keccak256(abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - spender, - value, - nonce, - deadline)) -)) -``` - -where `DOMAIN_SEPARATOR` is defined according to EIP-712. The `DOMAIN_SEPARATOR` should be unique to the contract and chain to prevent replay attacks from other domains, -and satisfy the requirements of EIP-712, but is otherwise unconstrained. -A common choice for `DOMAIN_SEPARATOR` is: - -```solidity -DOMAIN_SEPARATOR = keccak256( - abi.encode( - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), - keccak256(bytes(name)), - keccak256(bytes(version)), - chainid, - address(this) -)); -``` - -In other words, the message is the EIP-712 typed structure: - -```js -{ - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Permit": [ - { - "name": "owner", - "type": "address" - }, - { - "name": "spender", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - }, - { - "name": "nonce", - "type": "uint256" - }, - { - "name": "deadline", - "type": "uint256" - } - ], - }, - "primaryType": "Permit", - "domain": { - "name": erc20name, - "version": version, - "chainId": chainid, - "verifyingContract": tokenAddress - }, - "message": { - "owner": owner, - "spender": spender, - "value": value, - "nonce": nonce, - "deadline": deadline - } -} -``` - -Note that nowhere in this definition we refer to `msg.sender`. The caller of the `permit` function can be any address. - -## Rationale - -The `permit` function is sufficient for enabling any operation involving EIP-20 tokens to be paid for using the token itself, rather than using ETH. - -The `nonces` mapping is given for replay protection. - -A common use case of `permit` has a relayer submit a `Permit` on behalf of the `owner`. In this scenario, the relaying party is essentially given a free option to submit or withhold the `Permit`. If this is a cause of concern, the `owner` can limit the time a `Permit` is valid for by setting `deadline` to a value in the near future. The `deadline` argument can be set to `uint(-1)` to create `Permit`s that effectively never expire. - -EIP-712 typed messages are included because of its wide spread adoption in many wallet providers. - -## Backwards Compatibility - -There are already a couple of `permit` functions in token contracts implemented in contracts in the wild, most notably the one introduced in the `dai.sol`. - -Its implementation differs slightly from the presentation here in that: - -- instead of taking a `value` argument, it takes a bool `allowed`, setting approval to 0 or `uint(-1)`. -- the `deadline` argument is instead called `expiry`. This is not just a syntactic change, as it effects the contents of the signed message. - -There is also an implementation in the token `Stake` (Ethereum address `0x0Ae055097C6d159879521C384F1D2123D1f195e6`) with the same ABI as `dai` but with different semantics: it lets users issue "expiring approvals", that only allow `transferFrom` to occur while `expiry >= block.timestamp`. - -The specification presented here is in line with the implementation in Uniswap V2. - -The requirement to revert if the permit is invalid was added when the EIP was already widely deployed, but at the moment it was consistent with all found implementations. - -## Security Considerations - -Though the signer of a `Permit` may have a certain party in mind to submit their transaction, another party can always front run this transaction and call `permit` before the intended party. The end result is the same for the `Permit` signer, however. - -Since the ecrecover precompile fails silently and just returns the zero address as `signer` when given malformed messages, it is important to ensure `owner != address(0)` to avoid `permit` from creating an approval to spend "zombie funds" belong to the zero address. - -Signed `Permit` messages are censorable. The relaying party can always choose to not submit the `Permit` after having received it, withholding the option to submit it. The `deadline` parameter is one mitigation to this. If the signing party holds ETH they can also just submit the `Permit` themselves, which can render previously signed `Permit`s invalid. - -The standard EIP-20 race condition for approvals (SWC-114) applies to `permit` as well. - -If the `DOMAIN_SEPARATOR` contains the `chainId` and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2612.md diff --git a/EIPS/eip-2615.md b/EIPS/eip-2615.md index 9191c801c64bc3..d741d840a9bb0b 100644 --- a/EIPS/eip-2615.md +++ b/EIPS/eip-2615.md @@ -1,241 +1 @@ ---- -eip: 2615 -title: Non-Fungible Token with mortgage and rental functions -author: Kohshi Shiba -discussions-to: https://github.com/ethereum/EIPs/issues/2616 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-04-25 -requires: 165, 721 ---- - -## Simple Summary - -This standard proposes an extension to ERC721 Non-Fungible Tokens (NFTs) to support rental and mortgage functions. These functions are necessary for NFTs to emulate real property, just like those in the real world. - -## Abstract - -This standard is an extension of ERC721. It proposes additional roles, the right of tenants to enable rentals, and the right of lien. - -With ERC2615, NFT owners will be able to rent out their NFTs and take out a mortgage by collateralizing their NFTs. For example, this standard can apply to: - -- Virtual items (in-game assets, virtual artwork, etc.) -- Physical items (houses, automobiles, etc.) -- Intellectual property rights -- DAO membership tokens - -NFT developers are also able to easily integrate ERC2615 since it is fully backwards-compatible with the ERC721 standard. - -One notable point is that the person who has the right to use an application is not the owner but the user (i.e. tenant). Application developers must implement this specification into their applications. - -## Motivation - -It has been challenging to implement rental and mortgage functions with the ERC721 standard because it only has one role defined (which is the Owner). - -Currently, a security deposit is needed for trustless renting with ERC721, and ownership lockup within a contract is necessary whenever one chooses to mortgage their ERC721 property. The tracking and facilitation of these relationships must be done separately from the ERC721 standard. - -This proposal eliminates these requirements by integrating basic rights of tenantship and liens. By standardizing these functions, developers can more easily integrate rental and mortgage functions for their applications. - -## Specification - -This standard proposes three user roles: the **Lien Holder**, the **Owner**, and the **User**. Their rights are as follows: - -- A **Lien Holder** has the right to: - - 1. Transfer the **Owner** role - 2. Transfer the **User** role - -- An **Owner** has the right to: - - 1. Transfer the **Owner** role - 2. Transfer the **User** role - -- A **User** has the right to: - 1. Transfer the **User** role - -### ERC-2615 Interface - -```solidity -event TransferUser(address indexed from, address indexed to, uint256 indexed itemId, address operator); -event ApprovalForUser(address indexed user, address indexed approved, uint256 itemId); -event TransferOwner(address indexed from, address indexed to, uint256 indexed itemId, address operator); -event ApprovalForOwner(address indexed owner, address indexed approved, uint256 itemId); -event ApprovalForAll(address indexed owner, address indexed operator, bool approved); -event LienApproval(address indexed to, uint256 indexed itemId); -event TenantRightApproval(address indexed to, uint256 indexed itemId); -event LienSet(address indexed to, uint256 indexed itemId, bool status); -event TenantRightSet(address indexed to, uint256 indexed itemId,bool status); - -function balanceOfOwner(address owner) public view returns (uint256); -function balanceOfUser(address user) public view returns (uint256); -function userOf(uint256 itemId) public view returns (address); -function ownerOf(uint256 itemId) public view returns (address); - -function safeTransferOwner(address from, address to, uint256 itemId) public; -function safeTransferOwner(address from, address to, uint256 itemId, bytes memory data) public; -function safeTransferUser(address from, address to, uint256 itemId) public; -function safeTransferUser(address from, address to, uint256 itemId, bytes memory data) public; - -function approveForOwner(address to, uint256 itemId) public; -function getApprovedForOwner(uint256 itemId) public view returns (address); -function approveForUser(address to, uint256 itemId) public; -function getApprovedForUser(uint256 itemId) public view returns (address); -function setApprovalForAll(address operator, bool approved) public; -function isApprovedForAll(address requester, address operator) public view returns (bool); - -function approveLien(address to, uint256 itemId) public; -function getApprovedLien(uint256 itemId) public view returns (address); -function setLien(uint256 itemId) public; -function getCurrentLien(uint256 itemId) public view returns (address); -function revokeLien(uint256 itemId) public; - -function approveTenantRight(address to, uint256 itemId) public; -function getApprovedTenantRight(uint256 itemId) public view returns (address); -function setTenantRight(uint256 itemId) public; -function getCurrentTenantRight(uint256 itemId) public view returns (address); -function revokeTenantRight(uint256 itemId) public; -``` - -### ERC-2615 Receiver - -```solidity -function onERCXReceived(address operator, address from, uint256 itemId, uint256 layer, bytes memory data) public returns(bytes4); -``` - -### ERC-2615 Extensions - -Extensions here are provided to help developers build with this standard. - -#### 1. ERC721 Compatible functions - -This extension makes this standard compatible with ERC721. By adding the following functions, developers can take advantage of the existing tools for ERC721. - -Transfer functions in this extension will transfer both the **Owner** and **User** roles when the tenant right has not been set. Conversely, when the tenant right has been set, only the **Owner** role will be transferred. - -```solidity -function balanceOf(address owner) public view returns (uint256) -function ownerOf(uint256 itemId) public view returns (address) -function approve(address to, uint256 itemId) public -function getApproved(uint256 itemId) public view returns (address) -function transferFrom(address from, address to, uint256 itemId) public -function safeTransferFrom(address from, address to, uint256 itemId) public -function safeTransferFrom(address from, address to, uint256 itemId, bytes memory data) pubic -``` - -#### 2. Enumerable - -This extension is analogous to the enumerable extension of the ERC721 standard. - -```solidity -function totalNumberOfItems() public view returns (uint256); -function itemOfOwnerByIndex(address owner, uint256 index, uint256 layer)public view returns (uint256 itemId); -function itemByIndex(uint256 index) public view returns (uint256); -``` - -#### 3. Metadata - -This extension is analogous to the metadata extension of the ERC721 standard. - -```solidity -function itemURI(uint256 itemId) public view returns (string memory); -function name() external view returns (string memory); -function symbol() external view returns (string memory); -``` - -## How rentals and mortgages work - -This standard does not deal with token or value transfer. Other logic (outside the scope of this standard) must be used to orchestrate these transfers and to implement validation of payment. - -### Mortgage functions - -The following diagram demonstrates the mortgaging functionality. - -![concept image](../assets/eip-2615/mortgage-sequential.jpg "mortgage") - -Suppose Alice owns an NFT and wants to take out a mortgage, and Bob wants to earn interest by lending tokens to Alice. - -1. Alice approves the setting of a lien for the NFT Alice owns. -2. Alice sends a loan request to the mortgage contract. -3. Bob fills the loan request and transfers tokens to the mortgage contract. The lien is then set on the NFT by the mortgage contract. -4. Alice can now withdraw the borrowed tokens from the mortgage contract. -5. Alice registers repayment (anyone can pay the repayment). -6. Bob can finish the agreement if the agreement period ends and the agreement is kept (i.e. repayment is paid without delay). -7. Bob can revoke the agreement if the agreement is breached (e.g. repayment is not paid on time) and execute the lien and take over the ownership of the NFT. - -### Rental functions - -The following diagram demonstrates the rental functionality. - -![concept image](../assets/eip-2615/rental-sequential.jpg "rental") - -Suppose Alice owns NFTs and wants to rent out a NFT, and Bob wants to lease a NFT. - -1. Alice approves the setting of a tenant-right for the NFT Alice owns. -2. Alice sends a rental listing to the rental contract. -3. Bob fills the rental request, and the right to use the NFT is transferred to Bob. At the same time, the tenant-right is set, and Alice becomes not able to transfer the right to use the NFT. -4. Bob registers rent (anyone can pay the rent). -5. Alice can withdraw the rent from the rental contract. -6. Alice can finish the agreement if the agreement period has ended and the agreement is kept (i.e. rent is paid without delay). -7. Alice can revoke the agreement if the agreement is breached (e.g. rent is not paid on time) and revoke the tenant-right and take over the right to use the NFT. - -## Rationale - -There have been some attempts to achieve rentals or mortgages with ERC721. However, as I noted before, it has been challenging to achieve. I will explain the reasons and advantages of this standard below. - -### No security lockup for rentals - -To achieve trustless rental of NFTs with ERC721, it has been necessary to deposit funds as security. This is required to prevent malicious activity from tenants, as it is impossible to take back ownership once it is transferred. - -With this standard, security deposits are no longer needed since the standard natively supports rental and tenantship functions. - -### No ownership escrow when taking out a mortgage - -In order to take out a mortgage on NFTs, it has been necessary to transfer the NFTs to a contract as collateral. This is required to prevent the potential default risk of the mortgage. - -However, secured collateral with ERC721 hurts the utility of the NFT. Since most NFT applications provide services to the canonical owner of a NFT, the NFT essentially cannot be utilized under escrow. - -With ERC2615, it is possible to collateralize NFTs and use them at the same time. - -### Easy integration - -Because of the above reasons, a great deal of effort is required to implement rental and mortgage functions with ERC721. Adopting this standard is a much easier way to integrate rental and mortgage functionality. - -### No money/token transactions within tokens - -A NFT itself does not handle lending or rental functions directly. This standard is open-source, and there is no platform lockup. Developers can integrate it without having to worry about those risks. - -## Backward compatibility - -As mentioned in the specifications section, this standard can be fully ERC721 compatible by adding an extension function set. - -In addition, new functions introduced in this standard have many similarities with the existing functions in ERC721. This allows developers to easily adopt the standard quickly. - -## Test Cases - -When running the tests, you need to create a test network with Ganache-CLI: - -``` -ganache-cli -a 15 --gasLimit=0x1fffffffffffff -e 1000000000 -``` - -And then run the tests using Truffle: - -``` -truffle test -e development -``` - -Powered by Truffle and Openzeppelin test helper. - -## Implementation - -[Github Reposotory](https://github.com/kohshiba/ERC-X). - -## Security Considerations - -Since the external contract will control lien or tenant rights, flaws within the external contract directly lead to the standard's unexpected behavior. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2615.md diff --git a/EIPS/eip-2645.md b/EIPS/eip-2645.md index 8b23336209ac0e..4c293a919c5f4c 100644 --- a/EIPS/eip-2645.md +++ b/EIPS/eip-2645.md @@ -1,71 +1 @@ ---- -eip: 2645 -title: Hierarchical Deterministic Wallet for Layer-2 -author: Tom Brand , Louis Guthmann -discussions-to: https://ethereum-magicians.org/t/hierarchical-deterministic-wallet-for-computation-integrity-proof-cip-layer-2/4286 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-05-13 ---- - -## Simple Summary -In the context of Computation Integrity Proof (CIP) Layer-2 solutions such as ZK-Rollups, users are required to sign messages on new elliptic curves optimized for those environnements. We leverage existing work on Key Derivation ([BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) and [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)) to define an efficient way to securely produce CIP L2s private keys, as well as creating domain separation between Layer-2 applications. - -## Abstract -We provide a Derivation Path allowing a user to derive hierarchical keys for Layer-2 solutions depending on the zk-technology, the application, the user’s Layer-1 address, as well as an efficient grinding method to enforce the private key distribution within the curve domain. The propose Derivation Path is defined as follow -``` -m / purpose' / layer' / application' / eth_address_1' / eth_address_2' / index -``` - -## Motivation -In the context of Computation Integrity Proof (CIP) Layer-2 solutions such as ZK-Rollups, users are required to sign messages on new elliptic curves optimized for those environments. Extensive work has been done to make it secure on Bitcoin via [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) and [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). These protocols are the standard for wallets in the entire industry, independent of the underlying blockchain. As Layer-2 solutions are taking off, it is a necessary requirement to maintain the same standard and security in this new space. - -## Specification -Starkware keys are derived with the following [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)-compatible derivation path, with direct inspiration from [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki): -``` -m / purpose' / layer' / application' / eth_address_1' / eth_address_2' / index -``` -where: -* `m` - the seed. -* `purpose` - `2645` (the number of this EIP). -* `layer` - the 31 lowest bits of sha256 on the layer name. Serve as a domain separator between different technologies. In the context of `starkex`, the value would be `579218131`. -* `application` - the 31 lowest bits of sha256 of the application name. Serve as a domain separator between different applications. In the context of DeversiFi in June 2020, it is the 31 lowest bits of sha256(starkexdvf) and the value would be `1393043894`. -* `eth_address_1 / eth_address_2` - the first and second 31 lowest bits of the corresponding eth_address. -* `index` - to allow multiple keys per eth_address. - -As example, the expected path for address 0x0000....0000 assuming seed `m` and index 0 in the context of DeversiFi in June 2020: `m/2645'/579218131'/1393043894'/0'/0'/0` - -The key derivation should follow the following algorithm -``` -N = 2**256 -n = Layer2 curve order -path = stark derivation path -BIP32() = Official BIP-0032 derivation function on secp256k1 -hash = SHA256 -i = 0 -root_key = BIP32(path) -while True: - key = hash(root_key|i) - if (key < (N - (N % n))): - return key % n - i++ -``` -This algorithm has been defined to maintain efficiency on existing restricted devices. - -Nota Bene: At each round, the probability for a key to be greater than (N - (N % n)) is < 2^(-5). - -## Rationale -This EIP specifies two aspects of keys derivation in the context of Hierarchical Wallets: -- Derivation Path -- Grinding Algorithm to enforce a uniform distribution over the elliptic curve. -The derivation path is defined to allow efficient keys separation based on technology and application while maintaining a 1-1 relation with the Layer-1 wallet. In such a way, losing EIP-2645 wallets falls back to losing the Layer-1 wallet. - -## Backwards Compatibility -This standard complies with BIP43. - -## Security Considerations -This EIP has been defined to maintain separation of keys while providing foolproof logic on key derivation. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2645.md diff --git a/EIPS/eip-2678.md b/EIPS/eip-2678.md index 5daf0ac93a10fc..364ff1c412ad38 100644 --- a/EIPS/eip-2678.md +++ b/EIPS/eip-2678.md @@ -1,1042 +1 @@ ---- -eip: 2678 -title: Revised Ethereum Smart Contract Packaging Standard (EthPM v3) -author: g. nicholas d’andrea (@gnidan), Piper Merriam (@pipermerriam), Nick Gheorghita (@njgheorghita), Christian Reitwiessner (@chriseth), Ben Hauser (@iamdefinitelyahuman), Bryant Eisenbach (@fubuloubu) -discussions-to: https://ethereum-magicians.org/t/ethpm-v3-specification-working-group/4086 -status: Final -type: Standards Track -category: ERC -created: 2020-05-26 ---- - - -## Simple Summary - -A data format describing a smart contract software package. - - -## Abstract - -This EIP defines a data format for *package manifest* documents, -representing a package of one or more smart contracts, optionally -including source code and any/all deployed instances across multiple -networks. Package manifests are minified JSON objects, to be distributed -via content addressable storage networks, such as IPFS. Packages -are then published to on-chain EthPM registries, defined in -[EIP-1319](./eip-1319.md), from where they can be freely accessed. - -This document presents a natural language description of a formal -specification for version **3** of this format. - - -## Motivation - -This standard aims to encourage the Ethereum development ecosystem -towards software best practices around code reuse. By defining an open, -community-driven package data format standard, this effort seeks to -provide support for package management tools development by offering a -general-purpose solution that has been designed with observed common -practices in mind. - -- Updates the schema for a *package manifest* to be compatible with - the [metadata](https://solidity.readthedocs.io/en/latest/metadata.html) output for compilers. -- Updates the `"sources"` object definition to support a wider range of source file types and serve as [JSON input](https://solidity.readthedocs.io/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) for a compiler. -- Moves compiler definitions to a top-level `"compilers"` array in order to: - - Simplify the links between a compiler version, sources, and the - compiled assets. - - Simplify packages that use multiple compiler versions. -- Updates key formatting from `snake_case` to `camelCase` to be - more consistent with [JSON convention](https://google.github.io/styleguide/jsoncstyleguide.xml?showone=Property_Name_Format#Property_Name_Format). - -### Guiding Principles - -This specification makes the following assumptions about the document -lifecycle. - -1. Package manifests are intended to be generated programmatically by - package management software as part of the release process. - -2. Package manifests will be consumed by package managers during tasks - like installing package dependencies or building and deploying new - releases. - -3. Package manifests will typically **not** be stored alongside the - source, but rather by package registries *or* referenced by package - registries and stored in something akin to IPFS. - -4. Package manifests can be used to verify public deployments of source - contracts. - -### Use Cases - -The following use cases were considered during the creation of this -specification. - -* **owned**: A package which contains contracts which are not meant to be used by themselves but rather as base contracts to provide functionality to other contracts through inheritance. -* **transferable**: A package which has a single dependency. -* **standard-token**: A package which contains a reusable contract. -* **safe-math-lib**: A package which contains deployed instance of one of the package contracts. -* **piper-coin**: A package which contains a deployed instance of a reusable contract from a dependency. -* **escrow**: A package which contains a deployed instance of a local contract which is linked against a deployed instance of a local library. -* **wallet**: A package with a deployed instance of a local contract which is linked against a deployed instance of a library from a dependency. -* **wallet-with-send**: A package with a deployed instance which links against a deep dependency. -* **simple-auction**: Compiler `"metadata"` field output. - -## Package Specification - -### Conventions - -#### RFC2119 - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, -“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this -document are to be interpreted as described in RFC 2119. - -- - - -#### Prefixed vs Unprefixed - -A [prefixed](#prefixed) hexadecimal value begins with `0x`. -[Unprefixed](#unprefixed) values have no prefix. Unless otherwise -specified, all hexadecimal values **should** be represented with the -`0x` prefix. - -* **Prefixed**: `0xdeadbeef` -* **Unprefixed**: `deadbeef` - -### Document Format - -The canonical format is a single JSON object. Packages **must** conform -to the following serialization rules. - -- The document **must** be tightly packed, meaning no linebreaks or - extra whitespace. - -- The keys in all objects **must** be sorted alphabetically. - -- Duplicate keys in the same object are invalid. - -- The document **must** use - [UTF-8](https://en.wikipedia.org/wiki/UTF-8) - encoding. - -- The document **must** not have a trailing newline. - -- To ensure backwards compatibility, `manifest_version` is a forbidden - top-level key. - - -### Document Specification - -The following fields are defined for the package. Custom fields **may** -be included. Custom fields **should** be prefixed with `x-` to prevent -name collisions with future versions of the specification. - -* **See Also**: Formalized ([JSON-Schema](https://json-schema.org)) version of this specification: [package.spec.json](../assets/eip-2678/package.spec.json) -* **Jump To**: [Definitions](#object-definitions) - -### EthPM Manifest Version - -The `manifest` field defines the specification version that this -document conforms to. - -- Packages **must** include this field. - -* **Required**: Yes -* **Key**: `manifest` -* **Type**: String -* **Allowed Values**: `ethpm/3` - -### Package Name - -The `name` field defines a human readable name for this package. - -- Packages **should** include this field to be released on an EthPM - registry. - -- Package names **must** begin with a lowercase letter and be - comprised of only the lowercase letters `a-z`, numeric characters `0-9`, and the - dash character `-`. - -- Package names **must** not exceed 255 characters in length. - -* **Required**: If `version` is included. -* **Key**: `name` -* **Type**: String -* **Format**: **must** match the regular expression `^[a-z][-a-z0-9]{0,255}$` - -### Package Version - -The `version` field declares the version number of this release. - -- Packages **should** include this field to be released on an EthPM - registry. - -- This value **should** conform to the - [semver](http://semver.org/) version numbering - specification. - -* **Required**: If `name` is included. -* **Key**: `version` -* **Type**: String - -### Package Metadata - -The `meta` field defines a location for metadata about the package which -is not integral in nature for package installation, but may be important -or convenient to have on-hand for other reasons. - -- This field **should** be included in all Packages. - -* **Required**: No -* **Key**: `meta` -* **Type**: [Package Meta Object](#the-package-meta-object) - -### Sources - -The `sources` field defines a source tree that **should** comprise the -full source tree necessary to recompile the contracts contained in this -release. - -* **Required**: No -* **Key**: `sources` -* **Type**: Object (String: [Sources Object](#the-source-object)) - -### Contract Types - -The `contractTypes` field hosts the [Contract -Types](#contract-type) which have been included in this release. - -- Packages **should** only include contract types that can be found in - the source files for this package. - -- Packages **should not** include contract types from dependencies. - -- Packages **should not** include abstract contracts in the contract - types section of a release. - -* **Required**: No -* **Key**: `contractTypes` -* **Type**: Object (String: [Contract Type Object](#the-contract-type-object)) -* **Format**: Keys **must** be valid [Contract Aliases](#contract-alias).
Values **must** conform to the [Contract Type Object](#the-contract-type-object) definition. - -### Compilers - -The `compilers` field holds the information about the compilers and -their settings that have been used to generate the various -`contractTypes` included in this release. - -* **Required**: No -* **Key**: `compilers` -* **Type**: Array ([Compiler Information Object](#the-compiler-information-object)) - -### Deployments - -The `deployments` field holds the information for the chains on which -this release has [Contract Instances](#contract-instance) as well -as the [Contract Types](#contract-type) and other deployment -details for those deployed contract instances. The set of chains defined -by the [BIP122 URI](#bip122-uri) keys for this object **must** be -unique. There cannot be two different URI keys in a deployments field -representing the same blockchain. - -* **Required**: No -* **Key**: `deployments` -* **Type**: Object (String: Object(String: [Contract Instance Object](#the-contract-instance-object))) -* **Format**: Keys **must** be a valid BIP122 URI chain definition.
Values **must** be objects which conform to the following format:
- Keys **must** be valid [Contract Instance Names](#contract-instance-name)
- Values **must** be a valid [Contract Instance Object](#the-contract-instance-object) - -### Build Dependencies - -The `buildDependencies` field defines a key/value mapping of EthPM -packages that this project depends on. - -* **Required**: No -* **Key**: `buildDependencies` -* **Type**: Object (String: String) -* **Format**: Keys **must** be valid [package names](#package-name).
Values **must** be a [Content Addressable URI](#content-addressable-uri) which resolves to a valid package that conforms the same EthPM manifest version as its parent. - -### Object Definitions - -Definitions for different objects used within the Package. All objects -allow custom fields to be included. Custom fields **should** be prefixed -with `x-` to prevent name collisions with future versions of the -specification. - - -### The *Link Reference* Object - -A [Link Reference](#link-reference) object has the following -key/value pairs. All link references are assumed to be associated with -some corresponding [Bytecode](#bytecode). - -#### Offsets: `offsets` - -The `offsets` field is an array of integers, corresponding to each of -the start positions where the link reference appears in the bytecode. -Locations are 0-indexed from the beginning of the bytes representation -of the corresponding bytecode. This field is invalid if it references a -position that is beyond the end of the bytecode. - -* **Required**: Yes -* **Type**: Array - -#### Length: `length` - -The `length` field is an integer which defines the length in bytes of -the link reference. This field is invalid if the end of the defined link -reference exceeds the end of the bytecode. - -* **Required**: Yes -* **Type**: Integer - -#### Name: `name` - -The `name` field is a string which **must** be a valid -[Identifier](#identifier). Any link references which **should** be -linked with the same link value **should** be given the same name. - -* **Required**: No -* **Type**: String -* **Format**: **must** conform to the [Identifier](#identifier) format. - -### The *Link Value* Object - -Describes a single [Link Value](#link-value). - -A **Link Value object** is defined to have the following key/value -pairs. - - -#### Offsets: `offsets` - -The `offsets` field defines the locations within the corresponding -bytecode where the `value` for this link value was written. These -locations are 0-indexed from the beginning of the bytes representation -of the corresponding bytecode. - -* **Required**: Yes -* **Type**: Integer -* **Format**: See below. - -Format - -Array of integers, where each integer **must** conform to all of the -following. - -- greater than or equal to zero - -- strictly less than the length of the unprefixed hexadecimal - representation of the corresponding bytecode. - -#### Type: `type` - -The `type` field defines the `value` type for determining what is -encoded when [linking](#linking) the corresponding bytecode. - -* **Required**: Yes -* **Type**: String -* **Allowed Values**: `"literal"` for bytecode literals.
`"reference"` for named references to a particular [Contract Instance](#contract-instance) - -#### Value: `value` - -The `value` field defines the value which should be written when [linking](#linking) the corresponding bytecode. - -* **Required**: Yes -* **Type**: String -* **Format**: Determined based on `type`, see below. - -Format - -For static value *literals* (e.g. address), value **must** be a 0x-prefixed -hexadecimal string representing bytes. - - -To reference the address of a [Contract -Instance](#contract-instance) from the current package the value -should be the name of that contract instance. - -- This value **must** be a valid [Contract Instance - Name](#contract-instance-name). - -- The chain definition under which the contract instance that this - link value belongs to must contain this value within its keys. - -- This value **may not** reference the same contract instance that - this link value belongs to. - -To reference a contract instance from a [Package](#package) from -somewhere within the dependency tree the value is constructed as -follows. - -- Let `[p1, p2, .. pn]` define a path down the dependency tree. - -- Each of `p1, p2, pn` **must** be valid package names. - -- `p1` **must** be present in keys of the `buildDependencies` for the - current package. - -- For every `pn` where `n > 1`, `pn` **must** be present in the keys - of the `buildDependencies` of the package for `pn-1`. - -- The value is represented by the string - `::<...>::` where all of ``, - ``, `` are valid package names and `` is - a valid [Contract Name](#contract-name). - -- The `` value **must** be a valid [Contract - Instance Name](#contract-instance-name). - -- Within the package of the dependency defined by ``, all of the - following must be satisfiable: - - - There **must** be *exactly* one chain defined under the - `deployments` key which matches the chain definition that this - link value is nested under. - - - The `` value **must** be present in the keys - of the matching chain. - -### The *Bytecode* Object - -A bytecode object has the following key/value pairs. - -#### Bytecode: `bytecode` - -The `bytecode` field is a string containing the `0x` prefixed -hexadecimal representation of the bytecode. - -* **Required**: Yes -* **Type**: String -* **Format**: `0x` prefixed hexadecimal. - -#### Link References: `linkReferences` - -The `linkReferences` field defines the locations in the corresponding -bytecode which require [linking](#linking). - -* **Required**: No -* **Type**: Array -* **Format**: All values **must** be valid [Link Reference objects](#the-link-reference-object). See also below. - -Format - -This field is considered invalid if *any* of the [Link -References](#link-reference) are invalid when applied to the -corresponding `bytecode` field, *or* if any of the link references -intersect. - -Intersection is defined as two link references which overlap. - -#### Link Dependencies: `linkDependencies` - -The `linkDependencies` defines the [Link Values](#link-value) that -have been used to link the corresponding bytecode. - -* **Required**: No -* **Type**: Array -* **Format**: All values **must** be valid [Link Value objects](#the-link-value-object). See also below. - -Format - -Validation of this field includes the following: - -- Two link value objects **must not** contain any of the same values - for `offsets`. - -- Each [link value object](#the-link-value-object) **must** have a - corresponding [link reference object](#the-link-reference-object) under - the `linkReferences` field. - -- The length of the resolved `value` **must** be equal to the `length` - of the corresponding [Link Reference](#link-reference). - - -### The *Package Meta* Object - -The *Package Meta* object is defined to have the following key/value -pairs. - -#### Authors - -The `authors` field defines a list of human readable names for the -authors of this package. Packages **may** include this field. - -* **Required**: No -* **Key**: `authors` -* **Type**: Array(String) - -#### License - -The `license` field declares the license associated with this package. -This value **should** conform to the -[SPDX](https://spdx.org/licenses/) -format. Packages **should** include this field. If a file [Source -Object](#the-source-object) defines its own license, that license takes -precedence for that particular file over this package-scoped `meta` -license. - -* **Required**: No -* **Key**: `license` -* **Type**: String - -#### Description - -The `description` field provides additional detail that may be relevant -for the package. Packages **may** include this field. - -* **Required**: No -* **Key**: `description` -* **Type**: String - -#### Keywords - -The `keywords` field provides relevant keywords related to this package. - -* **Required**: No -* **Key**: `keywords` -* **Type**: Array(String) - -#### Links - -The `links` field provides URIs to relevant resources associated with -this package. When possible, authors **should** use the following keys -for the following common resources. - -- `website`: Primary website for the package. - -- `documentation`: Package Documentation - -- `repository`: Location of the project source code. - -* **Required**: No -* **Key**: `links` -* **Type**: Object (String: String) - -### The *Sources* Object - -A *Sources* object is defined to have the following fields. - -* **Key**: A unique identifier for the source file. (String) -* **Value**: [Source Object](#the-source-object) - -### The *Source* Object - -#### Checksum: `checksum` - -Hash of the source file. - -* **Required**: Only **if** the `content` field is missing and none of the provided URLs contain a content hash. -* **Key**: `checksum` -* **Value**: [Checksum Object](#the-checksum-object) - -#### URLS: `urls` - -Array of urls that resolve to the same source file. -- Urls **should** be stored on a content-addressable filesystem. - **If** they are not, then either `content` or `checksum` **must** be - included. - -- Urls **must** be prefixed with a scheme. - -- If the resulting document is a directory the key **should** be - interpreted as a directory path. - -- If the resulting document is a file the key **should** be - interpreted as a file path. - -* **Required**: If `content` is not included. -* **Key**: `urls` -* **Value**: Array(String) - -#### Content: `content` - -Inlined contract source. If both `urls` and `content` are provided, the `content` value -**must** match the content of the files identified in `urls`. - -* **Required**: If `urls` is not included. -* **Key**: `content` -* **Value**: String - -#### Install Path: `installPath` - -Filesystem path of source file. -- **Must** be a relative filesystem path that begins with a `./`. - -- **Must** resolve to a path that is within the current virtual - working directory. - -- **Must** be unique across all included sources. - -- **Must not** contain `../` to avoid accessing files outside of - the source folder in improper implementations. - -* **Required**: This field **must** be included for the package to be writable to disk. -* **Key**: `installPath` -* **Value**: String - -#### Type: `type` - -The `type` field declares the type of the source file. The field -**should** be one of the following values: `solidity`, `vyper`, -`abi-json`, `solidity-ast-json`. - -* **Required**: No -* **Key**: `type` -* **Value**: String - -#### License: `license` - -The `license` field declares the type of license associated with -this source file. When defined, this license overrides the -package-scoped [meta license](#license). - -* **Required**: No -* **Key**: `license` -* **Value**: String - -### The *Checksum* Object - -A *Checksum* object is defined to have the following key/value pairs. - -#### Algorithm: `algorithm` - -The `algorithm` used to generate the corresponding hash. Possible -algorithms include, but are not limited to `sha3`, `sha256`, `md5`, -`keccak256`. - -* **Required**: Yes -* **Type**: String - -#### Hash: `hash` - -The `hash` of a source files contents generated with the corresponding -algorithm. - -* **Required**: Yes -* **Type**: String - -### The *Contract Type* Object - -A *Contract Type* object is defined to have the following key/value -pairs. - -#### Contract Name: `contractName` - -The `contractName` field defines the [Contract -Name](#contract-name) for this [Contract -Type](#contract-type). - -* **Required**: If the [Contract Name](#contract-name) and [Contract Alias](#contract-alias) are not the same. -* **Type**: String -* **Format**: **Must** be a valid [Contract Name](#contract-name) - -#### Source ID: `sourceId` - -The global source identifier for the source file from which this -contract type was generated. - -* **Required**: No -* **Type**: String -* **Format**: **Must** match a unique source ID included in the [Sources Object](#the-sources-object) for this package. - -#### Deployment Bytecode: `deploymentBytecode` - -The `deploymentBytecode` field defines the bytecode for this [Contract -Type](#contract-type). - -* **Required**: No -* **Type**: Object -* **Format**: **Must** conform to the [Bytecode object](#the-bytecode-object) format. - -#### Runtime Bytecode: `runtimeBytecode` - -The `runtimeBytecode` field defines the unlinked `0x`-prefixed runtime -portion of [Bytecode](#bytecode) for this [Contract -Type](#contract-type). - -* **Required**: No -* **Type**: Object -* **Format**: **Must** conform to the [Bytecode object](#the-bytecode-object) format. - -#### ABI: `abi` - -* **Required**: No -* **Type**: Array -* **Format**: **Must** conform to the [Ethereum Contract ABI JSON](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#json) format. - -#### UserDoc: `userdoc` - -* **Required**: No -* **Type**: Object -* **Format**: **Must** conform to the [UserDoc](https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format#user-documentation) format. - -#### DevDoc: `devdoc` - -* **Required**: No -* **Type**: Object -* **Format**: **Must** conform to the [DevDoc](https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format#developer-documentation) format. - -### The *Contract Instance* Object - -A **Contract Instance Object** represents a single deployed [Contract -Instance](#contract-instance) and is defined to have the following -key/value pairs. - -#### Contract Type: `contractType` - -The `contractType` field defines the [Contract -Type](#contract-type) for this [Contract -Instance](#contract-instance). This can reference any of the -contract types included in this [Package](#package) *or* any of the -contract types found in any of the package dependencies from the -`buildDependencies` section of the [Package -Manifest](#package-manifest). - -* **Required**: Yes -* **Type**: String -* **Format**: See below. - -Format - -Values for this field **must** conform to *one of* the two formats -herein. - -To reference a contract type from this Package, use the format -``. - -- The `` value **must** be a valid [Contract - Alias](#contract-alias). - -- The value **must** be present in the keys of the `contractTypes` - section of this Package. - -To reference a contract type from a dependency, use the format -`:`. - -- The `` value **must** be present in the keys of the - `buildDependencies` of this Package. - -- The `` value **must** be be a valid [Contract - Alias](#contract-alias). - -- The resolved package for `` must contain the - `` value in the keys of the `contractTypes` section. - -#### Address: `address` - -The `address` field defines the [Address](#address) of the -[Contract Instance](#contract-instance). - -* **Required**: Yes -* **Type**: String -* **Format**: Hex encoded `0x` prefixed Ethereum address matching the regular expression `^0x[0-9a-fA-F]{40}$`. - -#### Transaction: `transaction` - -The `transaction` field defines the transaction hash in which this -[Contract Instance](#contract-instance) was created. - -* **Required**: No -* **Type**: String -* **Format**: `0x` prefixed hex encoded transaction hash. - -#### Block: `block` - -The `block` field defines the block hash in which this the transaction -which created this *contract instance* was mined. - -* **Required**: No -* **Type**: String -* **Format**: `0x` prefixed hex encoded block hash. - -#### Runtime Bytecode: `runtimeBytecode` - -The `runtimeBytecode` field defines the runtime portion of bytecode for -this [Contract Instance](#contract-instance). When present, the -value from this field supersedes the `runtimeBytecode` from the -[Contract Type](#contract-type) for this [Contract -Instance](#contract-instance). - -* **Required**: No -* **Type**: Object -* **Format**: **Must** conform to the [Bytecode Object](#the-bytecode-object) format. - -Every entry in the `linkReferences` for this bytecode **must** have a -corresponding entry in the `linkDependencies` section. - -### The *Compiler Information* Object - -The `compilers` field defines the various compilers and settings used -during compilation of any [Contract Types](#contract-type) or -[Contract Instance](#contract-instance) included in this package. - -A *Compiler Information* object is defined to have the following -key/value pairs. - -#### Name: `name` - -The `name` field defines which compiler was used in compilation. - -* **Required**: Yes -* **Key**: `name` -* **Type**: String - -#### Version: `version` - -The `version` field defines the version of the compiler. The field -**should** be OS agnostic (OS not included in the string) and take the -form of either the stable version in -[semver](http://semver.org/) format or if built on a -nightly should be denoted in the form of `-` ex: -`0.4.8-commit.60cc1668`. - -* **Required**: Yes -* **Key**: `version` -* **Type**: String - -#### Settings: `settings` - -The `settings` field defines any settings or configuration that was used -in compilation. For the `"solc"` compiler, this **should** conform to -the [Compiler Input and Output -Description](http://solidity.readthedocs.io/en/latest/using-the-compiler.html#compiler-input-and-output-json-description). - -* **Required**: No -* **Key**: `settings` -* **Type**: Object - -#### Contract Types: `contractTypes` - -A list of the [Contract Alias](#contract-alias) or [Contract Types](#contract-type) in this package -that used this compiler to generate its outputs. - -- All `contractTypes` that locally declare `runtimeBytecode` - **should** be attributed for by a compiler object. - -- A single `contractTypes` **must** not be attributed to more than one - compiler. - -* **Required**: No -* **Key**: `contractTypes` -* **Type**: Array([Contract Alias](#contract-alias)) - - -### BIP122 URI - -BIP122 URIs are used to define a blockchain via a subset of the -[BIP-122](https://github.com/bitcoin/bips/blob/master/bip-0122.mediawiki) -spec. - - blockchain:///block/ - -The `` represents the blockhash of the first block on the -chain, and `` represents the hash of the -latest block that’s been reliably confirmed (package managers should be -free to choose their desired level of confirmations). - -### Glossary - -The terms in this glossary have been updated to reflect the changes made -in V3. - -#### ABI -The JSON representation of the application binary interface. See the -official -[specification](https://solidity.readthedocs.io/en/develop/abi-spec.html) -for more information. - -#### Address -A public identifier for an account on a particular chain - -#### Bytecode -The set of EVM instructions as produced by a compiler. Unless otherwise -specified this should be assumed to be hexadecimal encoded, representing -a whole number of bytes, and [prefixed](#prefixed) with `0x`. - -Bytecode can either be linked or unlinked. (see -[Linking](#linking)) - -* **Unlinked Bytecode**: The hexadecimal representation of a contract’s EVM instructions that contains sections of code that requires [linking](#linking) for the contract to be functional.
The sections of code which are unlinked **must** be filled in with zero bytes.
**Example**: `0x606060405260e06000730000000000000000000000000000000000000000634d536f` -* **Linked Bytecode**: The hexadecimal representation of a contract’s EVM instructions which has had all [Link References](#link-reference) replaced with the desired [Link Values](#link-value). **Example**: `0x606060405260e06000736fe36000604051602001526040518160e060020a634d536f` - -#### Chain Definition -This definition originates from [BIP122 -URI](https://github.com/bitcoin/bips/blob/master/bip-0122.mediawiki). - -A URI in the format `blockchain:///block/` - -- `chain_id` is the unprefixed hexadecimal representation of the - genesis hash for the chain. - -- `block_hash` is the unprefixed hexadecimal representation of the - hash of a block on the chain. - -A chain is considered to match a chain definition if the the genesis -block hash matches the `chain_id` and the block defined by `block_hash` -can be found on that chain. It is possible for multiple chains to match -a single URI, in which case all chains are considered valid matches - -#### Content Addressable URI -Any URI which contains a cryptographic hash which can be used to verify -the integrity of the content found at the URI. - -The URI format is defined in RFC3986 - -It is **recommended** that tools support IPFS and Swarm. - -#### Contract Alias -This is a name used to reference a specific [Contract -Type](#contract-type). Contract aliases **must** be unique within a -single [Package](#package). - -The contract alias **must** use *one of* the following naming schemes: - -- `` - -- `` - -The `` portion **must** be the same as the [Contract -Name](#contract-name) for this contract type. - -The `` portion **must** match the regular expression -`^[-a-zA-Z0-9]{1,256}$`. - -#### Contract Instance -A contract instance a specific deployed version of a [Contract -Type](#contract-type). - -All contract instances have an [Address](#address) on some specific -chain. - -#### Contract Instance Name -A name which refers to a specific [Contract -Instance](#contract-instance) on a specific chain from the -deployments of a single [Package](#package). This name **must** be -unique across all other contract instances for the given chain. The name -must conform to the regular expression -`^[a-zA-Z_$][a-zA-Z0-9_$]{0,255}$` - -In cases where there is a single deployed instance of a given [Contract -Type](#contract-type), package managers **should** use the -[Contract Alias](#contract-alias) for that contract type for this -name. - -In cases where there are multiple deployed instances of a given contract -type, package managers **should** use a name which provides some added -semantic information as to help differentiate the two deployed instances -in a meaningful way. - -#### Contract Name -The name found in the source code that defines a specific [Contract -Type](#contract-type). These names **must** conform to the regular -expression `^[a-zA-Z_$][a-zA-Z0-9_$]{0,255}$`. - -There can be multiple contracts with the same contract name in a -projects source files. - -#### Contract Type -Refers to a specific contract in the package source. This term can be -used to refer to an abstract contract, a normal contract, or a library. -Two contracts are of the same contract type if they have the same -bytecode. - -Example: - - contract Wallet { - ... - } - -A deployed instance of the `Wallet` contract would be of of type -`Wallet`. - -#### Identifier -Refers generally to a named entity in the [Package](#package). - -A string matching the regular expression -`^[a-zA-Z][-_a-zA-Z0-9]{0,255}$` - -#### Link Reference -A location within a contract’s bytecode which needs to be linked. A link -reference has the following properties. - -* **`offset`**: Defines the location within the bytecode where the link reference begins. -* **`length`**: Defines the length of the reference. -* **`name`**: (optional) A string to identify the reference. - -#### Link Value -A link value is the value which can be inserted in place of a [Link -Reference](#link-reference) - -#### Linking -The act of replacing [Link References](#link-reference) with [Link -Values](#link-value) within some [Bytecode](#bytecode). - -#### Package -Distribution of an application’s source or compiled bytecode along with -metadata related to authorship, license, versioning, et al. - -For brevity, the term **Package** is often used metonymously to mean -[Package Manifest](#package-manifest). - -#### Package Manifest -A machine-readable description of a package. - -#### Prefixed -[Bytecode](#bytecode) string with leading `0x`. - -* **Example**: `0xdeadbeef` - -#### Unprefixed -Not [Prefixed](#prefixed). - -* **Example**: `deadbeef` - -## Rationale - -### Minification - -EthPM packages are distributed as alphabetically-ordered & minified JSON to ensure consistency. -Since packages are published on content-addressable filesystems (eg. IPFS), this restriction -guarantees that any given set of contract assets will always resolve to the same content-addressed URI. - -### Package Names - -Package names are restricted to lower-case characters, numbers, and `-` to improve the readability -of the package name, in turn improving the security properties for a package. A user is more likely -to accurately identify their target package with this restricted set of characters, and not confuse -a malicious package that disguises itself as a trusted package with similar but different -characters (e.g. `O` and `0`). - -### BIP122 - -The BIP-122 standard has been used since EthPM v1 since it is an industry standard URI scheme for -identifying different blockchains and distinguishing between forks. - -### Compilers - -Compilers are now defined in a top-level array, simplifying the task for tooling to identify the compiler types -needed to interact with or validate the contract assets. This also removes unnecessarily duplicated -information, should multiple `contractTypes` share the same compiler type. - -## Backwards Compatibility - -To improve understanding and readability of the EthPM spec, the -`manifest_version` field was updated to `manifest` in v3. To ensure -backwards compatibility, v3 packages **must** define a top-level -`"manifest"` with a value of `"ethpm/3"`. Additionally, -`"manifest_version"` is a forbidden top-level key in v3 packages. - - -## Security Considerations - -Using EthPM packages implicitly requires importing &/or executing code written by others. The EthPM spec -guarantees that when using a properly constructed and released EthPM package, the user will have the exact same -code that was included in the package by the package author. However, it is impossible to guarantee that this code -is safe to interact with. Therefore, it is critical that end users only interact with EthPM packages authored and -released by individuals or organizations that they trust to include non-malicious code. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2678.md diff --git a/EIPS/eip-2680.md b/EIPS/eip-2680.md index 497cac5080d0c9..30a11c29f8da28 100644 --- a/EIPS/eip-2680.md +++ b/EIPS/eip-2680.md @@ -1,136 +1 @@ ---- -eip: 2680 -title: Ethereum 2 wallet layout -author: Jim McDonald -discussions-to: https://ethereum-magicians.org/t/eip-2680-ethereum-2-wallet-layout/4323 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-05-29 ---- - -## Simple Summary - -A standard layout and naming format for walletstore and keystore for both hierarchical (e.g. filesystem, Amazon S3) and non-hierarchical (key/value) storage systems. - -## Abstract - -Ethereum wallets have no standards for their layout in persistent storage, making different wallet implementations incompatible. This defines a standard for the placement of Ethereum walletstores and keystores, making it possible for different software to work with the same wallets and keys. - -## Motivation - -A standard layout for wallets and accounts allows interoperability between validators. This benefits users, as they can move from one validator software to another (and back) without requiring movement of files. This is important because any movement of files containing keys involves danger of either deleting them or duplicating them, both of which could cause loss of access to funds. - -## Specification - -There are four elements for a wallet that need to be addressed. These are defined below. - -### Base location -The base location is required to be well-known, either pre-defined or defined by the storage system's connection parameters. - -For filesystems the pre-defined base location for different operating systems is as follows: - - - Windows: `%APPDATA%\ethereum2\wallets` - - MacOSX: `${HOME}/Library/Application Support/ethereum2/wallets` - - Linux: `${HOME}/.config/ethereum2/wallets` - -For other hierarchical stores, for example Amazon S3, the base location MUST be the lower-case hex string representing the [SHA-256](../assets/eip-2680/sha256-384-512.pdf) hash of the string "Ethereum 2 wallet:" appended with the identifier for the hierarchical store. For example, if the account ID for a user's Amazon S3 account is "AbC0438EB" then: - - - string would be `Ethereum 2 wallet:AbC0438EB` - - SHA-256 hash of string would be the byte array `0x991ec14a8d13836b10d8c3039c9e30876491cb8aa9c9c16967578afc815c9229` - - base location would be the string `991ec14a8d13836b10d8c3039c9e30876491cb8aa9c9c16967578afc815c9229` - -For non-hierarchical stores there is no base location. - -### Wallet container -The wallet container holds the walletstore and related keystores. - -The wallet container is identified by the wallet's UUID. It MUST be a string following the syntactic structure as laid out in [section 3 of RFC 4122](https://tools.ietf.org/html/rfc4122#section-3). - -### Walletstore -The walletstore element contains the walletstore and is held within the wallet container. It is identified by the wallet's UUID. It MUST be a string following the syntactic structure as laid out in [section 3 of RFC 4122](https://tools.ietf.org/html/rfc4122#section-3). - -### Keystore -The keystore element contains the keystore for a given key and is held within the wallet container. It is identified by the key's UUID. It MUST be a string following the syntactic structure as laid out in [section 3 of RFC 4122](https://tools.ietf.org/html/rfc4122#section-3). - -## Hierarchical store example -Hierarchical stores are a common way to store and organize information. The most common example is the filesystem, but a number of object-based stores such as Amazon S3 also provide hierarchical naming. - -Putting these elements together for a sample wallet with wallet UUID `1f031fff-c51d-44fc-8baf-d6b304cb70a7` and key UUIDs `1302106c-8441-4e2e-b687-6c77f49fc624` and `4a320100-83fd-4db7-8126-6d6d205ba834` gives the following layout: - -``` -- 1f031fff-c51d-44fc-8baf-d6b304cb70a7 -+- 1302106c-8441-4e2e-b687-6c77f49fc624 -+- 1f031fff-c51d-44fc-8baf-d6b304cb70a7 -+- 4a320100-83fd-4db7-8126-6d6d205ba834 -``` - -### Non-hierarchical store example -Non-hierarchical stores use a simplified approach where the wallet UUID and key UUIDs are concatenated using the ':' character. Using the same example wallet and key UUIDs as above would result in objects with the following keys: - -``` -1f031fff-c51d-44fc-8baf-d6b304cb70a7:1302106c-8441-4e2e-b687-6c77f49fc624 -1f031fff-c51d-44fc-8baf-d6b304cb70a7:1f031fff-c51d-44fc-8baf-d6b304cb70a7 -1f031fff-c51d-44fc-8baf-d6b304cb70a7:4a320100-83fd-4db7-8126-6d6d205ba834 -``` - -### Protecting against concurrent write access -TBD - -### Iterating over wallets -In the case of hierarchical stores and iteration-capable non-hierarchical stores iteration over wallets is a matter of iterating over the files in the root container. - -An implementer MAY include an index in the base location. If so then it MUST follow the structure as specified in the following "Index format" section. - -### Iterating over accounts -In the case of hierarchical stores iteration over accounts is a matter of iterating over the files in the wallet container. - -An implementer MAY include an index within a wallet container for accounts within that wallet. If so then it MUST follow the structure as specified in the following "Index format" section. - -### Index format -The index format is the same for both wallets and accounts, following a standard JSON schema. - -```json -{ - "type": "array", - "items": { - "type": "object", - "properties": { - "uuid": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "uuid", - "name" - ] - } -} -``` - -The index MUST use the identifier 'index'. - -Public keys must NOT be stored in the index. - -## Rationale - -A standard for walletstores, similar to that for keystores, provides a higher level of compatibility between wallets and allows for simpler wallet and key interchange between them. - -## Implementation - -A Go implementation of the filesystem layout can be found at [https://github.com/wealdtech/go-eth2-wallet-filesystem](https://github.com/wealdtech/go-eth2-wallet-filesystem). - -A Go implementation of the Amazon S3 layout can be found at [https://github.com/wealdtech/go-eth2-wallet-s3](https://github.com/wealdtech/go-eth2-wallet-s3). - -## Security Considerations - -Locations for wallet stores are defined to be within each user's personal space, reducing the possibility of accidental exposure of information. It is, however, still possible for permissions to be set such that this data is world-readable, and applications implementing this EIP should attempt to set, and reset, permissions to ensure that only the relevant user has access to the information. - -The names for both wallet and key stores are UUIDs, ensuring that no data is leaked from the metadata. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2680.md diff --git a/EIPS/eip-2746.md b/EIPS/eip-2746.md index 22aa20efbabfb9..fc6c50ee259041 100644 --- a/EIPS/eip-2746.md +++ b/EIPS/eip-2746.md @@ -1,220 +1 @@ ---- -eip: 2746 -title: Rules Engine Standard -author: Aaron Kendall (@jaerith), Juan Blanco (@juanfranblanco) -discussions-to: https://ethereum-magicians.org/t/eip-2746-rules-engine-interface/4435 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-06-20 ---- - -## Simple Summary -An interface for using a smart contract as a rules engine. A single deployed contract can register a data domain, create sets of rules that perform actions on that domain, and then invoke a set as an atomic transaction. - -## Abstract -This standard proposes an interface that will allow the creation of hierarchal sets of rules (i.e., RuleTrees) that can be invoked to evaluate and manipulate a registered data domain. At the time of this draft, all intentions to insert additional functionality onto the blockchain requires the coding and creation of a newly deployed contract. However, this standard will allow users to deploy a contract just once, one which will then allow them to create (and invoke) pipelines of commands within that contract. - -## Motivation -At the time of this draft, all development for Ethereum requires writing the code that forms smart contracts and then deploying those contracts to Ethereum. In order to create a proper contract, many considerations must be taken into account when designing and implementing the code, especially in terms of efficiency (i.e., gas cost) and security. Even the simplest contracts require a certain amount of vigilance and examination, before and after deployment. These requirements pertain to all cases, even for simple cases of examining a value and/or altering it. - -These technical challenges might form an obstacle for many others who might wish to create software around Ethereum. Less technical companies and users might also want to configure and deploy simple functionality onto the chain, without knowing the relevant languages or details necessary. By having the data domain and the predefined actions (i.e., types of rules) implemented along with this interface, a deployed instance of such a rules engine contract can provide efficient and safe functionality to no-code or little-code clients, allowing more users of various technical proficiency to interact with the Ethereum ecosystem. - -## Specification -For the clarification of terminology, an Attribute is a registered data point within the data domain, representing data that exists either in the rules engine contract or elsewhere. A Rule is an predefined action that occurs upon a single data point (i.e., Attribute) in the predefined data domain. For example, a Rule could check whether the Attribute 'TokenAmt' has a value less than the RHL (i.e., right-hand value) of 10. A RuleSet is a collection of Rules, where their collection invocation creates a boolean result that determines the navigational flow of execution between RuleSets. A RuleTree is a collection of RuleSets that are organized within a hierarchy, where RuleSets can contain other RuleSets. - -```solidity -pragma solidity ^0.6.0; - -/** - @title ERC-2746 Rules Engine Standard - @dev See https://eips.ethereum.org/EIPS/eip-2746 - */ - interface ERCRulesEngine { - - /** - @dev Should emit when a RuleTree is invoked. - The `ruler` is the ID and owner of the RuleTree being invoked. It is also likely msg.sender. - */ - event CallRuleTree( - address indexed ruler - ); - - /** - @dev Should emit when a RuleSet is invoked. - The `ruler` is the ID and owner of the RuleTree in which the RuleSet is stored. It is also likely msg.sender. - The 'ruleSetId' is the ID of the RuleSet being invoked. - */ - event CallRuleSet( - address indexed ruler, - bytes32 indexed tmpRuleSetId - ); - - /** - @dev Should emit when a Rule is invoked. - The `ruler` is the ID and owner of the RuleTree in which the RuleSet is stored. It is also likely msg.sender. - The 'ruleSetId' is the ID of the RuleSet being invoked. - The 'ruleId' is the ID of the Rule being invoked. - The 'ruleType' is the type of the rule being invoked. - */ - event CallRule( - address indexed ruler, - bytes32 indexed ruleSetId, - bytes32 indexed ruleId, - uint ruleType - ); - - /** - @dev Should emit when a RuleSet fails. - The `ruler` is the ID and owner of the RuleTree in which the RuleSet is stored. It is also likely msg.sender. - The 'ruleSetId' is the ID of the RuleSet being invoked. - The 'severeFailure' is the indicator of whether or not the RuleSet is a leaf with a 'severe' error flag. - */ - event RuleSetError ( - address indexed ruler, - bytes32 indexed ruleSetId, - bool severeFailure - ); - - /** - @notice Adds a new Attribute to the data domain. - @dev Caller should be the deployer/owner of the rules engine contract. An Attribute value can be an optional alternative if it's not a string or numeric. - @param _attrName Name/ID of the Attribute - @param _maxLen Maximum length of the Attribute (if it is a string) - @param _maxNumVal Maximum numeric value of the Attribute (if it is numeric) - @param _defaultVal The default value for the Attribute (if one is not found from the source) - @param _isString Indicator of whether or not the Attribute is a string - @param _isNumeric Indicator of whether or not the Attribute is numeric - */ - function addAttribute(bytes32 _attrName, uint _maxLen, uint _maxNumVal, string calldata _defaultVal, bool _isString, bool _isNumeric) external; - - /** - @notice Adds a new RuleTree. - @param _owner Owner/ID of the RuleTree - @param _ruleTreeName Name of the RuleTree - @param _desc Verbose description of the RuleTree's purpose - */ - function addRuleTree(address _owner, bytes32 _ruleTreeName, string calldata _desc) external; - - /** - @notice Adds a new RuleSet onto the hierarchy of a RuleTree. - @dev RuleSets can have child RuleSets, but they will only be called if the parent's Rules execute to create boolean 'true'. - @param _owner Owner/ID of the RuleTree - @param _ruleSetName ID/Name of the RuleSet - @param _desc Verbose description of the RuleSet - @param _parentRSName ID/Name of the parent RuleSet, to which this will be added as a child - @param _severalFailFlag Indicator of whether or not the RuleSet's execution (as failure) will result in a failure of the RuleTree. (This flag only applies to leaves in the RuleTree.) - @param _useAndOp Indicator of whether or not the rules in the RuleSet will execute with 'AND' between them. (Otherwise, it will be 'OR'.) - @param _failQuickFlag Indicator of whether or not the RuleSet's execution (as failure) should immediately stop the RuleTree. - */ - function addRuleSet(address _owner, bytes32 _ruleSetName, string calldata _desc, bytes32 _parentRSName, bool _severalFailFlag, bool _useAndOp, bool _failQuickFlag) external; - - /** - @notice Adds a new Rule into a RuleSet. - @dev Rule types can be implemented as any type of action (greater than, less than, etc.) - @param _owner Owner/ID of the RuleTree - @param _ruleSetName ID/Name of the RuleSet to which the Rule will be added - @param _ruleName ID/Name of the Rule being added - @param _attrName ID/Name of the Attribute upon which the Rule is invoked - @param _ruleType ID of the type of Rule - @param _rightHandValue The registered value to be used by the Rule when performing its action upon the Attribute - @param _notFlag Indicator of whether or not the NOT operator should be performed on this Rule. - */ - function addRule(address _owner, bytes32 _ruleSetName, bytes32 _ruleName, bytes32 _attrName, uint _ruleType, string calldata _rightHandValue, bool _notFlag) external; - - /** - @notice Executes a RuleTree. - @param _owner Owner/ID of the RuleTree - */ - function executeRuleTree(address _owner) external returns (bool); - - /** - @notice Retrieves the properties of a Rule. - @param _owner Owner/ID of the RuleTree - @param _ruleSetName ID/Name of the RuleSet where the Rule resides - @param _ruleIdx Index of the rule in the RuleSet's listing - @return bytes32 ID/Name of Rule - @return uint Type of Rule - @return bytes32 Target Attribute of Rule - @return string Value mentioned in Rule - @return bool Flag for NOT operator in Rule - @return bytes32[] Values that should be provided in delegated call (if Rule is custom operator) - */ - function getRuleProps(address _owner, bytes32 _ruleSetName, uint _ruleIdx) external returns (bytes32, uint, bytes32, string memory, bool, bytes32[] memory); - - /** - @notice Retrieves the properties of a RuleSet - @param _owner Owner/ID of the RuleTree - @param _ruleSetName ID/Name of the RuleSet - @return string Verbose description of the RuleSet - @return bool Flag that indicates whether this RuleSet's failure (if a leaf) will cause the RuleTree to fail - @return bool Flag that indicates whether this RuleSet uses the AND operator when executing rules collectively - @return uint Indicates the number of rules hosted by this RuleSet - @return bytes32[] The list of RuleSets that are children of this RuleSet - */ - function getRuleSetProps(address _owner, bytes32 _ruleSetName) external returns (string memory, bool, bool, uint, uint, bytes32[] memory); - - /** - @notice Retrieves the properties of a RuleSet - @param _owner Owner/ID of the RuleTree - @return bytes32 Name of the RuleTree - @return string Verbose description of the RuleTree - @return bytes32 ID/Name of the RuleSet that serves as the root node for the RuleTree - */ - function getRuleTreeProps(address _owner) external returns (bytes32, string memory, bytes32); - - /** - @notice Removes a RuleTree. - @param _owner Owner/ID of the RuleTree - */ - function removeRuleTree(address _owner) external returns (bool); -} -``` - -### Considerations - -An argument could be made for interface functions that allow a RuleTree's owner to include others users as executors of the RuleTree. - -Another argument could be made for interface functions that allow an administrator to configure the origin point of an Attribute, such as whether the Attribute's value comes from a data structure (internal to the rules engine contract) or from calling a contract method (like an implementation of the [Diamond Standard](https://github.com/ethereum/EIPs/issues/2535)). - -Yet another argument could be made for interface functions that allow an administrator to extend the functionality catalog provided by the rules engine, by allowing other contracts' methods to be added as a rule operation. - -Also, an argument could be made for functions that calculate and report the range of potential cost for invoking a RuleTree. Unlike the normal execution of a contract method, the Ethereum transaction costs of invoking a RuleTree are more dynamic, depending on its depth/breadth and the navigational flow during invocation. Since the general cost of a RuleTree is unknown until the time of invocation, these functions could report the minimal amount of gas for a transaction (i.e., none of the Rules in a RuleTree are invoked) and the maximum amount for a transaction (i.e., all Rules in a RuleTree are invoked). - -### Example - -A company wishes to deploy a contract with data points and functionality that are predefined and/or under the control of an administrator, and it aims to build a no-code client that will allow less-technical users to define actions within the rules engine contract. In this example, the company wants one of its users to write the rules in a proprietary markup language, in order for the calculation of a VAT to be determined. For the sake of transparency, [these rules](https://ipfs.infura.io/ipfs/QmPrZ9959c7SzzqdLkVgX28xM7ZrqLeT3ydvRAHCaL1Hsn) are published onto IPFS, so that they are accessible to auditors and possibly government officials. The no-code client will then know how to parse the rules from the markup and communicate with the rules engine contract, establishing the RuleTree to be invoked later by the company's user(s) or off-chain programs. - -In order to calculate the value of the VAT, these provided rules invoke simple mathematical operations that can perform the calculation. However, the implementation of the rules engine contract could possess other functionality called by rules, ones that could execute more complicated logic or call the methods of other contracts. - -## Rationale - -### Attributes - -The data points are abstracted in order to let the implementation provide the mechanism for retrieving/populating the data. Data can be held by an internal data structure, another contract's method, or any number of other options. - -### Events - -The events specified will help the caller of the RuleTree after execution, so that they may ascertain the navigational flow of RuleSet execution within the RuleTree and so that they may understand which RuleSets failed. - -### Right-Hand Value - -In the function addRule(), the data type for the right-hand value is 'string' since the rule's action depends on its type, meaning that the value must be provided in a generic form. In the case of a Rule that performs numerical operations, the provided value could be transformed into a number when stored in the Rule. - -## Implementation -- [Wonka](https://github.com/Nethereum/Wonka/tree/master/Solidity/WonkaEngine) -- [Wonka Rules Editor](https://github.com/jaerith/WonkaRulesBlazorEditor) - -The Wonka implementation supports this proposed interface and also implements all of the additional considerations mentioned above. - -## Security Considerations - -The deployer of the contract should be the owner and administrator, allowing for the addition of Attributes and RuleTrees. Since a RuleTree is owned by a particular EOA (or contract address), the only accounts that should be able to execute the RuleTree should be its owner or the contract's owner/administrator. If Attributes are defined to exist as data within other contracts, the implementation must take into account the possibility that RuleTree owners must have the security to access the data in those contracts. - -## References - -**Standards** -- [EIP-2535 Diamond Standard](./eip-2535.md) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2746.md diff --git a/EIPS/eip-2767.md b/EIPS/eip-2767.md index 87ac14f0f4c6d7..415f9c3c277681 100644 --- a/EIPS/eip-2767.md +++ b/EIPS/eip-2767.md @@ -1,123 +1 @@ ---- -eip: 2767 -title: Contract Ownership Governance -author: Soham Zemse (@zemse), Nick Mudge (@mudgen) -discussions-to: https://github.com/ethereum/EIPs/issues/2766 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-07-04 -requires: 20, 165, 173 ---- - -## Simple Summary - -A standard for Governance contracts that holds the administrative ownership of other smart contracts with voting power distributed as `ERC-20` tokens. - -## Abstract - -The following standard defines the implementation of a standard API for a Governance smart contract based on `ERC-20`. Existing `ERC-173` compatible contracts can upgrade from private key wallet ownership to a Governance smart contract. Adhering to a standard API enables general tools to populate governance information of various projects, thus increasing transparency. - -## Motivation - -Traditionally, many contracts that require that they be owned or controlled in some way use `ERC-173` which standardized the use of ownership in the smart contracts. For example to withdraw funds or perform administrative actions. - -```solidity -contract dApp { - function doSomethingAdministrative() external onlyOwner { - // admin logic that can be performed by a single wallet - } -} -``` - -Often, such administrative rights for a contract are written for maintenance purpose but users need to trust the owner. Rescue operations by an owner have raised questions on decentralised nature of the projects. Also, there is a possibility of compromise of an owner's private key. - -At present, many governance implementations by ambitious projects need users to visit a specific UI to see governance information about their project. Some examples of live implementations having different API that does the same thing are [Compound Governance](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol#L27), [Uniswap Governance](https://github.com/Uniswap/governance/blob/master/contracts/GovernorAlpha.sol#L27) and [Sushiswap Governance](https://github.com/sushiswap/sushiswap/blob/master/contracts/GovernorAlpha.sol#L45). It's just like if the ERC-20 standard wasn't finalized, then token projects would have their own block explorer. Adhering to a standard API would enable general tools (like Etherscan) to populate governance information, thus increasing transparency to users. Using widely popular `ERC-20` token as a governance token, existing tools built to work with `ERC-20` can already display voters. This can result in a wide adoption for contract governance over private key based ownership. - -## Specification - -A Governance contract that is compliant with `ERC-2767` shall implement the following interfaces: - -```solidity -/// @title ERC-2767 Governance -/// @dev ERC-165 InterfaceID: 0xd8b04e0e -interface ERC2767 is ERC165 { - /// @notice Gets number votes required for achieving consensus - /// @dev Should cost less than 30000 gas - /// @return Required number of votes for achieving consensus - function quorumVotes() external view returns (uint256); - - /// @notice The address of the Governance ERC20 token - function token() external view returns (address); -} -``` - -### `ERC-20` Governance Token - -An `ERC-2767` Governance Contract should reference an address through `token()` that implements `ERC-20` interface. `token()` is allowed to return self address (`address(this)`), if `ERC-20` functionalities are implemented in the same contract (one can consider checking out Diamond Standard [`ERC-2535`](https://eips.ethereum.org/EIPS/eip-2535) to optimise contract size). - -Implementations are allowed to have varying `ERC-20`'s `totalSupply()` (through any standard of minting or burning). But having a fixed `quorumVotes()` return value in this case would cause required votes consensus in `%` with respect to `totalSupply()` to change. To automatically account for this, any custom logic under `quorumVotes()` is allowed to return for e.g. `51%` of `totalSupply()`. - -### `ERC-165` Interface Identification - -An `ERC-2767` Governance Contract should also implement `ERC-165`. This helps general tools to identify whether a contract is a `ERC-2767` Governance contract. - -```solidity -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -## Rationale - -The goals of this EIP have been the following: - -- Standardize API of Governance contracts to make it easy for analysis tools to be built. -- Encourage use of `ERC-20` based weighted governance over existing multi-sig (_generally limited to 50 max owners_) for big projects. -- Encourage existing `ERC-173` ownership smart contracts / projects to move to Governance based ownership by removing the effort needed to host custom UI for their project. -- Encourage availability of publicly audited governance contracts, just like `ERC-20` which anyone can use. -- Make it possible to utilize existing `ERC-20` tools for owners of governance token analysis. -- Make future protocols possible that need to interact with governances of multiple projects. -- Keep this EIP minimal and allow another EIPs to standardize any specific functionalities. - -## Backwards Compatibility - -Smart contracts that are `ERC-173` compliant can transfer their ownership to a Governance contract. This enables such contracts to become compatible with `ERC-2767` Governance. - -However, there are some existing projects with governance implementations and most of them have custom APIs ([Compound Governance](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol#L27), [Uniswap Governance](https://github.com/Uniswap/governance/blob/master/contracts/GovernorAlpha.sol#L27) and [Sushiswap Governance](https://github.com/sushiswap/sushiswap/blob/master/contracts/GovernorAlpha.sol#L45)), since a standard did not exist. Not having an `ERC-2767` compatible governance contract means only that general tools might not be able to populate their governance information without including some special code for the project. - -For existing governance contracts to get compatible with `ERC-2767`: - -1. Projects can deploy a new governance contract and transfer ownership to it to be `ERC-2767` compatible. This is suitable for those who use Multi-sig wallets for Governance. -2. It is understood that redeploying governance contracts would be a troublesome task, and contracts who already have functionality similar to `ERC-20` based (weighted votes) have a bit advanced way to avoid it. Basically, they can create a forwarder contract implements `ERC-2767` and forwards all calls to the actual non-standard methods. Projects can list the forwarder contract to display the information project's governance info without requiring any custom code in analysys tool, but this might have certain limitations depending on the project's existing governance implementation. Specification of forwarder contract is out of scope for this EIP and it may be addressed in another EIP if required. - - - -## Implementation - -The reference implementations are available in this [repository](https://github.com/zemse/contract-ownership-governance). Publicly audited implementations will be included in future. - -## Security Considerations - -Implementers are free to choose between On-chain and Off-chain consensus. Exact specification is out of scope for this standard (open for other EIPs to standardize). However, this section mentions points that implementers can consider. - -#### On-chain - -In such implementations, community can create transaction proposals and vote on it by sending on-chain transactions. - -- OpenZeppelin Snapshots can be used to prevent double voting. - -#### Off-chain - -- The signatures in off-chain governance implementation can follow recommendations of `ERC-191` or `ERC-712`. -- To prevent replaying signatures, it'd be best if executer is required to sort the signatures based on increasing addresses. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2767.md diff --git a/EIPS/eip-2770.md b/EIPS/eip-2770.md index db0b5d726f7a40..d9ff7b34c7603f 100644 --- a/EIPS/eip-2770.md +++ b/EIPS/eip-2770.md @@ -1,207 +1 @@ ---- -eip: 2770 -title: Meta-Transactions Forwarder Contract -author: Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh) -discussions-to: https://ethereum-magicians.org/t/erc-2770-meta-transactions-forwarder-contract/5391 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-07-01 -requires: 712, 2771 ---- - -## Simple Summary -Standardized contract interface for extensible meta-transaction forwarding. - -## Abstract - -This proposal defines an external API of an extensible Forwarder whose responsibility is to validate transaction -signatures on-chain and expose the signer to the destination contract, that is expected to accommodate all use-cases. -The ERC-712 structure of the forwarding request can be extended allowing wallets to display readable data even -for types not known during the Forwarder contract deployment. - -## Motivation - -There is a growing interest in making it possible for Ethereum contracts to -accept calls from externally owned accounts that do not have ETH to pay for -gas. - -This can be accomplished with meta-transactions, which are transactions that have been signed as plain data by one -externally owned account first and then wrapped into an Ethereum transaction by a different account. - -`msg.sender` is a transaction parameter that can be inspected by a contract to -determine who signed the transaction. The integrity of this parameter is -guaranteed by the Ethereum EVM, but for a meta-transaction verifying -`msg.sender` is insufficient, and signer address must be recovered as well. - -The Forwarder contract described here allows multiple Gas Relays and Relay Recipient contracts to rely -on a single instance of the signature verifying code, improving reliability and security -of any participating meta-transaction framework, as well as avoiding on-chain code duplication. - -## Specification -The Forwarder contract operates by accepting a signed typed data together with it's ERC-712 signature, -performing signature verification of incoming data, appending the signer address to the data field and -performing a call to the target. - -### Forwarder data type registration -Request struct MUST contain the following fields in this exact order: -``` -struct ForwardRequest { - address from; - address to; - uint256 value; - uint256 gas; - uint256 nonce; - bytes data; - uint256 validUntil; -} -``` -`from` - an externally-owned account making the request \ -`to` - a destination address, normally a smart-contract\ -`value` - an amount of Ether to transfer to the destination\ -`gas` - an amount of gas limit to set for the execution\ -`nonce` - an on-chain tracked nonce of a transaction\ -`data` - the data to be sent to the destination\ -`validUntil` - the highest block number the request can be forwarded in, or 0 if request validity is not time-limited - -The request struct MAY include any other fields, including nested structs, if necessary. -In order for the Forwarder to be able to enforce the names of the fields of this struct, only registered types are allowed. - -Registration MUST be performed in advance by a call to the following method: -``` -function registerRequestType(string typeName, string typeSuffix) -``` -`typeName` - a name of a type being registered\ -`typeSuffix` - an ERC-712 compatible description of a type - -For example, after calling -``` -registerRequestType("ExtendedRequest", "uint256 x,bytes z,ExtraData extraData)ExtraData(uint256 a,uint256 b,uint256 c)") -``` -the following ERC-712 type will be registered with forwarder: -``` -/* primary type */ -struct ExtendedRequest { - address from; - address to; - uint256 value; - uint256 gas; - uint256 nonce; - bytes data; - uint256 validUntil; - uint256 x; - bytes z; - ExtraData extraData; -} - -/* subtype */ -struct ExtraData { - uint256 a; - uint256 b; - uint256 c; -} -``` - -### Signature verification - -The following method performs an ERC-712 signature check on a request: -``` -function verify( - ForwardRequest forwardRequest, - bytes32 domainSeparator, - bytes32 requestTypeHash, - bytes suffixData, - bytes signature -) view; -``` -`forwardRequest` - an instance of the `ForwardRequest` struct -`domainSeparator` - caller-provided domain separator to prevent signature reuse across dapps (refer to ERC-712) -`requestTypeHash` - hash of the registered relay request type -`suffixData` - RLP-encoding of the remainder of the request struct -`signature` - an ERC-712 signature on the concatenation of `forwardRequest` and `suffixData` - -### Command execution - -In order for the Forwarder to perform an operation, the following method is to be called: -``` -function execute( - ForwardRequest forwardRequest, - bytes32 domainSeparator, - bytes32 requestTypeHash, - bytes suffixData, - bytes signature -) -public -payable -returns ( - bool success, - bytes memory ret -) -``` - -Performs the ‘verify’ internally and if it succeeds performs the following call: -``` -bytes memory data = abi.encodePacked(forwardRequest.data, forwardRequest.from); -... -(success, ret) = forwardRequest.to.call{gas: forwardRequest.gas, value: forwardRequest.value}(data); -``` -Regardless of whether the inner call succeeds or reverts, the nonce is incremented, invalidating the signature and preventing a replay of the request. - -Note that `gas` parameter behaves according to EVM rules, specifically EIP-150. The forwarder validates internally that -there is enough gas for the inner call. In case the `forwardRequest` specifies non-zero value, extra `40000 gas` is -reserved in case inner call reverts or there is a remaining Ether so there is a need to transfer value from the `Forwarder`: -```solidity -uint gasForTransfer = 0; -if ( req.value != 0 ) { - gasForTransfer = 40000; // buffer in case we need to move Ether after the transaction. -} -... -require(gasleft()*63/64 >= req.gas + gasForTransfer, "FWD: insufficient gas"); -``` -In case there is not enough `value` in the Forwarder the execution of the inner call fails.\ -Be aware that if the inner call ends up transferring Ether to the `Forwarder` in a call that did not originally have `value`, this -Ether will remain inside `Forwarder` after the transaction is complete. - -### ERC-712 and 'suffixData' parameter -`suffixData` field must provide a valid 'tail' of an ERC-712 typed data. -For instance, in order to sign on the `ExtendedRequest` struct, the data will be a concatenation of the following chunks: -* `forwardRequest` fields will be RLP-encoded as-is, and variable-length `data` field will be hashed -* `uint256 x` will be appended entirely as-is -* `bytes z` will be hashed first -* `ExtraData extraData` will be hashed as a typed data - -So a valid `suffixData` is calculated as following: -``` -function calculateSuffixData(ExtendedRequest request) internal pure returns (bytes) { - return abi.encode(request.x, keccak256(request.z), hashExtraData(request.extraData)); -} - -function hashExtraData(ExtraData extraData) internal pure returns (bytes32) { - return keccak256(abi.encode( - keccak256("ExtraData(uint256 a,uint256 b,uint256 c)"), - extraData.a, - extraData.b, - extraData.c - )); -} -``` - -### Accepting Forwarded calls -In order to support calls performed via the Forwarder, the Recipient contract must read the signer address from the -last 20 bytes of `msg.data`, as described in ERC-2771. - -## Rationale -Further relying on `msg.sender` to authenticate end users by their externally-owned accounts is taking the Ethereum dapp ecosystem to a dead end. - -A need for users to own Ether before they can interact with any contract has made a huge portion of use-cases for smart contracts non-viable, -which in turn limits the mass adoption and enforces this vicious cycle. - -`validUntil` field uses a block number instead of timestamp in order to allow for better precision and integration -with other common block-based timers. - -## Security Considerations -All contracts introducing support for the Forwarded requests thereby authorize this contract to perform any operation under any account. -It is critical that this contract has no vulnerabilities or centralization issues. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2770.md diff --git a/EIPS/eip-2771.md b/EIPS/eip-2771.md index 8fadf2340bfc89..fb134491e5943d 100644 --- a/EIPS/eip-2771.md +++ b/EIPS/eip-2771.md @@ -1,140 +1 @@ ---- -eip: 2771 -title: Secure Protocol for Native Meta Transactions -description: A contract interface for receiving meta transactions through a trusted forwarder -author: Ronan Sandford (@wighawag), Liraz Siri (@lirazsiri), Dror Tirosh (@drortirosh), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Hadrien Croubois (@Amxx), Sachin Tomar (@tomarsachin2271), Patrick McCorry (@stonecoldpat), Nicolas Venturo (@nventuro), Fabian Vogelsteller (@frozeman), Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/erc-2771-secure-protocol-for-native-meta-transactions/4488 -status: Final -type: Standards Track -category: ERC -created: 2020-07-01 ---- - -## Abstract - -This EIP defines a contract-level protocol for `Recipient` contracts to accept meta-transactions through trusted `Forwarder` contracts. No protocol changes are made. `Recipient` contracts are sent the effective `msg.sender` (referred to as `_msgSender()`) and `msg.data` (referred to as `_msgData()`) by appending additional calldata. - -## Motivation - -There is a growing interest in making it possible for Ethereum contracts to accept calls from externally owned accounts that do not have ETH to pay for gas. Solutions that allow for third parties to pay for gas costs are called meta transactions. For the purposes of this EIP, meta transactions are transactions that have been authorized by a **Transaction Signer** and relayed by an untrusted third party that pays for the gas (the **Gas Relay**). - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Definitions - -**Transaction Signer**: Signs & sends transactions to a Gas Relay - -**Gas Relay**: Receives signed requests off-chain from Transaction Signers and pays gas to turn it into a valid transaction that goes through a Trusted Forwarder - -**Trusted Forwarder**: A contract trusted by the `Recipient` to correctly verify signatures and nonces before forwarding the request from Transaction Signers - -**Recipient**: A contract that accepts meta-transactions through a Trusted Forwarder - -### Example Flow - -![Example flow](../assets/eip-2771/example-flow.png) - -### Extracting The Transaction Signer address - -The **Trusted Forwarder** is responsible for calling the **Recipient** contract and MUST append the address of the **Transaction Signer** (20 bytes of data) to the end of the call data. - -For example : - -```solidity -(bool success, bytes memory returnData) = to.call.value(value)(abi.encodePacked(data, from)); -``` - -The **Recipient** contract can then extract the **Transaction Signer** address by performing 3 operations: - -1. Check that the **Forwarder** is trusted. How this is implemented is out of the scope of this proposal. -2. Extract the **Transaction Signer** address from the last 20 bytes of the call data and use that as the original `sender` of the transaction (instead of `msg.sender`) -3. If the `msg.sender` is not a trusted forwarder (or if the `msg.data` is shorter than 20 bytes), then return the original `msg.sender` as it is. - -The **Recipient** MUST check that it trusts the Forwarder to prevent it from -extracting address data appended from an untrusted contract. This could result -in a forged address. - -### Protocol Support Discovery Mechanism - -Unless a **Recipient** contract is being used by a particular frontend that knows that this contract has support for native meta transactions, it would not be possible to offer the user the choice of using meta-transaction to interact with the contract. We thus need a mechanism by which the **Recipient** can let the world know that it supports meta transactions. - -This is especially important for meta transactions to be supported at the Web3 wallet level. Such wallets may not necessarily know anything about the **Recipient** contract users may wish to interact with. - -As a **Recipient** could trust forwarders with different interfaces and capabilities (e.g., transaction batching, different message signing formats), we need to allow wallets to discover which Forwarder is trusted. - -To provide this discovery mechanism a **Recipient** contract MUST implement this function: - -```solidity -function isTrustedForwarder(address forwarder) external view returns(bool); -``` - -`isTrustedForwarder` MUST return `true` if the forwarder is trusted by the Recipient, otherwise it MUST return `false`. `isTrustedForwarder` MUST NOT revert. - -Internally, the **Recipient** MUST then accept a request from forwarder. - -`isTrustedForwarder` function MAY be called on-chain, and as such gas restrictions MUST be put in place. It SHOULD NOT consume more than 50,000 gas - -## Rationale - -* Make it easy for contract developers to add support for meta - transactions by standardizing the simplest viable contract interface. -* Without support for meta transactions in the recipient contract, an externally owned - account can not use meta transactions to interact with the recipient contract. -* Without a standard contract interface, there is no standard way for a client - to discover whether a recipient supports meta transactions. -* Without a standard contract interface, there is no standard way to send a - meta transaction to a recipient. -* Without the ability to leverage a trusted forwarder every recipient contract - has to internally implement the logic required to accept meta transactions securely. -* Without a discovery protocol, there is no mechanism for a client to discover - whether a recipient supports a specific forwarder. -* Making the contract interface agnostic to the internal implementation - details of the trusted forwarder, makes it possible for a recipient contract - to support multiple forwarders with no change to code. -* `msg.sender` is a transaction parameter that can be inspected by a contract to determine who signed the transaction. The integrity of this parameter is guaranteed by the Ethereum EVM, but for a meta transaction securing `msg.sender` is insufficient. - * The problem is that for a contract that is not natively aware of meta transactions, the `msg.sender` of the transaction will make it appear to be coming from the **Gas Relay** and not the **Transaction Signer**. A secure protocol for a contract to accept meta transactions needs to prevent the **Gas Relay** from forging, modifying or duplicating requests by the **Transaction Signer**. - -## Reference Implementation - -### Recipient Example - -```solidity -contract RecipientExample { - - function purchaseItem(uint256 itemId) external { - address sender = _msgSender(); - // ... perform the purchase for sender - } - - address immutable _trustedForwarder; - constructor(address trustedForwarder) internal { - _trustedForwarder = trustedForwarder; - } - - function isTrustedForwarder(address forwarder) public returns(bool) { - return forwarder == _trustedForwarder; - } - - function _msgSender() internal view returns (address payable signer) { - signer = msg.sender; - if (msg.data.length>=20 && isTrustedForwarder(signer)) { - assembly { - signer := shr(96,calldataload(sub(calldatasize(),20))) - } - } - } - -} -``` - -## Security Considerations - -A malicious forwarder may forge the value of `_msgSender()` and effectively send transactions from any address. Therefore, `Recipient` contracts must be very careful in trusting forwarders. If a forwarder is upgradeable, then one must also trust that the contract won't perform a malicious upgrade. - -In addition, modifying which forwarders are trusted must be restricted, since an attacker could "trust" their own address to forward transactions, and therefore be able to forge transactions. It is recommended to have the list of trusted forwarders be immutable, and if this is not feasible, then only trusted contract owners should be able to modify it. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2771.md diff --git a/EIPS/eip-2848.md b/EIPS/eip-2848.md index 5212f677e59255..5898b8a8ba56a8 100644 --- a/EIPS/eip-2848.md +++ b/EIPS/eip-2848.md @@ -1,202 +1 @@ ---- -eip: 2848 -title: My Own Messages (MOM) -author: Giuseppe Bertone (@Neurone) -discussions-to: https://github.com/InternetOfPeers/EIPs/issues/1 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-08-02 ---- - -## Simple Summary - -My Own Messages (MOM) is a standard to create your very own public, always updated, unstoppable, verifiable, message board. - -## Abstract - -My Own Messages (MOM) use Ethereum as a certification layer for commands and multihash of your messages. It don't use smart contracts but simple self-send transactions with specific payload attached. - -To ge more insights, you can test a [live client](http://internetofpeers.org/mom-client/), watch a [full video overview and demo](https://www.youtube.com/watch?v=z1SnoQkQYkU) and read a [brief presentation](../assets/eip-2848/presentation.pdf). - -## Motivation - -As a _developer_ or _pool's owner_, I'd like to send messages to my users in a decentralized way. They must be able to easily verify my role in the smart contract context (owner, user, and so on) and they must be able to do it without relying on external, insecure and hackable social media sites (Facebook, Twitter, you name it). Also, I'd like to read messages from my userbase, in the same secure and verifiable manner. - -As a _user_, I want a method to easily share my thoughts and idea, publish content, send messages, receive feedback, receive tips, and so on, without dealing with any complexity: just write a message, send it and it's done. Also, I want to write to some smart contract's owner or to the sender of some transaction. - -As an _explorer service_, I want to give my users an effective way to read information by smart contract owners and a place to share ideas and information without using third party services (i.e. Etherscan uses Disqus, and so on) - -And in _any role_, I want a method that does not allow scams - transactions without values, no smart contract's address to remember or to fake - and it does not allow spam - it's cheap but not free, and even if you can link/refer other accounts, you cannot send them messages directly, and others must explicitly follow and listen to your transactions if they want to read your messages. - -Main advantages: - -- You can send messages to users of your ÐApp or Smart Contract, and they always know it is a voice reliable as the smart contract is. -- Create your Ethereum account dedicated to your personal messages, say something only once and it can be seen on every social platform (no more reply of the same post/opinion on dozens of sites like Reddit, Twitter, Facebook, Medium, Disqus, and so on...) -- Small fee to be free: pay just few cents of dollar to notarize your messages, and distribute them with IPFS, Swarm or any other storage you prefer. Because the multihash of the content is notarized, you can always check the integrity of the message you download even from centralized storage services. -- Finally, you can ask and get tips for your words directly into your wallet. - -I know, My Own Messages (MOM) sounds like _mom_. And yes, pun intended :) - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt) when, and only when, they appear in all capitals as shown here. - -Clients following MOM standard **MUST** allow users to send and to read MOM transaction, creating an _updated message list_ for each address the users are interested in. - -Reading MOM transactions, MOM clients **MUST** be able to show the current and updated message list, and they **SHOULD** be able to show also all the message history if users ask for it. - -Apart from message list, MOM clients **SHOULD** be able to download the content of the messages and to show them to the user. - -Clients **SHOULD** allow users to choose and set the source to download content from, and they **SHOULD** be able to use common Content Addressable Networks - i.e. IPFS or Swarm - or HTTP servers. If content is downloaded from HTTP servers, clients **MUST** check the content against the declared multihash. - -As the default setting, clients **MUST** consider `text/markdown` ([RFC 7763](https://www.ietf.org/rfc/rfc7763.txt)) as the media type of the content represented by a multihash, and in particular [Markdown](https://en.wikipedia.org/wiki/Markdown) text in [UTF-8](https://en.wikipedia.org/wiki/UTF-8) without [BOM](https://en.wikipedia.org/wiki/Byte_order_mark). - -Clients **MAY** let users choose to parse messages considering other content types. In this case they **SHOULD** cast a warning to users stating that a content type other than `text/markdown` is used while processing messages. - -It's **RECOMMENDED** that clients inform users about the actual setting of the default content type. - -### MOM transactions - -Clients **MUST** assume that **invalid MOM transactions don't exist**. If a transaction does not strictly follow the MOM standard, clients **MUST** ignore it and they **MUST NOT** consider it a MOM transaction at all. - -Because there can be security implications parsing data sent by users, clients **SHOULD NOT** try to keep track or interpret transactions as _invalid_ MOM transactions. - -#### Valid MOM transaction's data structure - -| ATTRIBUTE | VALUE | -|:--------|:------------| -| `to` | **MUST** be the same account signing the transaction. | -| `value` | **MUST** be `0` wei. | -| `data` | **MUST** be at least `2` bytes. The first byte **MUST** be operational code and following bytes **MUST** be based on the operational codes listed below. | - -#### List of supported operations and messages - -Each operational code has one or more parameters, and all parameters **MUST** be considered mandatory. - -Optional parameters don't exist: if parameters for the specific operational code are not all present or they don't follow the rules, clients **MUST** ignore the transaction completely. - -Messages **MUST** be always referenced with the multihash of their content. - -Operations are divided into two sets: **CORE** and **EXTENDED** operations. - -- Clients **MUST** support all core operations and they **SHOULD** support as much extended operations as possible. -- Clients **SHOULD** support and implement as much extended operations as possible, but they **MAY** choose to implement only some specific extended operations they are interested in. - -#### Core operations - -| OPERATION | CODE | PARAMETERS | MEANING | EFFECT | -|-----------|:--------:|------------|---------|--------| -| ADD | `0x00` | multihash | Add a message. The parameter **MUST** be the multihash of the message. | Clients **MUST** add the message to the message list of the sender. | -| UPDATE | `0x01` | multihash, multihash | Update a message. The first parameter **MUST** be the multihash of the message to be updated. The second parameter **MUST** be the multihash of the updated message. | Clients **MUST** update the message list to show the updated message. | -| REPLY | `0x02` | multihash, multihash | Reply to a message. The first parameter **MUST** be the multihash of the message to reply to. The second parameter **MUST** the multihash of the message. | Clients **MUST** insert a new message in the message list and they **MUST** preserve the relationship with the referenced message. | -| DELETE | `0x03` | multihash | Delete a message. The parameter **MUST** be the multihash of the message to delete. | Clients **MUST** remove the message from the message list. | -| CLOSE ACCOUNT | `0xFD` | multihash | Close an account. The parameter **MUST** be the multihash of the message with the motivations for closing the account. | Clients **MUST** add the message with motivations to the message list and they **MUST NOT** consider MOM messages sent by that address to be valid anymore, ever. In other words, MOM clients **MUST** ignore any other transaction sent by that address while creating the message list. This is useful when users want to change account, for example because the private key seems compromised. | -| RAW | `0xFF` | any | The parameter **MUST** be at least `1` byte. Content type is not disclosed and it **MUST NOT** be considered as `text/markdown`. | Clients **MUST** add the message to the message list but they **MUST NOT** try to decode the content. Clients **SHOULD** allow users to see this message only if explicitly asked for. This operation can be used for _blind_ notarization that general client can ignore. | - -#### Note about `DELETE` operational code - -Please note that sending a `DELETE` command users are not asking to actually delete anything from the blockchain, they are just asking clients to hide that specific message because it's not valid anymore for some reasons. You can think of it like if users say: _I changed my mind so please ÐApps don't show this anymore_. As already stated in the specifications above, clients **MUST** follow this request by the author, unless expressly asked otherwise by the user. - -Please also note that, because it's usually up to the author of a message to be sure the content is available to everyone, if a `DELETE` message was sent it's very likely the content referenced by the multihash isn't available anymore, simply because probably it's not shared by anyone. - -#### Extended operations - -| OPERATION | CODE | PARAMETERS | MEANING | EFFECT | -|-----------|:--------:|------------|---------|--------| -| ADD & REFER | `0x04` | multihash, address | Add a message and refer an account. The first parameter **MUST** be the multihash of the message. The second parameter **MUST** be an address referenced by the message. | Clients **MUST** add the message to the message list and they **MUST** track the reference to the specified account. This can be useful _to invite_ the owner of the referenced account to read this specific message. | -| UPDATE & REFER | `0x05` | multihash, multihash, address | Update a message. The first parameter **MUST** be the multihash of the message to be updated. The second parameter **MUST** be the multihash of the updated message. The third parameter **MUST** be an address referenced by the message.| Clients **MUST** update the message list to show the updated message and they **MUST** track the reference to the specified account. This can be useful _to invite_ the owner of the referenced account to read this specific message. | -| ENDORSE | `0x06` | multihash | Endorse a message identified by the specified multihash. The parameter **MUST** be the multihash of the message to be endorsed. | Clients **MUST** record and track the endorsement for that specific message. Think it as a _like_, a _retwitt_, etc. | -| REMOVE ENDORSEMENT | `0x07` | multihash | Remove endorsement to the message identified by the specified multihash. The parameter **MUST** be the multihash of the message. | Clients **MUST** remove the endorsement for that specific message. | -| DISAPPROVE | `0x08` | multihash | Disapprove a message identified by the specified multihash. The parameter **MUST** be the multihash of the message to disapprove. | Clients **MUST** record and track the disapproval for that specific message. Think it as a _I don't like it_. | -| REMOVE DISAPPROVAL | `0x09` | multihash | Remove disapproval of a message identified by the specified multihash. The parameter **MUST** be the multihash of the message. | Clients **MUST** remove the disapproval for that specific message. | -| ENDORSE & REPLY | `0x0A` | multihash, multihash | Endorse a message and reply to it. The first parameter **MUST** be the multihash of the message to reply to. The second parameter **MUST** be the multihash of the message. | Clients **MUST** insert a new message in the message list and they **MUST** preserve the relationship with the referenced message. Clients **MUST** also record and track the endorsement for that specific message. | -| DISAPPROVE & REPLY | `0x0B` | multihash, multihash | Disapprove a message and reply to it. The first parameter **MUST** be the multihash of the message to reply to. The second parameter **MUST** be the multihash of the message. | Clients **MUST** insert a new message in the message list and they **MUST** preserve the relationship with the referenced message. Clients **MUST** also record and track the disapproval for that specific message. | - -## Rationale - -Ethereum is _account based_, so it's good to be identified as a single source of information. - -It is also able of doing notarization very well and to impose some restrictions on transaction's structure, so it's good for commands. - -IPFS, Swarm or other CANs (Content Addressable Networks) or storage methods are good to store a lot of information. So, the union of both worlds it's a good solution to achieve the objectives of this message standard. - -The objective is also to avoid in the first place any kind of scam and malicious behaviors, so MOM don't allow to send transactions to other accounts and the value of a MOM transaction is always 0. - -### Why not using a smart contract? - -MOM wants to be useful, easy to implement and read, error proof, fast and cheap, but: - -- using a smart contract for messages can leads more easily to errors and misunderstandings: - - address of the contract can be wrong - - smart contract must be deployed on that specific network to send messages -- executing a smart contract costs much more than sending transactions -- executing a smart contract just to store static data is the best example of an anti-pattern (expensive and almost useless) - -Without a specific smart contract to rely on, the MOM standard can be implemented and used right now in any existing networks, and even in future ones. - -Finally, if you can achieve exactly the same result without a smart contract, you didn't need a smart contract at the first place. - -### Why not storing messages directly on-chain? - -There's no benefit to store _static_ messages on-chain, if they are not related to some smart contract's state or if they don't represent exchange of value. The cost of storing data on-chain is also very high. - -### Why not storing op codes inside the message? - -While cost effectiveness is a very important feature in a blockchain related standard, there's also a compromise to reach with usability and usefulness. - -Storing commands inside the messages forces the client to actually download messages to understand what to do with them. This is very inefficient, bandwidth and time consuming. - -Being able to see the commands before downloading the content, it allows the client to recreate the history of all messages and then, at the end, download only updated messages. - -Creating a structure for the content of the messages leads to many issues and considerations in parsing the content, if it's correct, misspelled, and so on. - -Finally, the **content must remain clean**. You really want to notarize the content and not to refer to a data structure, because this can lead to possible false-negative when checking if a content is the same of another. - -### Why multihash? - -[Multihash](https://github.com/multiformats/multihash) is flexible, future-proof and there are already tons of library supporting it. Ethereum must be easily integrable with many different platforms and architectures, so MOM standard follows that idea. - -## Backwards Compatibility - -You can already find few transactions over the Ethereum network that use a pattern similar to this EIP. Sometimes it's done to invalidate a previous transaction in memory pool, using the same nonce but with more gas price, so that transaction is mined cancelling the previous one still in the memory pool. This kind of transactions can be easily ignored if created before the approval of this EIP or just checking if the payload follows the correct syntax. - -## Test Cases - -A MOM-compliant client can be found and tested on [GitHub](https://github.com/InternetOfPeers/mom-client). - -You can use the latest version of MOM client directly via [GitHub Pages](https://internetofpeers.github.io/mom-client) or via IPFS (see the [client repo](https://github.com/InternetOfPeers/mom-client) for the latest updated address). - -## Implementation - -You can use an already working MOM JavaScript package on [GitHub Packages](https://github.com/InternetOfPeers/mom-js/packages/323930) or [npmjs](https://www.npmjs.com/package/@internetofpeers/mom-js). The package is already used by the MOM client above, and you can use it in your ÐApps too with: - -```bash -npm install @internetofpeers/mom-js -``` - -Transaction [`0x8e49485c56897757a6f2707b92cd5dad06126afed92261b9fe1a19b110bc34e6`](https://etherscan.io/tx/0x8e49485c56897757a6f2707b92cd5dad06126afed92261b9fe1a19b110bc34e6) is an example of a valid MOM transaction already mined on the Main net; it's an `ADD` message. - -## Security Considerations - -MOM is very simple and it has no real security concerns by itself. The standard already considers valid only transactions with `0` value and where `from` and `to` addresses are equals. - -The only concerns can come from the payload, but it is more related to the client and not to the standard itself, so here you can find some security suggestions related to clients implementing the standard. - -### Parsing commands - -MOM standard involves parsing payloads generated by potentially malicious clients, so attention must be made to avoid unwanted code execution. - -- Strictly follow only the standard codes -- Don't execute any commands outside of the standard ones, unless expressly acknowledged by the user -- Ignore malformed transactions (transactions that don't strictly follow the rules) - -### Messages - -Default content-type of a message following the MOM standard is Markdown text in UTF8 without BOM. It is highly recommended to disallow the reading of any not-text content-type, unless expressly acknowledged by the user. - -Because content multihash is always stored into the chain, clients can download that content from Content Addressable Network (like IPFS or Swarm) or from central servers. In the latter case, a client should always check the integrity of the received messages, or it must warn the user if it cannot do that (feature not implemented or in error). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2848.md diff --git a/EIPS/eip-2876.md b/EIPS/eip-2876.md index e2b55e433260fb..4b69200f54a9d4 100644 --- a/EIPS/eip-2876.md +++ b/EIPS/eip-2876.md @@ -1,184 +1 @@ ---- -eip: 2876 -title: Deposit contract and address standard -author: Jonathan Underwood (@junderw) -discussions-to: https://github.com/junderw/deposit-contract-poc/issues/1 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-08-13 ---- - -## Simple Summary -This ERC defines a simple contract interface for managing deposits. It also defines a new address format that encodes the extra data passed into the interface's main deposit function. - -## Abstract -An ERC-2876 compatible **deposit system** can accept ETH payments from multiple depositors without the need for managing multiple keys or requiring use of a hot wallet. - -An ERC-2876 compatible **wallet application** can send ETH to ERC-2876 compatible **deposit systems** in a way that the **deposit system** can differentiate their payment using the 8 byte id specified in this standard. - -Adoption of ERC-2876 by all exchanges (as a deposit system and as a wallet for their withdrawal systems), merchants, and all wallet applications/libraries will likely decrease total network gas usage by these systems, since two value transactions cost 42000 gas while a simple ETH forwarding contract will cost closer to 30000 gas depending on the underlying implementation. - -This also has the benefit for deposit system administrators of allowing for all deposits to be forwarded to a cold wallet directly without any manual operations to gather deposits from multiple external accounts. - -## Motivation -Centralized exchanges and merchants (Below: "apps") require an address format for accepting deposits. Currently the address format used refers to an account (external or contract), but this creates a problem. It requires that apps create a new account for every invoice / user. If the account is external, that means the app must have the deposit addresses be hot wallets, or have increased workload for cold wallet operators (as each deposit account will create 1 value tx to sweep). If the account is contract, generating an account costs at least 60k gas for a simple proxy, which is cost-prohibitive. - -Therefore, merchant and centralized exchange apps are forced between taking on one of the following: - -- Large security risk (deposit accounts are hot wallets) -- Large manual labor cost (cold account manager spends time sweeping thousands of cold accounts) -- Large service cost (deploying a contract-per-deposit-address model). - -The timing of this proposal is within the context of increased network gas prices. During times like this, more and more services who enter the space are being forced into hot wallets for deposits, which is a large security risk. - -The motivation for this proposal is to lower the cost of deploying and managing a system that accepts deposits from many users, and by standardizing the methodology for this, services across the world can easily use this interface to send value to and from each other without the need to create multiple accounts. - -## Specification - -### Definitions -- The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. -- `The contract interface` is the contract component of this ERC. -- `The deposit address format` is the newly made format described in "Deposit Address Format" for encoding the 20 byte account address and the 8 byte id. -- `The contract` refers to the contract that implements `the contract interface` of this ERC. -- `The 8 byte "id"` is an 8 byte id used as the input parameter for the contract interface. -- `The 5 byte "nonce"` is the first 5 most significant bytes of the `"id"`. -- `The 3 byte "checksum"` is the last 3 least significant bytes of the `"id"` -- `deposit(bytes8)` refers to the function of that signature, which is defined in `the contract interface`. -- `The parent application` refers to the application that will use the information gained within the `deposit(bytes8)` function. (ie. an exchange backend or a non-custodial merchant application) -- `The depositor` refers to the person that will send value to `the contract` via the `deposit(bytes8)` call. -- `The wallet` refers to any application or library that sends value transactions upon the request of `the depositor`. (ie. MyEtherWallet, Ledger, blockchain.com, various libraries) - -### Deposit Address Format - -In order to add the 8 byte "id" data, we need to encode it along with the 20 byte -account address. The 8 bytes are appended to the 20 byte address. - -A 3 byte checksum is included in the id, which is the first 3 bytes of the keccak256 -hash of the 20 byte address and first 5 byte nonce of the id concatenated (25 bytes). - -The Deposit Address format can be generated with the following JavaScript code: - -```js -/** - * Converts a 20 byte account address and a 5 byte nonce to a deposit address. - * The format of the return value is 28 bytes as follows. The + operator is byte - * concatenation. - * (baseAddress + nonce + keccak256(baseAddress + nonce)[:3]) - * - * @param {String} baseAddress the given HEX address (20 byte hex string with 0x prepended) - * @param {String} nonce the given HEX nonce (5 byte hex string with 0x prepended) - * @return {String} - */ -function generateAddress (baseAddress, nonce) { - if ( - !baseAddress.match(/^0x[0-9a-fA-F]{40}$/) || - !nonce.match(/^0x[0-9a-fA-F]{10}$/) - ) { - throw new Error('Base Address and nonce must be 0x hex strings'); - } - const ret = - baseAddress.toLowerCase() + nonce.toLowerCase().replace(/^0x/, ''); - const myHash = web3.utils.keccak256(ret); - return ret + myHash.slice(2, 8); // first 3 bytes from the 0x hex string -}; -``` - -The checksum can be verified within the deposit contract itself using the following: - -```solidity -function checksumMatch(bytes8 id) internal view returns (bool) { - bytes32 chkhash = keccak256( - abi.encodePacked(address(this), bytes5(id)) - ); - bytes3 chkh = bytes3(chkhash); - bytes3 chki = bytes3(bytes8(uint64(id) << 40)); - return chkh == chki; -} -``` - -### The Contract Interface - -A contract that follows this ERC: - -- `The contract` MUST revert if sent a transaction where `msg.data` is null (A pure value transaction). -- `The contract` MUST have a deposit function as follows: - -```solidity -interface DepositEIP { - function deposit(bytes8 id) external payable returns (bool); -} -``` - -- `deposit(bytes8)` MUST return `false` when the contract needs to keep the value, but signal to the depositor that the deposit (in terms of the parent application) itself has not yet succeeded. (This can be used for partial payment, ie. the invoice is for 5 ETH, sending 3 ETH returns false, but sending a second tx with 2 ETH will return true.) -- `deposit(bytes8)` MUST revert if the deposit somehow failed and the contract does not need to keep the value sent. -- `deposit(bytes8)` MUST return `true` if the value will be kept and the payment is logically considered complete by the parent application (exchange/merchant). -- `deposit(bytes8)` SHOULD check the checksum contained within the 8 byte id. (See "Deposit Address Format" for an example) -- `The parent application` SHOULD return any excess value received if the deposit id is a one-time-use invoice that has a set value and the value received is higher than the set value. However, this SHOULD NOT be done by sending back to `msg.sender` directly, but rather should be noted in the parent application and the depositor should be contacted out-of-band to the best of the application manager's ability. - -### Depositing Value to the Contract from a Wallet - -- `The wallet` MUST accept `the deposit address format` anywhere the 20-byte address format is accepted for transaction destination. -- `The wallet` MUST verify the 3 byte checksum and fail if the checksum doesn't match. -- `The wallet` MUST fail if the destination address is `the deposit address format` and the `data` field is set to anything besides null. -- `The wallet` MUST set the `to` field of the underlying transaction to the first 20 bytes of the deposit address format, and set the `data` field to `0x3ef8e69aNNNNNNNNNNNNNNNN000000000000000000000000000000000000000000000000` where `NNNNNNNNNNNNNNNN` is the last 8 bytes of the deposit address format. (ie. if the deposit address format is set to `0x433e064c42e87325fb6ffa9575a34862e0052f26913fd924f056cd15` then the `to` field is `0x433e064c42e87325fb6ffa9575a34862e0052f26` and the `data` field is `0x3ef8e69a913fd924f056cd15000000000000000000000000000000000000000000000000`) - -## Rationale -The contract interface and address format combination has one notable drawback, which was brought up in discussion. This ERC can only handle deposits for native value (ETH) and not other protocols such as ERC-20. However, this is not considered a problem, because it is best practice to logically AND key-wise separate wallets for separate currencies in any exchange/merchant application for accounting reasons and also for security reasons. Therefore, using this method for the native value currency (ETH) and another method for ERC-20 tokens etc. is acceptable. Any attempt at doing something similar for ERC-20 would require modifying the ERC itself (by adding the id data as a new input argument to the transfer method etc.) which would grow the scope of this ERC too large to manage. However, if this address format catches on, it would be trivial to add the bytes8 id to any updated protocols (though adoption might be tough due to network effects). - -The 8 byte size of the id and the checksum 3 : nonce 5 ratio were decided with the following considerations: - -- 24 bit checksum is better than the average 15 bit checksum of an EIP-55 address. -- 40 bit nonce allows for over 1 trillion nonces. -- 64 bit length of the id was chosen as to be long enough to support a decent checksum and plenty of nonces, but not be too long. (Staying under 256 bits makes hashing cheaper in gas costs as well.) - -## Backwards Compatibility -An address generated with the deposit address format will not be considered a valid address for applications that don't support it. If the user is technical enough, they can get around lack of support by verifying the checksum themselves, creating the needed data field by hand, and manually input the data field. (assuming the wallet app allows for arbitrary data input on transactions) A tool could be hosted on github for users to get the needed 20 byte address and msg.data field from a deposit address. - -Since a contract following this ERC will reject any plain value transactions, there is no risk of extracting the 20 byte address and sending to it without the calldata. - -However, this is a simple format, and easy to implement, so the author of this ERC will first implement in web3.js and encourage adoption with the major wallet applications. - -## Test Cases -``` -[ - { - "address": "0x083d6b05729c58289eb2d6d7c1bb1228d1e3f795", - "nonce": "0xbdd769c69b", - "depositAddress": "0x083d6b05729c58289eb2d6d7c1bb1228d1e3f795bdd769c69b3b97b9" - }, - { - "address": "0x433e064c42e87325fb6ffa9575a34862e0052f26", - "nonce": "0x913fd924f0", - "depositAddress": "0x433e064c42e87325fb6ffa9575a34862e0052f26913fd924f056cd15" - }, - { - "address": "0xbbc6597a834ef72570bfe5bb07030877c130e4be", - "nonce": "0x2c8f5b3348", - "depositAddress": "0xbbc6597a834ef72570bfe5bb07030877c130e4be2c8f5b3348023045" - }, - { - "address": "0x17627b07889cd22e9fae4c6abebb9a9ad0a904ee", - "nonce": "0xe619dbb618", - "depositAddress": "0x17627b07889cd22e9fae4c6abebb9a9ad0a904eee619dbb618732ef0" - }, - { - "address": "0x492cdf7701d3ebeaab63b4c7c0e66947c3d20247", - "nonce": "0x6808043984", - "depositAddress": "0x492cdf7701d3ebeaab63b4c7c0e66947c3d202476808043984183dbe" - } -] -``` - -## Implementation -A sample implementation with an example contract and address generation (in the tests) is located here: - -https://github.com/junderw/deposit-contract-poc - -## Security Considerations -In general, contracts that implement the contract interface should forward funds received to the deposit(bytes8) function to their cold wallet account. This address SHOULD be hard coded as a constant OR take advantage of the `immutable` keyword in solidity versions `>=0.6.5`. - -To prevent problems with deposits being sent after the parent application is shut down, a contract SHOULD have a kill switch that will revert all calls to deposit(bytes8) rather than using `selfdestruct(address)` (since users who deposit will still succeed, since an external account will receive value regardless of the calldata, and essentially the self-destructed contract would become a black hole for any new deposits) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2876.md diff --git a/EIPS/eip-2917.md b/EIPS/eip-2917.md index cc88c591047469..00d158830e052e 100644 --- a/EIPS/eip-2917.md +++ b/EIPS/eip-2917.md @@ -1,165 +1 @@ ---- -eip: 2917 -title: Staking Reward Calculation -author: Tony Carson , Mehmet Sabir Kiraz , Süleyman Kardaş -discussions-to: https://github.com/ethereum/EIPs/issues/2925 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-08-28 ---- - -## Simple Summary -ERC2917 is a new standardization for on-chain calculation of staking reward. - -## Abstract -Based on the product of effective collateral and time, ERC2917 calculates the reward a user can get at any time, and realize the real decentralized DeFi. Here below is the formula for the calculation of reward for a user U: - -![concept image](../assets/eip-2917/erc-reward-formula.png "erc-reward-formula") - -where ∆pi denotes individual productivity of the user U between the consecutive block numbers ti-1 and ti, ∆Pi denotes global productivity between the consecutive block numbers ti-1 and ti, and ∆Gi denotes gross product between the consecutive block numbers ti-1 and ti. The formula ensures that there is no benefit in case of exiting earlier or entering later in the computation. The reward a user can get for a period is based on his total productivity during that specific time. The formula has been simplified through Solidity and generalized design to make it available across all DeFi products. -We note that the smart contract can be triggered for every computation of on the following events: -- whenever the productivity of a user changes (increase/decrease), -- whenever a user withdraws. - -## Motivation - -One of the main drawbacks of many DeFi projects is the reward distribution mechanism within the smart contract. In fact, there are two main mechanisms are adopted so far. -1. Distribution of rewards is only given when all users exit the contract -2. The project collects on-chain data, conducts calculation off-chain, and sends the results -to the chain before starting rewards distribution accordingly - -The first approach conducts all calculation in an on-chain fashion, the cycle of its rewards distribution is too long. Furthermore, users need to remove their collateral before getting the rewards, which can be harmful for their rewards. The second approach is a semi-decentralized model since the main algorithm involves an off-chain computation. Therefore, the fairness and transparency properties cannot be reflected and this can even create the investment barrier for users. - -Since there is more DeFi projects coming out everyday, users could not find a proper way to get to know: -1) amount of interests he/she would get -2) how the interest calculated -3) what is his/her contribution compare to the overall - -By standardizing ERC2917, it abstracts the interface for interests generation process. Making wallet applications easier to collect each DeFi's metrics, user friendlier. - -## Specification - -Every ERC-2917 compliant contract must implement the ERC2917 and ERC20 interfaces (if necessary): - -```solidity -interface IERC2917 is IERC20 { - - /// @dev This emit when interests amount per block is changed by the owner of the contract. - /// It emits with the old interests amount and the new interests amount. - event InterestRatePerBlockChanged (uint oldValue, uint newValue); - - /// @dev This emit when a users' productivity has changed - /// It emits with the user's address and the the value after the change. - event ProductivityIncreased (address indexed user, uint value); - - /// @dev This emit when a users' productivity has changed - /// It emits with the user's address and the the value after the change. - event ProductivityDecreased (address indexed user, uint value); - - - /// @dev Return the current contract's interests rate per block. - /// @return The amount of interests currently producing per each block. - function interestsPerBlock() external view returns (uint); - - /// @notice Change the current contract's interests rate. - /// @dev Note the best practice will be restrict the gross product provider's contract address to call this. - /// @return The true/false to notice that the value has successfully changed or not, when it succeed, it will emite the InterestRatePerBlockChanged event. - function changeInterestRatePerBlock(uint value) external returns (bool); - - /// @notice It will get the productivity of given user. - /// @dev it will return 0 if user has no productivity proved in the contract. - /// @return user's productivity and overall productivity. - function getProductivity(address user) external view returns (uint, uint); - - /// @notice increase a user's productivity. - /// @dev Note the best practice will be restrict the callee to prove of productivity's contract address. - /// @return true to confirm that the productivity added success. - function increaseProductivity(address user, uint value) external returns (bool); - - /// @notice decrease a user's productivity. - /// @dev Note the best practice will be restrict the callee to prove of productivity's contract address. - /// @return true to confirm that the productivity removed success. - function decreaseProductivity(address user, uint value) external returns (bool); - - /// @notice take() will return the interests that callee will get at current block height. - /// @dev it will always calculated by block.number, so it will change when block height changes. - /// @return amount of the interests that user are able to mint() at current block height. - function take() external view returns (uint); - - /// @notice similar to take(), but with the block height joined to calculate return. - /// @dev for instance, it returns (_amount, _block), which means at block height _block, the callee has accumulated _amount of interests. - /// @return amount of interests and the block height. - function takeWithBlock() external view returns (uint, uint); - - /// @notice mint the available interests to callee. - /// @dev once it mint, the amount of interests will transfer to callee's address. - /// @return the amount of interests minted. - function mint() external returns (uint); -} -``` - -### InterestRatePerBlockChanged - -This emit when interests amount per block is changed by the owner of the contract. It emits with the old interests amount and the new interests amount. - - -### ProductivityIncreased - -It emits with the user's address and the the value after the change. - - -### ProductivityDecreased - -It emits with the user's address and the the value after the change. - -### interestsPerBlock - -It returns the amount of interests currently producing per each block. - -### changeInterestRatePerBlock - -Note the best practice will be restrict the gross product provider's contract address to call this. - -The true/false to notice that the value has successfully changed or not, when it succeed, it will emite the InterestRatePerBlockChanged event. - -### getProductivity - -It returns user's productivity and overall productivity. It returns 0 if user has no productivity proved in the contract. - -### increaseProductivity - -It increases a user's productivity. - -### decreaseProductivity - -It decreases a user's productivity. - -### take - -It returns the interests that callee will get at current block height. - -### takeWithBlock - -Similar to take(), but with the block height joined to calculate return. - -For instance, it returns (_amount, _block), which means at block height _block, the callee has accumulated _amount of interests. - -It returns amount of interests and the block height. - -### mint -it mints the amount of interests will transfer to callee's address. It returns the amount of interests minted. - -## Rationale -TBD - -## Implementation -The implementation code is on the github: - -- [ERC2917 Demo](https://github.com/gnufoo/ERC3000-Proposal) - -## Security Considerations -TBD - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2917.md diff --git a/EIPS/eip-2942.md b/EIPS/eip-2942.md index ea2ec7e3fdb579..6ab5ced66034b5 100644 --- a/EIPS/eip-2942.md +++ b/EIPS/eip-2942.md @@ -1,66 +1 @@ ---- -eip: 2942 -title: EthPM URI Specification -author: Nick Gheorghita (@njgheorghita), Piper Merriam (@pipermerriam), g. nicholas d'andrea (@gnidan), Benjamin Hauser (@iamdefinitelyahuman) -discussions-to: https://ethereum-magicians.org/t/ethpm-v3-specification-working-group/4086/7 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-09-04 -requires: 2678 ---- - -## Simple Summary -A custom URI scheme to identify an EthPM registry, package, release, or specific contract asset within a release. - -## Abstract -When interacting with the EthPM ecosystem, users and tooling can benefit from a URI scheme to identify EthPM assets. Being able to specify a package, registry, or release with a single string makes simplifies the steps required to install, publish, or distribute EthPM packages. - -## Specification -`scheme://registry_address[:chain_id][/package_name[@package_version[/json_pointer]]]` - -#### `scheme` -- Required -- Must be one of `ethpm` or `erc1319`. If future versions of the EthPM registry standard are designed and published via the ERC process, those ERCs will also be valid schemes. - -#### `registry_address` -- Required -- This **SHOULD** be either an ENS name or a 0x-prefixed, checksummed address. ENS names are more suitable for cases where mutability of the underlying asset is acceptable and there is implicit trust in the owner of the name. 0x prefixed addresses are more preferable in higher security cases to avoid needing to trust the controller of the name. - -#### `chain_id` -- Optional -- Integer representing the chain id on which the registry is located -- If omitted, defaults to `1` (mainnet). - -#### `package_name` -- Optional -- String of the target package name - -#### `package_version` -- Optional -- String of the target package version -- If the package version contains any [url unsafe characters](https://en.wikipedia.org/wiki/Percent-encoding), they **MUST** be safely escaped -- Since semver is not strictly enforced by the ethpm spec, if the `package_version` is omitted from a uri, tooling **SHOULD** avoid guessing in the face of any ambiguity and present the user with a choice from the available versions. - -#### `json_pointer` -- Optional -- A path that identifies a specific asset within a versioned package release. -- This path **MUST** conform to the [JSON pointer](https://tools.ietf.org/html/rfc6901) spec and resolve to an available asset within the package. - -## Rationale -Most interactions within the EthPM ecosystem benefit from a single-string representation of EthPM assets; from installing a package, to identifying a registry, to distributing a package. A single string that can faithfully represent any kind of EthPM asset, across the mainnet or testnets, reduces the mental overload for new users, minimizes configuration requirements for frameworks, and simplifies distribution of packages for package authors. - -## Test Cases -A JSON file for testing various URIs can be found in the [`ethpm-spec`](https://github.com/ethpm/ethpm-spec/) repository fixtures. - -## Implementation -The EthPM URI scheme has been implemented in the following libraries: -- [Brownie](https://eth-brownie.readthedocs.io/en/stable/) -- [Truffle](https://www.trufflesuite.com/docs/truffle/overview) -- [EthPM CLI](https://ethpm-cli.readthedocs.io/en/latest/) - -## Security Considerations -In most cases, an EthPM URI points to an immutable asset, giving full security that the target asset has not been modified. However, in the case where an EthPM URI uses an ENS name as its registry address, it is possible that the ENS name has been redirected to a new registry, in which case the guarantee of immutability no longer exists. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2942.md diff --git a/EIPS/eip-2980.md b/EIPS/eip-2980.md index ec74e0e5fcf248..39fae204f6caff 100644 --- a/EIPS/eip-2980.md +++ b/EIPS/eip-2980.md @@ -1,197 +1 @@ ---- -eip: 2980 -title: Swiss Compliant Asset Token -description: An interface for asset tokens, compliant with Swiss Law and compatible with [ERC-20](./eip-20.md). -author: Gianluca Perletti (@Perlets9), Alan Scarpellini (@alanscarpellini), Roberto Gorini (@robertogorini), Manuel Olivi (@manvel79) -discussions-to: https://github.com/ethereum/EIPs/issues/2983 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-09-08 -requires: 20 ---- - -## Abstract - -This new standard is an [ERC-20](./eip-20.md) compatible token with restrictions that comply with the following Swiss laws: the [Stock Exchange Act](../assets/eip-2980/Swiss-Confederation-SESTA.pdf), the [Banking Act](../assets/eip-2980/Swiss-Confederation-BA.pdf), the [Financial Market Infrastructure Act](../assets/eip-2980/Swiss-Confederation-FMIA.pdf), the [Act on Collective Investment Schemes](../assets/eip-2980/Swiss-Confederation-CISA.pdf) and the [Anti-Money Laundering Act](../assets/eip-2980/Swiss-Confederation-AMLA.pdf). The [Financial Services Act](../assets/eip-2980/Swiss-Confederation-FINSA.pdf) and the [Financial Institutions Act](../assets/eip-2980/Swiss-Confederation-FINIA.pdf) must also be considered. The solution achieved meet also the European jurisdiction. - -This new standard meets the new era of asset tokens (known also as "security tokens"). These new methods manage securities ownership during issuance and trading. The issuer is the only role that can manage a white-listing and the only one that is allowed to execute “freeze” or “revoke” functions. - -## Motivation - -In its ICO guidance dated February 16, 2018, FINMA (Swiss Financial Market Supervisory Authority) defines asset tokens as tokens representing assets and/or relative rights ([FINMA ICO Guidelines](../assets/eip-2980/Finma-ICO-Guidelines.pdf)). It explicitly mentions that asset tokens are analogous to and can economically represent shares, bonds, or derivatives. The long list of relevant financial market laws mentioned above reveal that we need more methods than with Payment and Utility Token. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -The words "asset tokens" and "security tokens" can be considered synonymous. - -Every ERC-2980 compliant contract MUST implement the ERC-2980 interface. - -### ERC-2980 (Token Contract) - -``` solidity -interface ERC2980 extends ERC20 { - - /// @dev This emits when funds are reassigned - event FundsReassigned(address from, address to, uint256 amount); - - /// @dev This emits when funds are revoked - event FundsRevoked(address from, uint256 amount); - - /// @dev This emits when an address is frozen - event FundsFrozen(address target); - - /** - * @dev getter to determine if address is in frozenlist - */ - function frozenlist(address _operator) external view returns (bool); - - /** - * @dev getter to determine if address is in whitelist - */ - function whitelist(address _operator) external view returns (bool); - -} -``` - -The ERC-2980 extends [ERC-20](./eip-20.md). Due to the indivisible nature of asset tokens, the decimals number MUST be zero. - -### Whitelist and Frozenlist - -The accomplishment of the Swiss Law requirements is achieved by the use of two distinct lists of address: the Whitelist and the Frozenlist. -Addresses can be added to one or the other list at any time by operators with special privileges, called Issuers, and described below. -Although these lists may look similar, they differ for the following reasons: the Whitelist members are the only ones who can receive tokens from other addresses. There is no restriction on the possibility that these addresses can transfer the tokens already in their ownership. -This can occur when an address, present in the Whitelist, is removed from this list, without however being put in the Frozenlist and remaining in possession of its tokens. -On the other hand, the addresses assigned to the Frozenlist, as suggested by the name itself, have to be considered "frozen", so they cannot either receive tokens or send tokens to anyone. - -Below is an example interface for the implementation of a whitelist-compatible and a frozenlist-compratible contract. - -``` solidity -Interface Whitelistable { - - /** - * @dev add an address to the whitelist - * Throws unless `msg.sender` is an Issuer operator - * @param _operator address to add - * @return true if the address was added to the whitelist, false if the address was already in the whitelist - */ - function addAddressToWhitelist(address _operator) external returns (bool); - - /** - * @dev remove an address from the whitelist - * Throws unless `msg.sender` is an Issuer operator - * @param _operator address to remove - * @return true if the address was removed from the whitelist, false if the address wasn't in the whitelist in the first place - */ - function removeAddressFromWhitelist(address _operator) external returns (bool); - -} - -Interface Freezable { - - /** - * @dev add an address to the frozenlist - * Throws unless `msg.sender` is an Issuer operator - * @param _operator address to add - * @return true if the address was added to the frozenlist, false if the address was already in the frozenlist - */ - function addAddressToFrozenlist(address _operator) external returns (bool); - - /** - * @dev remove an address from the frozenlist - * Throws unless `msg.sender` is an Issuer operator - * @param _operator address to remove - * @return true if the address was removed from the frozenlist, false if the address wasn't in the frozenlist in the first place - */ - function removeAddressFromFrozenlist(address _operator) external returns (bool); - -} -``` - -### Issuers - -A key role is played by the Issuer. This figure has the permission to manage Whitelists and Frozenlists, to revoke tokens and reassign them and to transfer the role to another address. No restrictions on the possibility to have more than one Issuer per contract. Issuers are nominated by the Owner of the contract, who also is in charge of remove the role. The possibility of nominating the Owner itself as Issuer at the time of contract creation (or immediately after) is not excluded. - -Below is an example interface for the implementation of the Issuer functionalities. - -``` solidity -Interface Issuable { - - /** - * @dev getter to determine if address has issuer role - */ - function isIssuer(address _addr) external view returns (bool); - - /** - * @dev add a new issuer address - * Throws unless `msg.sender` is the contract owner - * @param _operator address - * @return true if the address was not an issuer, false if the address was already an issuer - */ - function addIssuer(address _operator) external returns (bool); - - /** - * @dev remove an address from issuers - * Throws unless `msg.sender` is the contract owner - * @param _operator address - * @return true if the address has been removed from issuers, false if the address wasn't in the issuer list in the first place - */ - function removeIssuer(address _operator) external returns (bool); - - /** - * @dev Allows the current issuer to transfer its role to a newIssuer - * Throws unless `msg.sender` is an Issuer operator - * @param _newIssuer The address to transfer the issuer role to - */ - function transferIssuer(address _newIssuer) external; - -} -``` - -### Revoke and Reassign - -Revoke and Reassign methods allow Issuers to move tokens from addresses, even if they are in the Frozenlist. The Revoke method transfers the entire balance of the target address to the Issuer who invoked the method. The Reassign method transfers the entire balance of the target address to another address. These rights for these operations MUST be allowed only to Issuers. - -Below is an example interface for the implementation of the Revoke and Reassign functionalities. - -``` solidity -Interface RevokableAndReassignable { - - /** - * @dev Allows the current Issuer to transfer token from an address to itself - * Throws unless `msg.sender` is an Issuer operator - * @param _from The address from which the tokens are withdrawn - */ - function revoke(address _from) external; - - /** - * @dev Allows the current Issuer to transfer token from an address to another - * Throws unless `msg.sender` is an Issuer operator - * @param _from The address from which the tokens are withdrawn - * @param _to The address who receives the tokens - */ - function reassign(address _from, address _to) external; - -} -``` - -## Rationale - -There are currently no token standards that expressly facilitate conformity to securities law and related regulations. EIP-1404 (Simple Restricted Token Standard) it’s not enough to address FINMA requirements around re-issuing securities to Investors. -In Swiss law, an issuer must eventually enforce the restrictions of their token transfer with a “freeze” function. The token must be “revocable”, and we need to apply a white-list method for AML/KYC checks. - -## Backwards Compatibility - -This EIP does not introduce backward incompatibilities and is backward compatible with the older ERC-20 token standard. -This standard allows the implementation of ERC-20 functions transfer, transferFrom, approve and allowance alongside to make a token fully compatible with ERC-20. -The token MAY implement decimals() for backward compatibility with ERC-20. If implemented, it MUST always return 0. - -## Security Considerations - -The security considerations mainly concern the role played by the Issuers. This figure, in fact, is not generally present in common ERC-20 tokens but has very powerful rights that allow him to move tokens without being in possession and freeze other addresses, preventing them from transferring tokens. It must be the responsibility of the owner to ensure that the addresses that receive this charge remain in possession of it only for the time for which they have been designated to do so, thus preventing any abuse. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2980.md diff --git a/EIPS/eip-2981.md b/EIPS/eip-2981.md index a24821bd229ed5..933fa27c719ae5 100644 --- a/EIPS/eip-2981.md +++ b/EIPS/eip-2981.md @@ -1,183 +1 @@ ---- -eip: 2981 -title: NFT Royalty Standard -author: Zach Burks (@vexycats), James Morgan (@jamesmorgan), Blaine Malone (@blmalone), James Seibel (@seibelj) -discussions-to: https://github.com/ethereum/EIPs/issues/2907 -status: Final -type: Standards Track -category: ERC -created: 2020-09-15 -requires: 165 ---- - -## Simple Summary - -A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal support for royalty payments across all NFT marketplaces and ecosystem participants. - -## Abstract - -This standard allows contracts, such as NFTs that support [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) interfaces, to signal a royalty amount to be paid to the NFT creator or rights holder every time the NFT is sold or re-sold. This is intended for NFT marketplaces that want to support the ongoing funding of artists and other NFT creators. The royalty payment must be voluntary, as transfer mechanisms such as `transferFrom()` include NFT transfers between wallets, and executing them does not always imply a sale occurred. Marketplaces and individuals implement this standard by retrieving the royalty payment information with `royaltyInfo()`, which specifies how much to pay to which address for a given sale price. The exact mechanism for paying and notifying the recipient will be defined in future EIPs. This ERC should be considered a minimal, gas-efficient building block for further innovation in NFT royalty payments. - -## Motivation -There are many marketplaces for NFTs with multiple unique royalty payment implementations that are not easily compatible or usable by other marketplaces. Just like the early days of ERC-20 tokens, NFT marketplace smart contracts are varied by ecosystem and not standardized. This EIP enables all marketplaces to retrieve royalty payment information for a given NFT. This enables accurate royalty payments regardless of which marketplace the NFT is sold or re-sold at. - -Many of the largest NFT marketplaces have implemented bespoke royalty payment solutions that are incompatible with other marketplaces. This standard implements standardized royalty information retrieval that can be accepted across any type of NFT marketplace. This minimalist proposal only provides a mechanism to fetch the royalty amount and recipient. The actual funds transfer is something which the marketplace should execute. - -This standard allows NFTs that support [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) interfaces, to have a standardized way of signalling royalty information. More specifically, these contracts can now calculate a royalty amount to provide to the rightful recipient. - -Royalty amounts are always a percentage of the sale price. If a marketplace chooses *not* to implement this EIP, then no funds will be paid for secondary sales. It is believed that the NFT marketplace ecosystem will voluntarily implement this royalty payment standard; in a bid to provide ongoing funding for artists/creators. NFT buyers will assess the royalty payment as a factor when making NFT purchasing decisions. - -Without an agreed royalty payment standard, the NFT ecosystem will lack an effective means to collect royalties across all marketplaces and artists and other creators will not receive ongoing funding. This will hamper the growth and adoption of NFTs and demotivate NFT creators from minting new and innovative tokens. - -Enabling all NFT marketplaces to unify on a single royalty payment standard will benefit the entire NFT ecosystem. - -While this standard focuses on NFTs and compatibility with the ERC-721 and ERC-1155 standards, EIP-2981 does not require compatibility with ERC-721 and ERC-1155 standards. Any other contract could integrate with EIP-2981 to return royalty payment information. ERC-2981 is, therefore, a universal royalty standard for many asset types. - -At a glance, here's an example conversation summarizing NFT royalty payments today: - ->**Artist**: "Do you support royalty payments on your platform?" ->**Marketplace**: "Yes we have royalty payments, but if your NFT is sold on another marketplace then we cannot enforce this payment." ->**Artist**: "What about other marketplaces that support royalties, don't you share my royalty information to make this work?" ->**Marketplace**: "No, we do not share royalty information." - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL -NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and -"OPTIONAL" in this document are to be interpreted as described in -RFC 2119. - -**ERC-721 and ERC-1155 compliant contracts MAY implement this ERC for royalties to provide a standard method of specifying royalty payment information.** - -Marketplaces that support this standard **SHOULD** implement some method of transferring royalties to the royalty recipient. Standards for the actual transfer and notification of funds will be specified in future EIPs. - -Marketplaces **MUST** pay the royalty in the same unit of exchange as that of the `_salePrice` passed to `royaltyInfo()`. This is equivalent to saying that the `_salePrice` parameter and the `royaltyAmount` return value **MUST** be denominated in the same monetary unit. For example, if the sale price is in ETH, then the royalty payment must also be paid in ETH, and if the sale price is in USDC, then the royalty payment must also be paid in USDC. - -Implementers of this standard **MUST** calculate a percentage of the `_salePrice` when calculating the royalty amount. Subsequent invocations of `royaltyInfo()` **MAY** return a different `royaltyAmount`. Though there are some important considerations for implementers if they choose to perform different percentage calculations between `royaltyInfo()` invocations. - -The `royaltyInfo()` function is not aware of the unit of exchange for the sale and royalty payment. With that in mind, implementers **MUST NOT** return a fixed/constant `royaltyAmount`, wherein they're ignoring the `_salePrice`. For the same reason, implementers **MUST NOT** determine the `royaltyAmount` based on comparing the `_salePrice` with constant numbers. In both cases, the `royaltyInfo()` function makes assumptions on the unit of exchange, which **MUST** be avoided. - -The percentage value used must be independent of the sale price for reasons previously mentioned (i.e. if the percentage value 10%, then 10% **MUST** apply whether `_salePrice` is 10, 10000 or 1234567890). If the royalty fee calculation results in a remainder, implementers **MAY** round up or round down to the nearest integer. For example, if the royalty fee is 10% and `_salePrice` is 999, the implementer can return either 99 or 100 for `royaltyAmount`, both are valid. - -The implementer **MAY** choose to change the percentage value based on other predictable variables that do not make assumptions about the unit of exchange. For example, the percentage value may drop linearly over time. An approach like this **SHOULD NOT** be based on variables that are unpredictable like `block.timestamp`, but instead on other more predictable state changes. One more reasonable approach **MAY** use the number of transfers of an NFT to decide which percentage value is used to calculate the `royaltyAmount`. The idea being that the percentage value could decrease after each transfer of the NFT. Another example could be using a different percentage value for each unique `_tokenId`. - -Marketplaces that support this standard **SHOULD NOT** send a zero-value transaction if the `royaltyAmount` returned is `0`. This would waste gas and serves no useful purpose in this EIP. - -Marketplaces that support this standard **MUST** pay royalties no matter where the sale occurred or in what currency, including on-chain sales, over-the-counter (OTC) sales and off-chain sales such as at auction houses. As royalty payments are voluntary, entities that respect this EIP must pay no matter where the sale occurred - a sale conducted outside of the blockchain is still a sale. The exact mechanism for paying and notifying the recipient will be defined in future EIPs. - -Implementers of this standard **MUST** have all of the following functions: - -```solidity -pragma solidity ^0.6.0; -import "./IERC165.sol"; - -/// -/// @dev Interface for the NFT Royalty Standard -/// -interface IERC2981 is IERC165 { - /// ERC165 bytes to add to interface array - set in parent contract - /// implementing this standard - /// - /// bytes4(keccak256("royaltyInfo(uint256,uint256)")) == 0x2a55205a - /// bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a; - /// _registerInterface(_INTERFACE_ID_ERC2981); - - /// @notice Called with the sale price to determine how much royalty - // is owed and to whom. - /// @param _tokenId - the NFT asset queried for royalty information - /// @param _salePrice - the sale price of the NFT asset specified by _tokenId - /// @return receiver - address of who should be sent the royalty payment - /// @return royaltyAmount - the royalty payment amount for _salePrice - function royaltyInfo( - uint256 _tokenId, - uint256 _salePrice - ) external view returns ( - address receiver, - uint256 royaltyAmount - ); -} - -interface IERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -### Examples - -This standard being used on an ERC-721 during deployment: - -#### Deploying an ERC-721 and signaling support for ERC-2981 - -```solidity -constructor (string memory name, string memory symbol, string memory baseURI) { - _name = name; - _symbol = symbol; - _setBaseURI(baseURI); - // register the supported interfaces to conform to ERC721 via ERC165 - _registerInterface(_INTERFACE_ID_ERC721); - _registerInterface(_INTERFACE_ID_ERC721_METADATA); - _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE); - // Royalties interface - _registerInterface(_INTERFACE_ID_ERC2981); - } -``` - -#### Checking if the NFT being sold on your marketplace implemented royalties - -```solidity -bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a; - -function checkRoyalties(address _contract) internal returns (bool) { - (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_ERC2981); - return success; - } -``` - -## Rationale - -### Optional royalty payments - -It is impossible to know which NFT transfers are the result of sales, and which are merely wallets moving or consolidating their NFTs. Therefore, we cannot force every transfer function, such as `transferFrom()` in ERC-721, to involve a royalty payment as not every transfer is a sale that would require such payment. We believe the NFT marketplace ecosystem will voluntarily implement this royalty payment standard to provide ongoing funding for artists/creators. NFT buyers will assess the royalty payment as a factor when making NFT purchasing decisions. - -### Simple royalty payments to a single address - -This EIP does not specify the manner of payment to the royalty recipient. Furthermore, it is impossible to fully know and efficiently implement all possible types of royalty payments logic. With that said, it is on the royalty payment receiver to implement all additional complexity and logic for fee splitting, multiple receivers, taxes, accounting, etc. in their own receiving contract or off-chain processes. Attempting to do this as part of this standard, it would dramatically increase the implementation complexity, increase gas costs, and could not possibly cover every potential use-case. This ERC should be considered a minimal, gas-efficient building block for further innovation in NFT royalty payments. Future EIPs can specify more details regarding payment transfer and notification. - -### Royalty payment percentage calculation - -This EIP mandates a percentage-based royalty fee model. It is likely that the most common case of percentage calculation will be where the `royaltyAmount` is always calculated from the `_salePrice` using a fixed percent i.e. if the royalty fee is 10%, then a 10% royalty fee must apply whether `_salePrice` is 10, 10000 or 1234567890. - -As previously mentioned, implementers can get creative with this percentage-based calculation but there are some important caveats to consider. Mainly, ensuring that the `royaltyInfo()` function is not aware of the unit of exchange and that unpredictable variables are avoided in the percentage calculation. To follow up on the earlier `block.timestamp` example, there is some nuance which can be highlighted if the following events ensued: - -1. Marketplace sells NFT. -2. Marketplace delays `X` days before invoking `royaltyInfo()` and sending payment. -3. Marketplace receives `Y` for `royaltyAmount` which was significantly different from the `royaltyAmount` amount that would've been calculated `X` days prior if no delay had occurred. -4. Royalty recipient is dissatisfied with the delay from the marketplace and for this reason, they raise a dispute. - -Rather than returning a percentage and letting the marketplace calculate the royalty amount based on the sale price, a `royaltyAmount` value is returned so there is no dispute with a marketplace over how much is owed for a given sale price. The royalty fee payer must pay the `royaltyAmount` that `royaltyInfo()` stipulates. - -### Unit-less royalty payment across all marketplaces, both on-chain and off-chain - -This EIP does not specify a currency or token used for sales and royalty payments. The same percentage-based royalty fee must be paid regardless of what currency, or token was used in the sale, paid in the same currency or token. This applies to sales in any location including on-chain sales, over-the-counter (OTC) sales, and off-chain sales using fiat currency such as at auction houses. As royalty payments are voluntary, entities that respect this EIP must pay no matter where the sale occurred - a sale outside of the blockchain is still a sale. The exact mechanism for paying and notifying the recipient will be defined in future EIPs. - -### Universal Royalty Payments - -Although designed specifically with NFTs in mind, this standard does not require that a contract implementing EIP-2981 is compatible with either ERC-721 or ERC-1155 standards. Any other contract could use this interface to return royalty payment information, provided that it is able to uniquely identify assets within the constraints of the interface. ERC-2981 is, therefore, a universal royalty standard for many other asset types. - -## Backwards Compatibility - -This standard is compatible with current ERC-721 and ERC-1155 standards. - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-2981.md diff --git a/EIPS/eip-3000.md b/EIPS/eip-3000.md index 7b36755eda0b64..abd2830bf0c73f 100644 --- a/EIPS/eip-3000.md +++ b/EIPS/eip-3000.md @@ -1,154 +1 @@ ---- -eip: 3000 -title: Optimistic enactment governance standard -author: Jorge Izquierdo (@izqui), Fabien Marino (@bonustrack) -discussions-to: https://github.com/ethereum/EIPs/issues/3042 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-09-24 ---- - -## Simple Summary - -Interface for scheduling, executing and challenging contract executions based on off-chain approval - -## Abstract - -ERC-3000 presents a basic on-chain spec for contracts to optimistically enact governance decisions made off-chain. - -The standard is opinionated in defining the 6 entrypoint functions to contracts supporting the standard. But it allows for any sort of resolver mechanism for the challenge/response games characteristic of optimistic contracts. - -While the authors currently believe resolving challenges [using a subjective oracle](https://aragon.org/blog/snapshot) is the right tradeoff, the standard has been designed such that changing to another mechanism is possible (a deterministic resolver like [Optimism's OVM](https://optimism.io) uses), even allowing to hot-swap it in the same live instance. - -## Specification - -### Data structures - -Some data structures are defined which are later used in the standard interfaces: - -```solidity -library ERC3000Data { - struct Container { - Payload payload; - Config config; - } - - struct Payload { - uint256 nonce; - uint256 executionTime; - address submitter; - IERC3000Executor executor; - Action[] actions; - bytes proof; - } - - struct Action { - address to; - uint256 value; - bytes data; - } - - struct Config { - uint256 executionDelay; - Collateral scheduleDeposit; - Collateral challengeDeposit; - Collateral vetoDeposit; - address resolver; - bytes rules; - } - - struct Collateral { - address token; - uint256 amount; - } -} -``` - -### Interface and events - -Given the data structures above, by taking advantage of the Solidity ABI encoder v2, we define four required functions and two optional functions as the interface for contracts to comply with ERC-3000. - -All standard functions are expected to revert (whether to include error messages/revert reasons as part of the standard is yet to be determined) when pre-conditions are not met or an unexpected error occurs. On success, each function must emit its associated event once and only once. - -```solidity -abstract contract IERC3000 { - /** - * @notice Schedules an action for execution, allowing for challenges and vetos on a defined time window - * @param container A Container struct holding both the paylaod being scheduled for execution and - the current configuration of the system - */ - function schedule(ERC3000Data.Container memory container) virtual public returns (bytes32 containerHash); - event Scheduled(bytes32 indexed containerHash, ERC3000Data.Payload payload, ERC3000Data.Collateral collateral); - - /** - * @notice Executes an action after its execution delayed has passed and its state hasn't been altered by a challenge or veto - * @param container A ERC3000Data.Container struct holding both the paylaod being scheduled for execution and - the current configuration of the system - * should be a MUST payload.executor.exec(payload.actions) - */ - function execute(ERC3000Data.Container memory container) virtual public returns (bytes[] memory execResults); - event Executed(bytes32 indexed containerHash, address indexed actor, bytes[] execResults); - - /** - * @notice Challenge a container in case its scheduling is illegal as per Config.rules. Pulls collateral and dispute fees from sender into contract - * @param container A ERC3000Data.Container struct holding both the paylaod being scheduled for execution and - the current configuration of the system - * @param reason Hint for case reviewers as to why the scheduled container is illegal - */ - function challenge(ERC3000Data.Container memory container, bytes memory reason) virtual public returns (uint256 resolverId); - event Challenged(bytes32 indexed containerHash, address indexed actor, bytes reason, uint256 resolverId, ERC3000Data.Collateral collateral); - - /** - * @notice Apply arbitrator's ruling over a challenge once it has come to a final ruling - * @param container A ERC3000Data.Container struct holding both the paylaod being scheduled for execution and - the current configuration of the system - * @param resolverId disputeId in the arbitrator in which the dispute over the container was created - */ - function resolve(ERC3000Data.Container memory container, uint256 resolverId) virtual public returns (bytes[] memory execResults); - event Resolved(bytes32 indexed containerHash, address indexed actor, bool approved); - - /** - * @dev OPTIONAL - * @notice Apply arbitrator's ruling over a challenge once it has come to a final ruling - * @param payloadHash Hash of the payload being vetoed - * @param config A ERC3000Data.Config struct holding the config attached to the payload being vetoed - */ - function veto(bytes32 payloadHash, ERC3000Data.Config memory config, bytes memory reason) virtual public; - event Vetoed(bytes32 indexed containerHash, address indexed actor, bytes reason, ERC3000Data.Collateral collateral); - - /** - * @dev OPTIONAL: implementer might choose not to implement (initial Configured event MUST be emitted) - * @notice Apply a new configuration for all *new* containers to be scheduled - * @param config A ERC3000Data.Config struct holding all the new params that will control the queue - */ - function configure(ERC3000Data.Config memory config) virtual public returns (bytes32 configHash); - event Configured(bytes32 indexed containerHash, address indexed actor, ERC3000Data.Config config); -} -``` - -## Rationale - -The authors believe that it is very important that this standard leaves the other open to any resolver mechanism to be implemented and adopted. - -That's why a lot of the function and variable names were left intentionally bogus to be compatible with future resolvers without changing the standard. - -ERC-3000 should be seen as a public good of top of which public infrastrastructure will be built, being way more important than any particular implementation or the interests of specific companies or projects. - -## Security Considerations - -The standard allows for the resolver for challenges to be configured, and even have different resolvers for coexisting scheduled payloads. Choosing the right resolver requires making the right tradeoff between security, time to finality, implementation complexity, and external dependencies. - -Using a subjective oracle as resolver has its risks, since security depends on the crypto-economic properties of the system. For an analysis of crypto-economic considerations of Aragon Court, you can check [the following doc](https://github.com/aragon/aragon-court/tree/master/docs/3-cryptoeconomic-considerations). - -On the other hand, implementing a deterministic resolver is prone to dangerous bugs given its complexity, and will rely on a specific version of the off-chain protocol, which could rapidly evolve while the standard matures and gets adopted. - -## Implementations - -### 1. Aragon Govern - -- [ERC-3000 interface (MIT license)](https://github.com/aragon/govern/blob/master/packages/erc3k) -- [Implementation (GPL-3.0 license)](https://github.com/aragon/govern/blob/master/packages/govern-core) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3000.md diff --git a/EIPS/eip-3005.md b/EIPS/eip-3005.md index c3f1219dfda2b8..0fc7ca71e578bc 100644 --- a/EIPS/eip-3005.md +++ b/EIPS/eip-3005.md @@ -1,416 +1 @@ ---- -eip: 3005 -title: Batched meta transactions -author: Matt (@defifuture) -discussions-to: https://ethereum-magicians.org/t/eip-3005-the-economic-viability-of-batched-meta-transactions/4673 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-09-25 ---- - -## Simple Summary - -Defines an extension function for ERC-20 (and other fungible token standards), which allows receiving and processing a batch of meta transactions. - -## Abstract - -This EIP defines a new function called `processMetaBatch()` that extends any fungible token standard, and enables batched meta transactions coming from many senders in one on-chain transaction. - -The function must be able to receive multiple meta transactions data and process it. This means validating the data and the signature, before proceeding with token transfers based on the data. - -The function enables senders to make gasless transactions, while reducing the relayer's gas cost due to batching. - -## Motivation - -Meta transactions have proven useful as a solution for Ethereum accounts that don't have any ether, but hold ERC-20 tokens and would like to transfer them (gasless transactions). - -The current meta transaction relayer implementations only allow relaying one meta transaction at a time. Some also allow batched meta transactions from the same sender. But none offers batched meta transactions from **multiple** senders. - -The motivation behind this EIP is to find a way to allow relaying batched meta transactions from **many senders** in **one on-chain transaction**, which also **reduces the total gas cost** that a relayer needs to cover. - -![](../assets/eip-3005/meta-txs-directly-to-token-smart-contract.png) - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -The key words "MUST (BUT WE KNOW YOU WON'T)", "SHOULD CONSIDER", "REALLY SHOULD NOT", "OUGHT TO", "WOULD PROBABLY", "MAY WISH TO", "COULD", "POSSIBLE", and "MIGHT" in this document are to be interpreted as described in RFC 6919. - -### Meta transaction data - -In order to successfully validate and transfer tokens, the `processMetaBatch()` function MUST process the following data about a meta transaction: - -- sender address -- receiver address -- token amount -- relayer fee -- a (meta tx) nonce -- an expiration date (this COULD be a block number, or it COULD be a block timestamp) -- a token address -- a relayer address -- a signature - -Not all of the data needs to be sent to the function by the relayer (see the function interface specification). Some of the data can be deduced or extracted from other sources (from transaction data and contract state). - -### `processMetaBatch()` function input data - -The `processMetaBatch()` function MUST receive the following data: - -- sender address -- receiver address -- token amount -- relayer fee -- an expiration date (this COULD be a block number, or it COULD be a block timestamp) -- a signature - -The following data is OPTIONAL to be sent to the function, because it can be extracted or derived from other sources: - -- a (meta tx) nonce -- a token address -- a relayer address - -### Meta transaction data hash - -The pseudocode for creating a hash of meta transaction data is the following: - -``` -keccak256(address(sender) - ++ address(recipient) - ++ uint256(amount) - ++ uint256(relayerFee) - ++ uint256(nonce) - ++ uint256(expirationDate) - ++ address(tokenContract) - ++ address(relayer) -) -``` - -The created hash MUST then be signed with the sender's private key. - -### Validation rules - -- Nonce of a new transaction MUST always be bigger by exactly 1 from the nonce of the last successfully processed meta transaction of the same sender to the same token contract. -- Sending to and from a 0x0 address MUST be prohibited. -- A meta transaction MUST be processed before the expiration date. -- Each sender's token balance MUST be equal or greater than the sum of their respective meta transaction token amount and relayer fee. -- A transaction where at least one meta transaction in the batch does not satisfy the above requirements MUST not be reverted. Instead, a failed meta transaction MUST be skipped or ignored. - -### `processMetaBatch()` function interface - -The `processMetaBatch()` function MUST have the following interface: - -```solidity -function processMetaBatch(address[] memory senders, - address[] memory recipients, - uint256[] memory amounts, - uint256[] memory relayerFees, - uint256[] memory blocks, - uint8[] memory sigV, - bytes32[] memory sigR, - bytes32[] memory sigS) public returns (bool); -``` - -The overview of parameters that are passed: - -- `senders`: an array of meta transaction sender addresses (token senders) -- `recipients `: an array of token recipients addresses -- `amounts`: an array of token amounts that are sent from each sender to each recipient, respectively -- `relayerFees`: an array of the relayer fees paid in tokens by senders. The fee receiver is a relayer (`msg.address`) -- `blocks`: an array of block numbers that represent an expiration date by which the meta transaction must be processed (alternatively, a timestamp could be used instead of a block number) -- `sigV`, `sigR`, `sigS`: three arrays that represent parts of meta transaction signatures - -Each entry in each of the arrays MUST represent data from one meta transaction. The order of the data is very important. Data from a single meta transaction MUST have the same index in every array. - -### Meta transaction nonce - -The token smart contract must keep track of a meta transaction nonce for each token holder. - -```solidity -mapping (address => uint256) private _metaNonces; -``` - -The interface for the `nonceOf()` function is the following: - -```solidity -function nonceOf(address account) public view returns (uint256); -``` - -### Token transfers - -After a meta transaction is successfully validated, the meta nonce of the meta transaction sender MUST be increased by 1. - -Then two token transfers MUST occur: - -- The specified token amount MUST go to the recipient. -- The relayer fee MUST go to the relayer (`msg.sender`). - -## Implementation - -The **reference implementation** adds a couple of functions to the existing ERC-20 token standard: - -- `processMetaBatch()` -- `nonceOf()` - -You can see the implementation of both functions in this file: [ERC20MetaBatch.sol](https://github.com/defifuture/erc20-batched-meta-transactions/blob/master/contracts/ERC20MetaBatch.sol). This is an extended ERC-20 contract with added meta transaction batch transfer capabilities. - -### `processMetaBatch()` - -The `processMetaBatch()` function is responsible for receiving and processing a batch of meta transactions that change token balances. - -```solidity -function processMetaBatch(address[] memory senders, - address[] memory recipients, - uint256[] memory amounts, - uint256[] memory relayerFees, - uint256[] memory blocks, - uint8[] memory sigV, - bytes32[] memory sigR, - bytes32[] memory sigS) public returns (bool) { - - address sender; - uint256 newNonce; - uint256 relayerFeesSum = 0; - bytes32 msgHash; - uint256 i; - - // loop through all meta txs - for (i = 0; i < senders.length; i++) { - sender = senders[i]; - newNonce = _metaNonces[sender] + 1; - - if(sender == address(0) || recipients[i] == address(0)) { - continue; // sender or recipient is 0x0 address, skip this meta tx - } - - // the meta tx should be processed until (including) the specified block number, otherwise it is invalid - if(block.number > blocks[i]) { - continue; // if current block number is bigger than the requested number, skip this meta tx - } - - // check if meta tx sender's balance is big enough - if(_balances[sender] < (amounts[i] + relayerFees[i])) { - continue; // if sender's balance is less than the amount and the relayer fee, skip this meta tx - } - - // check if the signature is valid - msgHash = keccak256(abi.encode(sender, recipients[i], amounts[i], relayerFees[i], newNonce, blocks[i], address(this), msg.sender)); - if(sender != ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash)), sigV[i], sigR[i], sigS[i])) { - continue; // if sig is not valid, skip to the next meta tx - } - - // set a new nonce for the sender - _metaNonces[sender] = newNonce; - - // transfer tokens - _balances[sender] -= (amounts[i] + relayerFees[i]); - _balances[recipients[i]] += amounts[i]; - relayerFeesSum += relayerFees[i]; - } - - // give the relayer the sum of all relayer fees - _balances[msg.sender] += relayerFeesSum; - - return true; -} -``` - -### `nonceOf()` - -Nonces are needed due to the replay protection (see *Replay attacks* under *Security Considerations*). - -```solidity -mapping (address => uint256) private _metaNonces; - -// ... - -function nonceOf(address account) public view returns (uint256) { - return _metaNonces[account]; -} -``` - -The link to the complete implementation (along with gas usage results) is here: [https://github.com/defifuture/erc20-batched-meta-transactions](https://github.com/defifuture/erc20-batched-meta-transactions). - -> Note that the OpenZeppelin ERC-20 implementation was used here. Some other implementation may have named the `_balances` mapping differently, which would require minor changes in the `processMetaBatch()` function. - -## Rationale - -### All-in-one - -Alternative implementations (like GSN) use multiple smart contracts to enable meta transactions, although this increases gas usage. This implementation (EIP-3005) intentionally keeps everything within one function which reduces complexity and gas cost. - -The `processMetaBatch()` function thus does the job of receiving a batch of meta transactions, validating them, and then transferring tokens from one address to another. - -### Function parameters - -As you can see, the `processMetaBatch()` function in the reference implementation takes the following parameters: - -- an array of **sender addresses** (meta txs senders, not relayers) -- an array of **receiver addresses** -- an array of **amounts** -- an array of **relayer fees** (relayer is `msg.sender`) -- an array of **block numbers** (a due "date" for meta tx to be processed) -- Three arrays that represent parts of a **signature** (v, r, s) - -**Each item** in these arrays represents **data of one meta transaction**. That's why the **correct order** in the arrays is very important. - -If a relayer gets the order wrong, the `processMetaBatch()` function would notice that (when validating a signature), because the hash of the meta transaction values would not match the signed hash. A meta transaction with an invalid signature is **skipped**. - -### The alternative way of passing meta transaction data into the function - -The reference implementation takes parameters as arrays. There's a separate array for each meta transaction data category (the ones that cannot be deduced or extracted from other sources). - -A different approach would be to bitpack all data of a meta transaction into one value and then unpack it within the smart contract. The data for a batch of meta transactions would be sent in an array, but there would need to be only one array (of packed data), instead of multiple arrays. - -### Why is nonce not one of the parameters in the reference implementation? - -Meta nonce is used for constructing a signed hash (see the `msgHash` line where a `keccak256` hash is constructed - you'll find a nonce there). - -Since a new nonce has to always be bigger than the previous one by exactly 1, there's no need to include it as a parameter array in the `processMetaBatch()` function, because its value can be deduced. - -This also helps avoid the "Stack too deep" error. - -### Can EIP-2612 nonces mapping be re-used? - -The EIP-2612 (`permit()` function) also requires a nonce mapping. At this point, I'm not sure yet if this mapping should be **re-used** in case a smart contract implements both EIP-3005 and EIP-2612. - -At the first glance, it seems the `nonces` mapping from EIP-2612 could be re-used, but this should be thought through (and tested) for possible security implications. - -### Token transfers - -Token transfers in the reference implementation could alternatively be done by calling the `_transfer()` function (part of the OpenZeppelin ERC-20 implementation), but it would increase the gas usage and it would also revert the whole batch if some meta transaction was invalid (the current implementation just skips it). - -Another gas usage optimization is to assign total relayer fees to the relayer at the end of the function, and not with every token transfer inside the for loop (thus avoiding multiple SSTORE calls that cost 5'000 gas). - -## Backwards Compatibility - -The code implementation of batched meta transactions is backwards compatible with any fungible token standard, for example, ERC-20 (it only extends it with one function). - -## Test Cases - -Link to tests: [https://github.com/defifuture/erc20-batched-meta-transactions/tree/master/test](https://github.com/defifuture/erc20-batched-meta-transactions/tree/master/test). - -## Security Considerations - -Here is a list of potential security issues and how are they addressed in this implementation. - -### Forging a meta transaction - -The solution against a relayer forging a meta transaction is for a user to sign the meta transaction with their private key. - -The `processMetaBatch()` function then verifies the signature using `ecrecover()`. - -### Replay attacks - -The `processMetaBatch()` function is secure against two types of a replay attack: - -**Using the same meta transaction twice in the same token smart contract** - -A nonce prevents a replay attack where a relayer would send the same meta transaction more than once. - -**Using the same meta transaction twice in different token smart contracts** - -A token smart contract address must be added into the signed hash (of a meta transaction). - -This address does not need to be sent as a parameter into the `processMetaBatch()` function. Instead, the function uses `address(this)` when constructing a hash in order to verify the signature. This way a meta transaction not intended for the token smart contract would be rejected (skipped). - -### Signature validation - -Signing a meta transaction and validating the signature is crucial for this whole scheme to work. - -The `processMetaBatch()` function validates a meta transaction signature, and if it's **invalid**, the meta transaction is **skipped** (but the whole on-chain transaction is **not reverted**). - -```solidity -msgHash = keccak256(abi.encode(sender, recipients[i], amounts[i], relayerFees[i], newNonce, blocks[i], address(this), msg.sender)); - -if(sender != ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash)), sigV[i], sigR[i], sigS[i])) { - continue; // if sig is not valid, skip to the next meta tx -} -``` - -Why not reverting the whole on-chain transaction? Because there could be only one problematic meta transaction, and the others should not be dropped just because of one rotten apple. - -That said, it is expected of relayers to validate meta transactions in advance before relaying them. That's why relayers are not entitled to a relayer fee for an invalid meta transaction. - -### Malicious relayer forcing a user into over-spending - -A malicious relayer could delay sending some user's meta transaction until the user would decide to make the token transaction on-chain. - -After that, the relayer would relay the delayed meta transaction which would mean that the user would have made two token transactions (over-spending). - -**Solution:** Each meta transaction should have an "expiry date". This is defined in a form of a block number by which the meta transaction must be relayed on-chain. - -```solidity -function processMetaBatch(... - uint256[] memory blocks, - ...) public returns (bool) { - - //... - - // loop through all meta txs - for (i = 0; i < senders.length; i++) { - - // the meta tx should be processed until (including) the specified block number, otherwise it is invalid - if(block.number > blocks[i]) { - continue; // if current block number is bigger than the requested number, skip this meta tx - } - - //... -``` - -### Front-running attack - -A malicious relayer could scout the Ethereum mempool to steal meta transactions and front-run the original relayer. - -**Solution:** The protection that `processMetaBatch()` function uses is that it requires the meta transaction sender to add the relayer's Ethereum address as one of the values in the hash (which is then signed). - -When the `processMetaBatch()` function generates a hash it includes the `msg.sender` address in it: - -```solidity -msgHash = keccak256(abi.encode(sender, recipients[i], amounts[i], relayerFees[i], newNonce, blocks[i], address(this), msg.sender)); - -if(sender != ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", msgHash)), sigV[i], sigR[i], sigS[i])) { - continue; // if sig is not valid, skip to the next meta tx -} -``` - -If the meta transaction was "stolen", the signature check would fail because the `msg.sender` address would not be the same as the intended relayer's address. - -### A malicious (or too impatient) user sending a meta transaction with the same nonce through multiple relayers at once - -A user that is either malicious or just impatient could submit a meta transaction with the same nonce (for the same token contract) to various relayers. Only one of them would get the relayer fee (the first one on-chain), while the others would get an invalid meta transaction. - -**Solution:** Relayers could **share a list of their pending meta transactions** between each other (sort of an info mempool). - -The relayers don't have to fear that someone would steal their respective pending transactions, due to the front-running protection (see above). - -If relayers see meta transactions from a certain sender address that have the same nonce and are supposed to be relayed to the same token smart contract, they can decide that only the first registered meta transaction goes through and others are dropped (or in case meta transactions were registered at the same time, the remaining meta transaction could be randomly picked). - -At a minimum, relayers need to share this meta transaction data (in order to detect meta transaction collision): - -- sender address -- token address -- nonce - -### Too big due block number - -The relayer could trick the meta transaction sender into adding too big due block number - this means a block by which the meta transaction must be processed. The block number could be far in the future, for example, 10 years in the future. This means that the relayer would have 10 years to submit the meta transaction. - -**One way** to solve this problem is by adding an upper bound constraint for a block number within the smart contract. For example, we could say that the specified due block number must not be bigger than 100'000 blocks from the current one (this is around 17 days in the future if we assume 15 seconds block time). - -```solidity -// the meta tx should be processed until (including) the specified block number, otherwise it is invalid -if(block.number > blocks[i] || blocks[i] > (block.number + 100000)) { - // If current block number is bigger than the requested due block number, skip this meta tx. - // Also skip if the due block number is too big (bigger than 100'000 blocks in the future). - continue; -} -``` - -This addition could open new security implications, that's why it is left out of this proof-of-concept. But anyone who wishes to implement it should know about this potential constraint, too. - -**The other way** is to keep the `processMetaBatch()` function as it is and rather check for the too big due block number **on the relayer level**. In this case, the user could be notified about the problem and could issue a new meta transaction with another relayer that would have a much lower block parameter (and the same nonce). - -## Copyright - -Copyright and related rights are waived via [CC0](../LICENSE.md). \ No newline at end of file +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3005.md diff --git a/EIPS/eip-3009.md b/EIPS/eip-3009.md index 1919a5d0efa13b..f498e6320beb53 100644 --- a/EIPS/eip-3009.md +++ b/EIPS/eip-3009.md @@ -1,536 +1 @@ ---- -eip: 3009 -title: Transfer With Authorization -author: Peter Jihoon Kim (@petejkim), Kevin Britz (@kbrizzle), David Knott (@DavidLKnott) -discussions-to: https://github.com/ethereum/EIPs/issues/3010 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-09-28 -requires: 20, 712 ---- - -## Simple Summary - -A contract interface that enables transferring of fungible assets via a signed authorization. - -## Abstract - -A set of functions to enable meta-transactions and atomic interactions with [ERC-20](./eip-20.md) token contracts via signatures conforming to the [EIP-712](./eip-712.md) typed message signing specification. - -This enables the user to: - -- delegate the gas payment to someone else, -- pay for gas in the token itself rather than in ETH, -- perform one or more token transfers and other operations in a single atomic transaction, -- transfer ERC-20 tokens to another address, and have the recipient submit the transaction, -- batch multiple transactions with minimal overhead, and -- create and perform multiple transactions without having to worry about them failing due to accidental nonce-reuse or improper ordering by the miner. - -## Motivation - -There is an existing spec, [EIP-2612](./eip-2612), that also allows meta-transactions, and it is encouraged that a contract implements both for maximum compatibility. The two primary differences between this spec and EIP-2612 are that: - -- EIP-2612 uses sequential nonces, but this uses random 32-byte nonces, and that -- EIP-2612 relies on the ERC-20 `approve`/`transferFrom` ("ERC-20 allowance") pattern. - -The biggest issue with the use of sequential nonces is that it does not allow users to perform more than one transaction at time without risking their transactions failing, because: - -- DApps may unintentionally reuse nonces that have not yet been processed in the blockchain. -- Miners may process the transactions in the incorrect order. - -This can be especially problematic if the gas prices are very high and transactions often get queued up and remain unconfirmed for a long time. Non-sequential nonces allow users to create as many transactions as they want at the same time. - -The ERC-20 allowance mechanism is susceptible to the [multiple withdrawal attack](https://blockchain-projects.readthedocs.io/multiple_withdrawal.html)/[SWC-114](https://swcregistry.io/docs/SWC-114), and encourages antipatterns such as the use of the "infinite" allowance. The wide-prevalence of upgradeable contracts have made the conditions favorable for these attacks to happen in the wild. - -The deficiencies of the ERC-20 allowance pattern brought about the development of alternative token standards such as the [ERC-777](./eip-777) and [ERC-677](https://github.com/ethereum/EIPs/issues/677). However, they haven't been able to gain much adoption due to compatibility and potential security issues. - -## Specification - -### Event - -```solidity -event AuthorizationUsed( - address indexed authorizer, - bytes32 indexed nonce -); - -// keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") -bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; - -// keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") -bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; - -/** - * @notice Returns the state of an authorization - * @dev Nonces are randomly generated 32-byte data unique to the authorizer's - * address - * @param authorizer Authorizer's address - * @param nonce Nonce of the authorization - * @return True if the nonce is used - */ -function authorizationState( - address authorizer, - bytes32 nonce -) external view returns (bool); - -/** - * @notice Execute a transfer with a signed authorization - * @param from Payer's address (Authorizer) - * @param to Payee's address - * @param value Amount to be transferred - * @param validAfter The time after which this is valid (unix time) - * @param validBefore The time before which this is valid (unix time) - * @param nonce Unique nonce - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - */ -function transferWithAuthorization( - address from, - address to, - uint256 value, - uint256 validAfter, - uint256 validBefore, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s -) external; - -/** - * @notice Receive a transfer with a signed authorization from the payer - * @dev This has an additional check to ensure that the payee's address matches - * the caller of this function to prevent front-running attacks. (See security - * considerations) - * @param from Payer's address (Authorizer) - * @param to Payee's address - * @param value Amount to be transferred - * @param validAfter The time after which this is valid (unix time) - * @param validBefore The time before which this is valid (unix time) - * @param nonce Unique nonce - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - */ -function receiveWithAuthorization( - address from, - address to, - uint256 value, - uint256 validAfter, - uint256 validBefore, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s -) external; -``` - -**Optional:** - -``` -event AuthorizationCanceled( - address indexed authorizer, - bytes32 indexed nonce -); - -// keccak256("CancelAuthorization(address authorizer,bytes32 nonce)") -bytes32 public constant CANCEL_AUTHORIZATION_TYPEHASH = 0x158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429; - -/** - * @notice Attempt to cancel an authorization - * @param authorizer Authorizer's address - * @param nonce Nonce of the authorization - * @param v v of the signature - * @param r r of the signature - * @param s s of the signature - */ -function cancelAuthorization( - address authorizer, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s -) external; -``` - - -The arguments `v`, `r`, and `s` must be obtained using the [EIP-712](./eip-712.md) typed message signing spec. - -**Example:** - -``` -DomainSeparator := Keccak256(ABIEncode( - Keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ), - Keccak256("USD Coin"), // name - Keccak256("2"), // version - 1, // chainId - 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 // verifyingContract -)) -``` - -With the domain separator, the typehash, which is used to identify the type of the EIP-712 message being used, and the values of the parameters, you are able to derive a Keccak-256 hash digest which can then be signed using the token holder's private key. - -**Example:** - -``` -// Transfer With Authorization -TypeHash := Keccak256( - "TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)" -) -Params := { From, To, Value, ValidAfter, ValidBefore, Nonce } - -// ReceiveWithAuthorization -TypeHash := Keccak256( - "ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)" -) -Params := { From, To, Value, ValidAfter, ValidBefore, Nonce } - -// CancelAuthorization -TypeHash := Keccak256( - "CancelAuthorization(address authorizer,bytes32 nonce)" -) -Params := { Authorizer, Nonce } -``` - -``` -// "‖" denotes concatenation. -Digest := Keecak256( - 0x1901 ‖ DomainSeparator ‖ Keccak256(ABIEncode(TypeHash, Params...)) -) - -{ v, r, s } := Sign(Digest, PrivateKey) -``` - -Smart contract functions that wrap `receiveWithAuthorization` call may choose to reduce the number of arguments by accepting the full ABI-encoded set of arguments for the `receiveWithAuthorization` call as a single argument of the type `bytes`. - -**Example:** - -```solidity -// keccak256("receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)")[0:4] -bytes4 private constant _RECEIVE_WITH_AUTHORIZATION_SELECTOR = 0xef55bec6; - -function deposit(address token, bytes calldata receiveAuthorization) - external - nonReentrant -{ - (address from, address to, uint256 amount) = abi.decode( - receiveAuthorization[0:96], - (address, address, uint256) - ); - require(to == address(this), "Recipient is not this contract"); - - (bool success, ) = token.call( - abi.encodePacked( - _RECEIVE_WITH_AUTHORIZATION_SELECTOR, - receiveAuthorization - ) - ); - require(success, "Failed to transfer tokens"); - - ... -} -``` - -### Use with web3 providers - -The signature for an authorization can be obtained using a web3 provider with the `eth_signTypedData{_v4}` method. - -**Example:** - -```javascript -const data = { - types: { - EIP712Domain: [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - { name: "verifyingContract", type: "address" }, - ], - TransferWithAuthorization: [ - { name: "from", type: "address" }, - { name: "to", type: "address" }, - { name: "value", type: "uint256" }, - { name: "validAfter", type: "uint256" }, - { name: "validBefore", type: "uint256" }, - { name: "nonce", type: "bytes32" }, - ], - }, - domain: { - name: tokenName, - version: tokenVersion, - chainId: selectedChainId, - verifyingContract: tokenAddress, - }, - primaryType: "TransferWithAuthorization", - message: { - from: userAddress, - to: recipientAddress, - value: amountBN.toString(10), - validAfter: 0, - validBefore: Math.floor(Date.now() / 1000) + 3600, // Valid for an hour - nonce: Web3.utils.randomHex(32), - }, -}; - -const signature = await ethereum.request({ - method: "eth_signTypedData_v4", - params: [userAddress, JSON.stringify(data)], -}); - -const v = "0x" + signature.slice(130, 132); -const r = signature.slice(0, 66); -const s = "0x" + signature.slice(66, 130); -``` - -## Rationale - -### Unique Random Nonce, Instead of Sequential Nonce - -One might say transaction ordering is one reason why sequential nonces are preferred. However, sequential nonces do not actually help achieve transaction ordering for meta transactions in practice: - -- For native Ethereum transactions, when a transaction with a nonce value that is too-high is submitted to the network, it will stay pending until the transactions consuming the lower unused nonces are confirmed. -- However, for meta-transactions, when a transaction containing a sequential nonce value that is too high is submitted, instead of staying pending, it will revert and fail immediately, resulting in wasted gas. -- The fact that miners can also reorder transactions and include them in the block in the order they want (assuming each transaction was submitted to the network by different meta-transaction relayers) also makes it possible for the meta-transactions to fail even if the nonces used were correct. (e.g. User submits nonces 3, 4 and 5, but miner ends up including them in the block as 4,5,3, resulting in only 3 succeeding) -- Lastly, when using different applications simultaneously, in absence of some sort of an off-chain nonce-tracker, it is not possible to determine what the correct next nonce value is if there exists nonces that are used but haven't been submitted and confirmed by the network. -- Under high gas price conditions, transactions can often "get stuck" in the pool for a long time. Under such a situation, it is much more likely for the same nonce to be unintentionally reused twice. For example, if you make a meta-transaction that uses a sequential nonce from one app, and switch to another app to make another meta-transaction before the previous one confirms, the same nonce will be used if the app relies purely on the data available on-chain, resulting in one of the transactions failing. -- In conclusion, the only way to guarantee transaction ordering is for relayers to submit transactions one at a time, waiting for confirmation between each submission (and the order in which they should be submitted can be part of some off-chain metadata), rendering sequential nonce irrelevant. - -### Valid After and Valid Before - -- Relying on relayers to submit transactions for you means you may not have exact control over the timing of transaction submission. -- These parameters allow the user to schedule a transaction to be only valid in the future or before a specific deadline, protecting the user from potential undesirable effects that may be caused by the submission being made either too late or too early. - -### EIP-712 - -- EIP-712 ensures that the signatures generated are valid only for this specific instance of the token contract and cannot be replayed on a different network with a different chain ID. -- This is achieved by incorporating the contract address and the chain ID in a Keccak-256 hash digest called the domain separator. The actual set of parameters used to derive the domain separator is up to the implementing contract, but it is highly recommended that the fields `verifyingContract` and `chainId` are included. - -## Backwards Compatibility - -New contracts benefit from being able to directly utilize EIP-3009 in order to create atomic transactions, but existing contracts may still rely on the conventional ERC-20 allowance pattern (`approve`/`transferFrom`). - -In order to add support for EIP-3009 to existing contracts ("parent contract") that use the ERC-20 allowance pattern, a forwarding contract ("forwarder") can be constructed that takes an authorization and does the following: - -1. Extract the user and deposit amount from the authorization -2. Call `receiveWithAuthorization` to transfer specified funds from the user to the forwarder -3. Approve the parent contract to spend funds from the forwarder -4. Call the method on the parent contract that spends the allowance set from the forwarder -5. Transfer the ownership of any resulting tokens back to the user - -**Example:** - -```solidity -interface IDeFiToken { - function deposit(uint256 amount) external returns (uint256); - - function transfer(address account, uint256 amount) - external - returns (bool); -} - -contract DepositForwarder { - bytes4 private constant _RECEIVE_WITH_AUTHORIZATION_SELECTOR = 0xef55bec6; - - IDeFiToken private _parent; - IERC20 private _token; - - constructor(IDeFiToken parent, IERC20 token) public { - _parent = parent; - _token = token; - } - - function deposit(bytes calldata receiveAuthorization) - external - nonReentrant - returns (uint256) - { - (address from, address to, uint256 amount) = abi.decode( - receiveAuthorization[0:96], - (address, address, uint256) - ); - require(to == address(this), "Recipient is not this contract"); - - (bool success, ) = address(_token).call( - abi.encodePacked( - _RECEIVE_WITH_AUTHORIZATION_SELECTOR, - receiveAuthorization - ) - ); - require(success, "Failed to transfer to the forwarder"); - - require( - _token.approve(address(_parent), amount), - "Failed to set the allowance" - ); - - uint256 tokensMinted = _parent.deposit(amount); - require( - _parent.transfer(from, tokensMinted), - "Failed to transfer the minted tokens" - ); - - uint256 remainder = _token.balanceOf(address(this); - if (remainder > 0) { - require( - _token.transfer(from, remainder), - "Failed to refund the remainder" - ); - } - - return tokensMinted; - } -} -``` - -## Test Cases - -See [EIP3009.test.ts](https://github.com/CoinbaseStablecoin/eip-3009/blob/master/test/EIP3009.test.ts). - -## Implementation - -**EIP3009.sol** -```solidity -abstract contract EIP3009 is IERC20Transfer, EIP712Domain { - // keccak256("TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") - bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = 0x7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267; - - // keccak256("ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)") - bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = 0xd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8; - - mapping(address => mapping(bytes32 => bool)) internal _authorizationStates; - - event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce); - - string internal constant _INVALID_SIGNATURE_ERROR = "EIP3009: invalid signature"; - - function authorizationState(address authorizer, bytes32 nonce) - external - view - returns (bool) - { - return _authorizationStates[authorizer][nonce]; - } - - function transferWithAuthorization( - address from, - address to, - uint256 value, - uint256 validAfter, - uint256 validBefore, - bytes32 nonce, - uint8 v, - bytes32 r, - bytes32 s - ) external { - require(now > validAfter, "EIP3009: authorization is not yet valid"); - require(now < validBefore, "EIP3009: authorization is expired"); - require( - !_authorizationStates[from][nonce], - "EIP3009: authorization is used" - ); - - bytes memory data = abi.encode( - TRANSFER_WITH_AUTHORIZATION_TYPEHASH, - from, - to, - value, - validAfter, - validBefore, - nonce - ); - require( - EIP712.recover(DOMAIN_SEPARATOR, v, r, s, data) == from, - "EIP3009: invalid signature" - ); - - _authorizationStates[from][nonce] = true; - emit AuthorizationUsed(from, nonce); - - _transfer(from, to, value); - } -} -``` - -**IERC20Transfer.sol** -```solidity -abstract contract IERC20Transfer { - function _transfer( - address sender, - address recipient, - uint256 amount - ) internal virtual; -} -``` - -**EIP712Domain.sol** -```solidity -abstract contract EIP712Domain { - bytes32 public DOMAIN_SEPARATOR; -} -``` - -**EIP712.sol** -```solidity -library EIP712 { - // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") - bytes32 public constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; - - function makeDomainSeparator(string memory name, string memory version) - internal - view - returns (bytes32) - { - uint256 chainId; - assembly { - chainId := chainid() - } - - return - keccak256( - abi.encode( - EIP712_DOMAIN_TYPEHASH, - keccak256(bytes(name)), - keccak256(bytes(version)), - address(this), - bytes32(chainId) - ) - ); - } - - function recover( - bytes32 domainSeparator, - uint8 v, - bytes32 r, - bytes32 s, - bytes memory typeHashAndData - ) internal pure returns (address) { - bytes32 digest = keccak256( - abi.encodePacked( - "\x19\x01", - domainSeparator, - keccak256(typeHashAndData) - ) - ); - address recovered = ecrecover(digest, v, r, s); - require(recovered != address(0), "EIP712: invalid signature"); - return recovered; - } -} -``` - -A fully working implementation of EIP-3009 can be found in [this repository](https://github.com/CoinbaseStablecoin/eip-3009/blob/master/contracts/lib/EIP3009.sol). The repository also includes [an implementation of EIP-2612](https://github.com/CoinbaseStablecoin/eip-3009/blob/master/contracts/lib/EI32612.sol) that uses the EIP-712 library code presented above. - -## Security Considerations - -Use `receiveWithAuthorization` instead of `transferWithAuthorization` when calling from other smart contracts. It is possible for an attacker watching the transaction pool to extract the transfer authorization and front-run the `transferWithAuthorization` call to execute the transfer without invoking the wrapper function. This could potentially result in unprocessed, locked up deposits. `receiveWithAuthorization` prevents this by performing an additional check that ensures that the caller is the payee. Additionally, if there are multiple contract functions accepting receive authorizations, the app developer could dedicate some leading bytes of the nonce could as the identifier to prevent cross-use. - -When submitting multiple transfers simultaneously, be mindful of the fact that relayers and miners will decide the order in which they are processed. This is generally not a problem if the transactions are not dependent on each other, but for transactions that are highly dependent on each other, it is recommended that the signed authorizations are submitted one at a time. - -The zero address must be rejected when using `ecrecover` to prevent unauthorized transfers and approvals of funds from the zero address. The built-in `ecrecover` returns the zero address when a malformed signature is provided. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3009.md diff --git a/EIPS/eip-3135.md b/EIPS/eip-3135.md index bc7133d5c15fea..956809a6f1b78e 100644 --- a/EIPS/eip-3135.md +++ b/EIPS/eip-3135.md @@ -1,268 +1 @@ ---- -eip: 3135 -title: Exclusive Claimable Token -author: Zhenyu Sun (@Ungigdu) -discussions-to: https://github.com/ethereum/EIPs/issues/3132 -status: Stagnant -type: Standards Track -category: ERC -created: 2020-08-10 -requires: 20 ---- - -## Simple Summary - -This standard defines a token which can be claimed only by token issuer with payer's signature. - -## Abstract - -This EIP defines a set of additions to the default token standard such as ERC-20, that allows online/offline service providers establish micropayment channels with any number of users by signing and verifying messages about the consumption of token off chain. Using this mechanism will reduce interactions with blockchain to minimal for both participants, thus saving gas and improve performance. - -## Motivation - -There are two main purposes of this EIP, one is to reduce interactions with blockchain, the second is to link Ethereum to real-world payment problems. - -Many small businesses want to build payment system based on blockchain but find it difficult. There are basically two ways: - -1. Directly pay with token. There are many wallet can receive and transfer token but transactions on Ethereum cost gas and take time to confirm. -2. User lock token on payment smart contract and service provider use payment messages signed by user to release token, establishing a micropayment channel. The advantage is interactions with blockchain is reduced and the signing/verifying process is off-chain. But interact with payment contract needs service provider to build a DApp, which require resources many small businesses do not have. Even if they managed to build DApps, they are all different, not standardized. Also, user should have a wallet with DApp browser and has to learn how to use it. - -This EIP helps to standardize the interactions of micropayment system, and make it possible for wallet build a universal UI in the future. - -## Specification - -```solidity - -/// @return Image url of this token or descriptive resources -function iconUrl() external view returns (string memory); - -/// @return Issuer of this token. Only issuer can execute claim function -function issuer() external view returns (address); - -/** - * @notice Remove consumption from payer's deposite - * @dev Check if msg.sender == issuer - * @param from Payer's address - * @param consumption How many token is consumed in this epoch, specified - * @param epoch Epoch increased by 1 after claim or withdraw, at the beginning of each epoch, consumption goes back to 0 - * @param signature Signature of payment message signed by payer -*/ -function claim(address from, uint256 consumption, uint256 epoch, bytes calldata signature) external; - -function transferIssuer(address newIssuer) external; - -/// @notice Move amount from payer's token balance to deposite balance to ensure payment is sufficient -function deposit(uint256 amount) external; - -/** - * @notice Give remaining deposite balance back to "to" account, act as "refund" function - * @dev In prepayment module, withdraw is executed from issuer account - * In lock-release module, withdraw is executed from user account - * @param to the account receiving remaining deposite - * @param amount how many token is returned -*/ -function withdraw(address to, uint256 amount) external; - -function depositBalanceOf(address user) external view returns(uint256 depositBalance, uint256 epoch); - -event Deposit( - address indexed from, - uint256 amount -); - -event Withdraw( - address indexed to, - uint256 amount -); - -event TransferIssuer( - address indexed oldIssuer, - address indexed newIssuer -); - -event Claim( - address indexed from, - address indexed to, - uint256 epoch, - uint256 consumption -); - -``` - -### signature - -the pseudo code generating an ECDSA signature: -``` -sign(keccak256(abi_encode( - "\x19Ethereum Signed Message:\n32", - keccak256(abi_encode( - token_address, - payer_address, - token_issuer, - token_consumption, //calculated by user client - epoch - )) - )) -,private_key) - -``` - -### verification process - -the verification contains check about both signature and token_consumption - -the pseudo code run by verification server is as follows: - -``` - -serving_loop: - - for { - /** - * unpaied_consumption is calculated by provider - * signed_consumption is claimable amount - * tolerance allows payer "owes" provider to a certain degree - */ - //getSignedConsumption returns amount that are already claimable - if(unpaied_consumption < signed_consumption + tolerance){ - informUser("user need charge", unpaied_consumption) - interruptService() - }else{ - isServing() || recoverService() - } - } - -verification_loop: - - for { - message = incomingMessage() - if(recover_signer(message, signature) != payer_address){ - informUser("check signature failed", hash(message)) - continue - } - - /** - * optional: when using echo server to sync messages between verification servers - * more info about this in Security Considerations section - */ - if(query(message) != message){ - informUser("message outdate", hash(message)) - continue - } - - if(epoch != message.epoch || message.consumption > getDepositBalance()){ - informUser("invalid message", epoch, unpaied_consumption) - continue - } - - signed_consumption = message.consumption - save(message) - } - -claim_process: - - if(claim()){ - unpaied_consumption -= signed_consumption - signed_consumption = 0 - epoch+=1 - } - -``` -### About withdraw - -The withdraw function is slightly different based on business models - -1. prepayment model - -In prepayment business model such as using token as recharge card of general store, the user pays (crypto)currency to store in advance for claimable token as recharge card (with bonus or discount). When checking out, the customer signs a message with updated consumption (old consumption + consumption this time) to store and store verifies this message off chain. The shopping process loops without any blockchain involved, until the customer wants to return the card and get money back. Because the store already holds all currency, the withdraw function should be executed by token issuer (store) to return remaining deposit balance after claim. The prepayment model can easily be built into a wallet with QR-code scanning function. - -2. lock-release model - -If we run a paid end-to-end encrypted e-mail service that accepts token as payment, we can use lock-release model. Unlike prepayment, we charge X * N token for an e-mail sent to N recipients. In this "pay for usage" scenario, the counting of services happens on both client and server side. The client should not trust charge amount given by server in case the it's malfunctioning or malicious. When client decide not to trust server, it stops signing messages, but some of token is taken hostage in deposit balance. To fix this problem, the withdraw function should be executed by payer account with limitation such as epoch didn't change in a month. - -## Rationale - -This EIP targets on ERC-20 tokens due to its widespread adoption. However, this extension is designed to be compatible with other token standard. - -The reason we chose to implement those functions in token contract rather than a separate record contract is as follows: -- Token can transfer is more convenient and more general than interact with DApp -- Token is more standardized and has better UI support -- Token is equal to service, make token economy more prosperous -- Remove the approve process - -## Backwards Compatibility - -This EIP is fully backwards compatible as its implementation extends the functionality of [ERC-20](./eip-20.md). - -## Implementation - -```solidity - -mapping (address => StampBalance) private _depositBalance; - -struct StampBalance{ - uint256 balance; - uint256 epoch; -} - -function deposit(uint256 value) override external{ - require(value <= _balances[msg.sender]); - _balances[msg.sender] = _balances[msg.sender].sub(value); - _depositBalance[msg.sender].balance = _depositBalance[msg.sender].balance.add(value); - emit Deposit(msg.sender, value); -} - -function withdraw(address to, uint256 value) override onlyIssuer external{ - require(value <= _depositBalance[to].balance); - _depositBalance[to].balance = _depositBalance[to].balance.sub(value); - _depositBalance[to].epoch += 1; - _balances[to] = _balances[to].add(value); - emit Withdraw(to, value); -} - -function depositBalanceOf(address user) override public view returns(uint256 depositBalance, uint256 epoch){ - return (_depositBalance[user].balance, _depositBalance[user].epoch); -} - -// prepayment model -function claim(address from, uint credit, uint epoch, bytes memory signature) override onlyIssuer external{ - require(credit > 0); - require(_depositBalance[from].epoch + 1 == epoch); - require(_depositBalance[from].balance >= credit); - bytes32 message = keccak256(abi.encode(this, from, _issuer, credit, epoch)); - bytes32 msgHash = prefixed(message); - require(recoverSigner(msgHash, signature) == from); - _depositBalance[from].balance = _depositBalance[from].balance.sub(credit); - _balances[_issuer] = _balances[_issuer].add(credit); - _depositBalance[from].epoch += 1; - emit Claim(from, msg.sender, credit, epoch); -} - -function prefixed(bytes32 hash) internal pure returns (bytes32) { - return keccak256(abi.encode("\x19Ethereum Signed Message:\n32", hash)); -} - -function recoverSigner(bytes32 message, bytes memory sig) internal pure returns (address) { - (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); - return ecrecover(message, v, r, s); -} - -function splitSignature(bytes memory sig) internal pure returns (uint8 v, bytes32 r, bytes32 s) { - require(sig.length == 65); - assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - v := byte(0, mload(add(sig, 96))) - } - return (v, r, s); -} - -``` - -## Security Considerations - -By restricting claim function to issuer, there is no race condition on chain layer. However double spending problem may occur when the issuer use multiple verifiers and payer signs many payment messages simultaneously. Some of those messages may get chance to be checked valid though only the message with the largest consumption can be claimed. This problem can be fixed by introducing an echo server which accepts messages from verifiers, returns the message sequentially with largest consumption and biggest epoch number. If a verifier gets an answer different from the message he send, it updates the message from echo server as the last message it receives along with local storage of the status about this payer. Then the verifier asks the payer again for a new message. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3135.md diff --git a/EIPS/eip-3156.md b/EIPS/eip-3156.md index 7c1e5929accb0a..20ac9dad533748 100644 --- a/EIPS/eip-3156.md +++ b/EIPS/eip-3156.md @@ -1,517 +1 @@ ---- -eip: 3156 -title: Flash Loans -author: Alberto Cuesta Cañada (@alcueca), Fiona Kobayashi (@fifikobayashi), fubuloubu (@fubuloubu), Austin Williams (@onewayfunction) -discussions-to: https://ethereum-magicians.org/t/erc-3156-flash-loans-review-discussion/5077 -status: Final -type: Standards Track -category: ERC -created: 2020-11-15 ---- - -## Simple Summary - -This ERC provides standard interfaces and processes for single-asset flash loans. - -## Abstract - -A flash loan is a smart contract transaction in which a lender smart contract lends assets to a borrower smart contract with the condition that the assets are returned, plus an optional fee, before the end of the transaction. This ERC specifies interfaces for lenders to accept flash loan requests, and for borrowers to take temporary control of the transaction within the lender execution. The process for the safe execution of flash loans is also specified. - -## Motivation - -Flash loans allow smart contracts to lend an amount of tokens without a requirement for collateral, with the condition that they must be returned within the same transaction. - -Early adopters of the flash loan pattern have produced different interfaces and different use patterns. The diversification is expected to intensify, and with it the technical debt required to integrate with diverse flash lending patterns. - -Some of the high level differences in the approaches across the protocols include: -- Repayment approaches at the end of the transaction, where some pull the principal plus the fee from the loan receiver, and others where the loan receiver needs to manually return the principal and the fee to the lender. -- Some lenders offer the ability to repay the loan using a token that is different to what was originally borrowed, which can reduce the overall complexity of the flash transaction and gas fees. -- Some lenders offer a single entry point into the protocol regardless of whether you're buying, selling, depositing or chaining them together as a flash loan, whereas other protocols offer discrete entry points. -- Some lenders allow to flash mint any amount of their native token without charging a fee, effectively allowing flash loans bounded by computational constraints instead of asset ownership constraints. - -## Specification - -A flash lending feature integrates two smart contracts using a callback pattern. These are called the LENDER and the RECEIVER in this EIP. - -### Lender Specification - -A `lender` MUST implement the IERC3156FlashLender interface. -``` -pragma solidity ^0.7.0 || ^0.8.0; -import "./IERC3156FlashBorrower.sol"; - - -interface IERC3156FlashLender { - - /** - * @dev The amount of currency available to be lent. - * @param token The loan currency. - * @return The amount of `token` that can be borrowed. - */ - function maxFlashLoan( - address token - ) external view returns (uint256); - - /** - * @dev The fee to be charged for a given loan. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function flashFee( - address token, - uint256 amount - ) external view returns (uint256); - - /** - * @dev Initiate a flash loan. - * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - */ - function flashLoan( - IERC3156FlashBorrower receiver, - address token, - uint256 amount, - bytes calldata data - ) external returns (bool); -} -``` - -The `maxFlashLoan` function MUST return the maximum loan possible for `token`. If a `token` is not currently supported `maxFlashLoan` MUST return 0, instead of reverting. - -The `flashFee` function MUST return the fee charged for a loan of `amount` `token`. If the token is not supported `flashFee` MUST revert. - -The `flashLoan` function MUST include a callback to the `onFlashLoan` function in a `IERC3156FlashBorrower` contract. - -``` -function flashLoan( - IERC3156FlashBorrower receiver, - address token, - uint256 amount, - bytes calldata data -) external returns (bool) { - ... - require( - receiver.onFlashLoan(msg.sender, token, amount, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan"), - "IERC3156: Callback failed" - ); - ... -} -``` - -The `flashLoan` function MUST transfer `amount` of `token` to `receiver` before the callback to the receiver. - -The `flashLoan` function MUST include `msg.sender` as the `initiator` to `onFlashLoan`. - -The `flashLoan` function MUST NOT modify the `token`, `amount` and `data` parameter received, and MUST pass them on to `onFlashLoan`. - -The `flashLoan` function MUST include a `fee` argument to `onFlashLoan` with the fee to pay for the loan on top of the principal, ensuring that `fee == flashFee(token, amount)`. - -The `lender` MUST verify that the `onFlashLoan` callback returns the keccak256 hash of "ERC3156FlashBorrower.onFlashLoan". - -After the callback, the `flashLoan` function MUST take the `amount + fee` `token` from the `receiver`, or revert if this is not successful. - -If successful, `flashLoan` MUST return `true`. - -### Receiver Specification - -A `receiver` of flash loans MUST implement the IERC3156FlashBorrower interface: - -``` -pragma solidity ^0.7.0 || ^0.8.0; - - -interface IERC3156FlashBorrower { - - /** - * @dev Receive a flash loan. - * @param initiator The initiator of the loan. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @param fee The additional amount of tokens to repay. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan" - */ - function onFlashLoan( - address initiator, - address token, - uint256 amount, - uint256 fee, - bytes calldata data - ) external returns (bytes32); -} -``` - -For the transaction to not revert, `receiver` MUST approve `amount + fee` of `token` to be taken by `msg.sender` before the end of `onFlashLoan`. - -If successful, `onFlashLoan` MUST return the keccak256 hash of "ERC3156FlashBorrower.onFlashLoan". - -## Rationale - -The interfaces described in this ERC have been chosen as to cover the known flash lending use cases, while allowing for safe and gas efficient implementations. - -`flashFee` reverts on unsupported tokens, because returning a numerical value would be incorrect. - -`flashLoan` has been chosen as a function name as descriptive enough, unlikely to clash with other functions in the lender, and including both the use cases in which the tokens lent are held or minted by the lender. - -`receiver` is taken as a parameter to allow flexibility on the implementation of separate loan initiators and receivers. - -Existing flash lenders all provide flash loans of several token types from the same contract. Providing a `token` parameter in both the `flashLoan` and `onFlashLoan` functions matches closely the observed functionality. - -A `bytes calldata data` parameter is included for the caller to pass arbitrary information to the `receiver`, without impacting the utility of the `flashLoan` standard. - -`onFlashLoan` has been chosen as a function name as descriptive enough, unlikely to clash with other functions in the `receiver`, and following the `onAction` naming pattern used as well in EIP-667. - -A `initiator` will often be required in the `onFlashLoan` function, which the lender knows as `msg.sender`. An alternative implementation which would embed the `initiator` in the `data` parameter by the caller would require an additional mechanism for the receiver to verify its accuracy, and is not advisable. - -The `amount` will be required in the `onFlashLoan` function, which the lender took as a parameter. An alternative implementation which would embed the `amount` in the `data` parameter by the caller would require an additional mechanism for the receiver to verify its accuracy, and is not advisable. - -A `fee` will often be calculated in the `flashLoan` function, which the `receiver` must be aware of for repayment. Passing the `fee` as a parameter instead of appended to `data` is simple and effective. - -The `amount + fee` are pulled from the `receiver` to allow the `lender` to implement other features that depend on using `transferFrom`, without having to lock them for the duration of a flash loan. An alternative implementation where the repayment is transferred to the `lender` is also possible, but would need all other features in the lender to be also based in using `transfer` instead of `transferFrom`. Given the lower complexity and prevalence of a "pull" architecture over a "push" architecture, "pull" was chosen. - -## Backwards Compatibility - -No backwards compatibility issues identified. - -## Implementation - -### Flash Borrower Reference Implementation - -``` -pragma solidity ^0.8.0; - -import "./interfaces/IERC20.sol"; -import "./interfaces/IERC3156FlashBorrower.sol"; -import "./interfaces/IERC3156FlashLender.sol"; - - -contract FlashBorrower is IERC3156FlashBorrower { - enum Action {NORMAL, OTHER} - - IERC3156FlashLender lender; - - constructor ( - IERC3156FlashLender lender_ - ) { - lender = lender_; - } - - /// @dev ERC-3156 Flash loan callback - function onFlashLoan( - address initiator, - address token, - uint256 amount, - uint256 fee, - bytes calldata data - ) external override returns(bytes32) { - require( - msg.sender == address(lender), - "FlashBorrower: Untrusted lender" - ); - require( - initiator == address(this), - "FlashBorrower: Untrusted loan initiator" - ); - (Action action) = abi.decode(data, (Action)); - if (action == Action.NORMAL) { - // do one thing - } else if (action == Action.OTHER) { - // do another - } - return keccak256("ERC3156FlashBorrower.onFlashLoan"); - } - - /// @dev Initiate a flash loan - function flashBorrow( - address token, - uint256 amount - ) public { - bytes memory data = abi.encode(Action.NORMAL); - uint256 _allowance = IERC20(token).allowance(address(this), address(lender)); - uint256 _fee = lender.flashFee(token, amount); - uint256 _repayment = amount + _fee; - IERC20(token).approve(address(lender), _allowance + _repayment); - lender.flashLoan(this, token, amount, data); - } -} -``` - -### Flash Mint Reference Implementation - -``` -pragma solidity ^0.8.0; - -import "../ERC20.sol"; -import "../interfaces/IERC20.sol"; -import "../interfaces/IERC3156FlashBorrower.sol"; -import "../interfaces/IERC3156FlashLender.sol"; - - -/** - * @author Alberto Cuesta Cañada - * @dev Extension of {ERC20} that allows flash minting. - */ -contract FlashMinter is ERC20, IERC3156FlashLender { - - bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); - uint256 public fee; // 1 == 0.01 %. - - /** - * @param fee_ The percentage of the loan `amount` that needs to be repaid, in addition to `amount`. - */ - constructor ( - string memory name, - string memory symbol, - uint256 fee_ - ) ERC20(name, symbol) { - fee = fee_; - } - - /** - * @dev The amount of currency available to be lent. - * @param token The loan currency. - * @return The amount of `token` that can be borrowed. - */ - function maxFlashLoan( - address token - ) external view override returns (uint256) { - return type(uint256).max - totalSupply(); - } - - /** - * @dev The fee to be charged for a given loan. - * @param token The loan currency. Must match the address of this contract. - * @param amount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function flashFee( - address token, - uint256 amount - ) external view override returns (uint256) { - require( - token == address(this), - "FlashMinter: Unsupported currency" - ); - return _flashFee(token, amount); - } - - /** - * @dev Loan `amount` tokens to `receiver`, and takes it back plus a `flashFee` after the ERC3156 callback. - * @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface. - * @param token The loan currency. Must match the address of this contract. - * @param amount The amount of tokens lent. - * @param data A data parameter to be passed on to the `receiver` for any custom use. - */ - function flashLoan( - IERC3156FlashBorrower receiver, - address token, - uint256 amount, - bytes calldata data - ) external override returns (bool){ - require( - token == address(this), - "FlashMinter: Unsupported currency" - ); - uint256 fee = _flashFee(token, amount); - _mint(address(receiver), amount); - require( - receiver.onFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS, - "FlashMinter: Callback failed" - ); - uint256 _allowance = allowance(address(receiver), address(this)); - require( - _allowance >= (amount + fee), - "FlashMinter: Repay not approved" - ); - _approve(address(receiver), address(this), _allowance - (amount + fee)); - _burn(address(receiver), amount + fee); - return true; - } - - /** - * @dev The fee to be charged for a given loan. Internal function with no checks. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function _flashFee( - address token, - uint256 amount - ) internal view returns (uint256) { - return amount * fee / 10000; - } -} -``` - -### Flash Loan Reference Implementation - -``` -pragma solidity ^0.8.0; - -import "../interfaces/IERC20.sol"; -import "../interfaces/IERC3156FlashBorrower.sol"; -import "../interfaces/IERC3156FlashLender.sol"; - - -/** - * @author Alberto Cuesta Cañada - * @dev Extension of {ERC20} that allows flash lending. - */ -contract FlashLender is IERC3156FlashLender { - - bytes32 public constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); - mapping(address => bool) public supportedTokens; - uint256 public fee; // 1 == 0.01 %. - - - /** - * @param supportedTokens_ Token contracts supported for flash lending. - * @param fee_ The percentage of the loan `amount` that needs to be repaid, in addition to `amount`. - */ - constructor( - address[] memory supportedTokens_, - uint256 fee_ - ) { - for (uint256 i = 0; i < supportedTokens_.length; i++) { - supportedTokens[supportedTokens_[i]] = true; - } - fee = fee_; - } - - /** - * @dev Loan `amount` tokens to `receiver`, and takes it back plus a `flashFee` after the callback. - * @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @param data A data parameter to be passed on to the `receiver` for any custom use. - */ - function flashLoan( - IERC3156FlashBorrower receiver, - address token, - uint256 amount, - bytes calldata data - ) external override returns(bool) { - require( - supportedTokens[token], - "FlashLender: Unsupported currency" - ); - uint256 fee = _flashFee(token, amount); - require( - IERC20(token).transfer(address(receiver), amount), - "FlashLender: Transfer failed" - ); - require( - receiver.onFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS, - "FlashLender: Callback failed" - ); - require( - IERC20(token).transferFrom(address(receiver), address(this), amount + fee), - "FlashLender: Repay failed" - ); - return true; - } - - /** - * @dev The fee to be charged for a given loan. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function flashFee( - address token, - uint256 amount - ) external view override returns (uint256) { - require( - supportedTokens[token], - "FlashLender: Unsupported currency" - ); - return _flashFee(token, amount); - } - - /** - * @dev The fee to be charged for a given loan. Internal function with no checks. - * @param token The loan currency. - * @param amount The amount of tokens lent. - * @return The amount of `token` to be charged for the loan, on top of the returned principal. - */ - function _flashFee( - address token, - uint256 amount - ) internal view returns (uint256) { - return amount * fee / 10000; - } - - /** - * @dev The amount of currency available to be lent. - * @param token The loan currency. - * @return The amount of `token` that can be borrowed. - */ - function maxFlashLoan( - address token - ) external view override returns (uint256) { - return supportedTokens[token] ? IERC20(token).balanceOf(address(this)) : 0; - } -} - -``` - -## Security Considerations - - -### Verification of callback arguments - -The arguments of `onFlashLoan` are expected to reflect the conditions of the flash loan, but cannot be trusted unconditionally. They can be divided in two groups, that require different checks before they can be trusted to be genuine. - -0. No arguments can be assumed to be genuine without some kind of verification. `initiator`, `token` and `amount` refer to a past transaction that might not have happened if the caller of `onFlashLoan` decides to lie. `fee` might be false or calculated incorrectly. `data` might have been manipulated by the caller. -1. To trust that the value of `initiator`, `token`, `amount` and `fee` are genuine a reasonable pattern is to verify that the `onFlashLoan` caller is in a whitelist of verified flash lenders. Since often the caller of `flashLoan` will also be receiving the `onFlashLoan` callback this will be trivial. In all other cases flash lenders will need to be approved if the arguments in `onFlashLoan` are to be trusted. -2. To trust that the value of `data` is genuine, in addition to the check in point 1, it is recommended to verify that the `initiator` belongs to a group of trusted addresses. Trusting the `lender` and the `initiator` is enough to trust that the contents of `data` are genuine. - -### Flash lending security considerations - -#### Automatic approvals -The safest approach is to implement an approval for `amount+fee` before the `flashLoan` is executed. - -Any `receiver` that keeps an approval for a given `lender` needs to include in `onFlashLoan` a mechanism to verify that the initiator is trusted. - -Any `receiver` that includes in `onFlashLoan` the approval for the `lender` to take the `amount + fee` needs to be combined with a mechanism to verify that the initiator is trusted. - -If an unsuspecting contract with a non-reverting fallback function, or an EOA, would approve a `lender` implementing ERC3156, and not immediately use the approval, and if the `lender` would not verify the return value of `onFlashLoan`, then the unsuspecting contract or EOA could be drained of funds up to their allowance or balance limit. This would be executed by an `initiator` calling `flashLoan` on the victim. The flash loan would be executed and repaid, plus any fees, which would be accumulated by the `lender`. For this reason, it is important that the `lender` implements the specification in full and reverts if `onFlashLoan` doesn't return the keccak256 hash for "ERC3156FlashBorrower.onFlashLoan". - -### Flash minting external security considerations - -The typical quantum of tokens involved in flash mint transactions will give rise to new innovative attack vectors. - -#### Example 1 - interest rate attack -If there exists a lending protocol that offers stable interests rates, but it does not have floor/ceiling rate limits and it does not rebalance the fixed rate based on flash-induced liquidity changes, then it could be susceptible to the following scenario: - -FreeLoanAttack.sol -1. Flash mint 1 quintillion STAB -2. Deposit the 1 quintillion STAB + $1.5 million worth of ETH collateral -3. The quantum of your total deposit now pushes the stable interest rate down to 0.00001% stable interest rate -4. Borrow 1 million STAB on 0.00001% stable interest rate based on the 1.5M ETH collateral -5. Withdraw and burn the 1 quint STAB to close the original flash mint -6. You now have a 1 million STAB loan that is practically interest free for perpetuity ($0.10 / year in interest) - -The key takeaway being the obvious need to implement a flat floor/ceiling rate limit and to rebalance the rate based on short term liquidity changes. - -#### Example 2 - arithmetic overflow and underflow -If the flash mint provider does not place any limits on the amount of flash mintable tokens in a transaction, then anyone can flash mint 2^256-1 amount of tokens. - -The protocols on the receiving end of the flash mints will need to ensure their contracts can handle this, either by using a compiler that embeds overflow protection in the smart contract bytecode, or by setting explicit checks. - -### Flash minting internal security considerations - -The coupling of flash minting with business specific features in the same platform can easily lead to unintended consequences. - -#### Example - Treasury draining -Assume a smart contract that flash lends its native token. The same smart contract borrows from a third party when users burn the native token. This pattern would be used to aggregate in the smart contract the collateralized debt of several users into a single account in the third party. The flash mint could be used to cause the lender to borrow to its limit, and then pushing interest rates in the underlying lender, liquidate the flash lender: -1. Flash mint from `lender` a very large amount of FOO. -2. Redeem FOO for BAR, causing `lender` to borrow from `underwriter` all the way to its borrowing limit. -3. Trigger a debt rate increase in `underwriter`, making `lender` undercollateralized. -4. Liquidate the `lender` for profit. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3156.md diff --git a/EIPS/eip-3224.md b/EIPS/eip-3224.md index 0d3ab96f0c6c50..a1ee4ca06f6bb7 100644 --- a/EIPS/eip-3224.md +++ b/EIPS/eip-3224.md @@ -1,442 +1 @@ ---- -eip: 3224 -title: Described Data -description: Contract method to compute human-readable descriptions for signable data. -author: Richard Moore (@ricmoo), Nick Johnson (@arachnid) -discussions-to: https://github.com/ethereum/EIPs/issues/3225 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-01-23 -requires: 191 ---- - - -## Abstract - -Human-readable descriptions for machine executable operations, -described in higher level machine readable data, so that wallets -can provide meaningful feedback to the user describing the -action the user is about to perform. - - -## Motivation - -When using an Ethereum Wallet (e.g. MetaMask, Clef, Hardware -Wallets) users must accept and authorize signing messages or -sending transactions. - -Due to the complexity of Ethereum transactions, wallets are very -limitd in their ability to provide insight into the contents of -transactions user are approving; outside special-cased support -for common transactions such as ERC20 transfers, this often amounts -to asking the user to sign an opaque blob of binary data. - -This EIP presents a method for dapp developers to enable a more -comfortable user experience by providing wallets with a means -to generate a better description about what the contract claims -will happen. - -It does not address malicious contracts which wish to lie, it -only addresses honest contracts that want to make their user's -life better. We believe that this is a reasonable security model, -as transaction descriptions can be audited at the same time as -contract code, allowing auditors and code reviewers to check that -transaction descriptions are accurate as part of their review. - - -## Specification - -The **description string** and **described data** are generated -simultaneously by evaluating the contract -(i.e. the **describer**), passing the **describer inputs** to the -method: - -```solidity -function eipXXXDescribe(bytes describer_inputs) view returns (string description_string, bytes described_data); -``` - -The method must be executable in a static context, (i.e. any -side effects, such as logX, sstore, etc.), including through -indirect calls may be ignored. - -During evaluation, the `ADDRESS` (i.e. `to`), `CALLER` -(i.e. `from`), `VALUE`, and `GASPRICE` must be the same as the -values for the transaction being described, so that the -code generating the description can rely on them. For signing -**described messages**, `VALUE` should always be 0. - -When executing the bytecode, best efforts should be made to -ensure `BLOCKHASH`, `NUMBER`, `TIMESTAMP` and `DIFFICULTY` -match the `"latest"` block. The `COINBASE` should be the zero -address. - -The method may revert, in which case the signing must be aborted. - - -### New JSON-RPC Methods - -Clients which manage private keys should expose additional -methods for interacting with the related accounts. - -If an user interface is not present or expected for any other -account-based operations, the description strings should be -ignored and the described data used directly. - -These JSON-RPC methods will also be implemented in standard -Ethereum libraries, so the JSON-RPC description is meant more -of a canonical way to describe them. - - -### Signing Described Messages - -```solidity -eth_signDescribedMessage(address, describer, describerInput) -// Result: { -// description: "text/plain;Hello World", -// data: "0x...", // described data -// signature: "0x..." -// } -``` - -Compute the **description string** and **described data** by -evaluating the call to **describer**, with the -**describerInput** passed to the ABI encoded call to -`eipXXXDescription(bytes)`. The `VALUE` during execution must -be 0. - -If the wallet contains a user interface for accepting or -denying signing a message, it should present the description -string to the user. Optionally, a wallet may wish to -additionally provide a way to examine the described data. - -If accepted, the computed **described data** is signed -according to [EIP-191](./eip-191.md), with the *version -byte* of `0x00` and the *version specific data* of describer -address. - -That is: - -``` -0x19 0x00 DESCRIBER_ADDRESS 0xDESCRIBED_DATA -``` - -The returned result includes the **described data**, allowing -dapps that use parameters computed in the contract to be -available. - -### Sending Described Transactions - -```solidity -eth_sendDescribedTransaction(address, { - to: "0x...", - value: 1234, - nonce: 42, - gas: 42000, - gasPrice: 9000000000, - describerInput: "0x1234...", -}) -// Result: { -// description: "text/plain;Hello World", -// transaction: "0x...", // serialized signed transaction -// } -``` - -Compute the **description string** and **described data** by -evaluating the call to the **describer** `to`, with the -**describerInput** passed to the ABI encoded call to -`eipXXXDescription(bytes)`. - -If the wallet contains a user interface for accepting or -denying a transaction, it should present the description string -along with fee and value information. Optionally, a wallet may -wish to additionally provide a way to further examine the -transaction. - -If accepted, the transaction data is set to the computed -**described data**, the derived transaction is signed and sent, -and the **description string** and serialized signed -transaction is returned. - - -### Signing Described Transaction - -```solidity -eth_signDescribedTransaction(address, { - to: "0x...", - value: 1234, - nonce: 42, - gas: 42000, - gasPrice: 9000000000, - describerInput: "0x1234...", -}) -// Result: { -// description: "text/plain;Hello World", -// transaction: "0x...", // serialized signed transaction -// } -``` - -Compute the **description string** and **described data** by -evaluating the call to the **describer** `to`, with the -**describerInput** passed to the ABI encoded call to -`eipXXXDescription(bytes)`. - -If the wallet contains a user interface for accepting or -denying a transaction, it should present the description string -along with fee and value information. Optionally, a wallet may -wish to additionally provide a way to further examine the -transaction. - -If accepted, the transaction data is set to the computed -**described data**, the derived transaction is signed (and not -sent) and the **description string** and serialized signed -transaction is returned. - -### Description Strings - -A **description string** must begin with a mime-type followed -by a semi-colon (`;`). This EIP specifies only the `text/plain` -mime-type, but future EIPs may specify additional types to -enable more rich processing, such as `text/markdown` so that -addresses can be linkable within clients or to enable -multi-locale options, similar to multipart/form-data. - - -## Rationale - -### Meta Description - -There have been many attempts to solve this problem, many of -which attempt to examine the encoded transaction data or -message data directly. - -In many cases, the information that would be necessary for a -meaningful description is not present in the final encoded -transaction data or message data. - -Instead this EIP uses an indirect description of the data. - -For example, the `commit(bytes32)` method of ENS places a -commitement **hash** on-chain. The hash contains the -**blinded** name and address; since the name is blinded, the -encoded data (i.e. the hash) no longer contains the original -values and is insufficient to access the necessary values to -be included in a description. - -By instead describing the commitment indirectly (with the -original information intact: NAME, ADDRESS and SECRET) a -meaningful description can be computed (e.g. "commit to NAME for ADDRESS (with SECRET)") -and the matching data can be computed (i.e. `commit(hash(name, owner, secret))`). - -### Entangling the Contract Address - -To prevent data being signed from one contract being used -against another, the contract address is entanlged into -both the transaction (implicitly via the `to` field) and -in messages by the EIP-191 versions specific data. - -The use of the zero address is reserved. - -### Alternatives - -- NatSpec and company are a class of more complex languages that attempt to describe the encoded data directly. Because of the language complexity they often end up being quite large requiring entire runtime environments with ample processing power and memory, as well as additional sandboxing to reduce security concerns. One goal of this is to reduce the complexity to something that could execute on hardware wallets and other simple wallets. These also describe the data directly, which in many cases (such as blinded data), cannot adequately describe the data at all - -- Custom Languages; due to the complexity of Ethereum transactions, any language used would require a lot of expressiveness and re-inventing the wheel. The EVM already exists (it may not be ideal), but it is there and can handle everything necessary. - -- Format Strings (e.g. Trustless Signing UI Protocol; format strings can only operate on the class of regular languages, which in many cases is insufficient to describe an Ethereum transaction. This was an issue quite often during early attempts at solving this problem. - -- The signTypedData [EIP-712](./eip-712.md) has many parallels to what this EIP aims to solve - -- @TODO: More - - -## Backwards Compatibility - -All signatures for messages are generated using [EIP-191](./eip-191.md) -which had a previously compatible version byte of `0x00`, so -there should be no concerns with backwards compatibility. - - -## Test Cases - -All test cases operate against the published and verified contracts: - -- Formatter: Ropsten @ 0x7a89c0521604008c93c97aa76950198bca73d933 -- TestFormatter: Ropsten @ 0xab3045aa85cbcabb06ed3f3fe968fa5457727270 - -The private key used for signing messages and transactions is: - -``` -privateKey = "0x6283185307179586476925286766559005768394338798750211641949889184" -``` - - -### Messages - -**Example: login with signed message** - -- sends selector login() -- received data with selector doLogin(bytes32 timestamp) - -``` -Input: - Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270 - Describer Input: 0xb34e97e800000000000000000000000000000000000000000000000000000000 - i.e. encode( - [ "bytes4" ], - [ SEL("login()") ] - ) - -Output: - Description: text/plain;Log into ethereum.org? - Data: 0x14629d78000000000000000000000000000000000000000000000000000000006010d607 - i.e. encodeWithSelector("doLogin(bytes32)", "0x000000000000000000000000000000000000000000000000000000006010d607" ] - -Signing: - Preimage: 0x1900ab3045aa85cbcabb06ed3f3fe968fa545772727014629d78000000000000000000000000000000000000000000000000000000006010d607 - Signature: 0x8b9def29343c85797a580c5cd3607c06e78a53351219f9ba706b9985c1a3c91e702bf678e07f5daf5ef48b3e3cc581202de233904b72cf2c4f7d714ce92075b21c -``` - -### Transactions - -All transaction test cases use the ropsten network (chainId: 3) -and for all unspecified properties use 0. - -**Example: ERC-20 transfer** - -``` -Input: - Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270 - Describer Input: 0xa9059cbb000000000000000000000000000000000000000000000000000000000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000002b992b75cbeb6000 - i.e. encode( - [ "bytes4", "address", "uint"], - [ SEL("transfer(address,uint256)"), "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18 ] - ) -Output: - Description: text/plain;Send 3.14159 TOKN to "ricmoose.eth" (0x8ba1f109551bD432803012645Ac136ddd64DBA72)? - Described Data: 0xa9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72 - i.e. encodeWithSelector("transfer(address,uint256)", "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18) - -Signing: - Signed Transaction: 0xf8a280808094ab3045aa85cbcabb06ed3f3fe968fa545772727080b844a9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba7229a0f33ea492d326ac32d9b7ae203c61bf7cf0ac576fb0cf8be8e4c63dc89c90de12a06c8efb28aaf3b70c032b3bd1edfc664578c49f040cf749bb19b000da56507fb2 -``` - -**Example: ERC-20 approve** - -``` -Input: - Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270 - Describer Input: 0x095ea7b3000000000000000000000000000000000000000000000000000000000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000002b992b75cbeb6000 - i.e. encode( - [ "bytes4", "address", "uint"], - [ SEL("approve(address,uint256)"), "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18 ] - ) - -Output: - Description: text/plain;Approve "ricmoose.eth" (0x8ba1f109551bD432803012645Ac136ddd64DBA72) to manage 3.14159 TOKN tokens? - Described Data: 0xa9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72 - i.e. encodeWithSelector("approve(address,uint256)", "0x8ba1f109551bD432803012645Ac136ddd64DBA72", 3.14159e18) - -Signing: - Signed Transaction: 0xf8a280808094ab3045aa85cbcabb06ed3f3fe968fa545772727080b844a9059cbb0000000000000000000000000000000000000000000000002b992b75cbeb60000000000000000000000000008ba1f109551bd432803012645ac136ddd64dba7229a0f33ea492d326ac32d9b7ae203c61bf7cf0ac576fb0cf8be8e4c63dc89c90de12a06c8efb28aaf3b70c032b3bd1edfc664578c49f040cf749bb19b000da56507fb2 -``` - -**Example: ENS commit** - -``` -Input: - Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270 - Describer Input: 0x0f0e373f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000e31f43c1d823afaa67a8c5fbb8348176d225a79e65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b00000000000000000000000000000000000000000000000000000000000000087269636d6f6f7365000000000000000000000000000000000000000000000000 - i.e. encode( - [ "bytes4", "string", "address", "bytes32"], - [ SEL("commit(string,address,bytes32)"), "ricmoose", "0xE31f43C1d823AfAA67A8C5fbB8348176d225A79e", "0x65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b" ] - ) - -Output: - Description: text/plain;Commit to the ENS name "ricmoose.eth" for 0xE31f43C1d823AfAA67A8C5fbB8348176d225A79e? - Described Data: 0xf14fcbc8e4a4f2bb818545497be34c7ab30e6e87e0001df4ba82e7c8b3f224fbaf255b91 - i.e. encodeWithSelector("commit(bytes32)", makeCommitment("ricmoose", "0xE31f43C1d823AfAA67A8C5fbB8348176d225A79e", "0x65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b")) - -Signing: - Signed Transaction: 0xf88180808094ab3045aa85cbcabb06ed3f3fe968fa545772727080a4f14fcbc8e4a4f2bb818545497be34c7ab30e6e87e0001df4ba82e7c8b3f224fbaf255b912aa0a62b41d1ebda584fe84cf8a05f61b429fe4ec361e13c17f30a23281106b38a8da00bcdd896fe758d8f0cfac46445a48f76f5e9fe27790d67c51412cb98a12a0844 -``` - -**Example: WETH mint()** - -``` -Input: - Address: 0xab3045AA85cBCaBb06eD3F3FE968fA5457727270 - Describer Input: 0x1249c58b00000000000000000000000000000000000000000000000000000000 - i.e. encode( - [ "bytes4" ], - [ SEL("mint()") ] - ) - Value: 1.23 ether - -Output: - Description: text/plain;Mint 1.23 WETH (spending 1.23 ether)? - Described Data: 0x1249c58b - i.e. encodeWithSelector("mint()") - -Signing: - Signed Transaction: 0xf86980808094ab3045aa85cbcabb06ed3f3fe968fa5457727270881111d67bb1bb0000841249c58b29a012df802e1394a97caab23c15c3a8c931668df4b2d6d604ca23f3f6b836d0aafca0071a2aebef6a9848616b4d618912f2003fb4babde3dba451b5246f866281a654 -``` - -## Reference Implementation - -@TODO (consider adding it as one or more files in `../assets/eip-####/`) - -I will add examples in Solidity and JavaScript. - - -## Security Considerations - -### Escaping Text - -Wallets must be careful when displaying text provided by -contracts and proper efforts must be taken to sanitize -it, for example, be sure to consider: - -- HTML could be embedded to attempt to trick web-based wallets into executing code using the script tag (possibly uploading any private keys to a server) -- In general, extreme care must be used when rendering HTML; consider the ENS names `not-ricmoo.eth` or ` ricmoo.eth`, which if rendered without care would appear as `ricmoo.eth`, which it is not -- Other marks which require escaping could be included, such as quotes (`"`), formatting (`\n` (new line), `\f` (form feed), `\t` (tab), any of many non-standard whitespaces), back-slassh (`\`) -- UTF-8 has had bugs in the past which could allow arbitrary code execution and crashing renderers; consider using the UTF-8 replacement character (or *something*) for code-points outside common planes or common sub-sets within planes -- Homoglyphs attacks -- Right-to-left marks may affect rendering -- Many other things, deplnding on your environment - -### Distinguished Signed Data - -Applications implementing this EIP to sign message data should -ensure there are no collisions within the data which could -result in ambiguously signed data. - -@TODO: Expand on this; compare packed data to ABI encoded data? - -### Enumeration - -If an abort occurs during signing, the response from this call -should match the response from a declined signing request; -otherwise this could be used for enumeration attacks, etc. A -random interactive-scale delay should also be added, otherwise -a < 10ms response could be interpreted as an error. - -### Replayablility - -Transactions contain an explicit nonce, but signed messages do -not. - -For many purposes, such as signing in, a nonce could be -injected (using block.timestamp) into the data. The log in -service can verify this is a recent timestamp. The timestamp -may or may not be omitted from the description string in this -case, as it it largely useful internally only. - -In general, when signing messages a nonce often makes sense to -include to prevent the same signed data from being used in the -future. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3224.md diff --git a/EIPS/eip-3234.md b/EIPS/eip-3234.md index df9e3864da9a96..da020810984d26 100644 --- a/EIPS/eip-3234.md +++ b/EIPS/eip-3234.md @@ -1,226 +1 @@ ---- -eip: 3234 -title: Batch Flash Loans -author: Alberto Cuesta Cañada (@albertocuestacanada), Fiona Kobayashi (@fifikobayashi), fubuloubu (@fubuloubu), Austin Williams (@onewayfunction) -discussions-to: https://ethereum-magicians.org/t/erc-3234-batch-flash-loans/5271 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-01-31 ---- - -## Simple Summary - -This ERC provides standard interfaces and processes for multiple-asset flash loans. - -## Motivation - -Flash loans of multiple assets, or batch flash loans, are a common offering of flash lenders, and have a strong use case in the simultaneous refinance of several positions between platforms. At the same time, batch flash loans are more complicated to use than single asset flash loans (ER3156). This divergence of use cases and user profiles calls for independent, but consistent, standards for single asset flash loans and batch flash loans. - - -## Specification - -A batch flash lending feature integrates two smart contracts using a callback pattern. These are called the LENDER and the RECEIVER in this EIP. - -### Lender Specification - -A `lender` MUST implement the IERC3234BatchFlashLender interface. -``` -pragma solidity ^0.7.0 || ^0.8.0; -import "./IERC3234BatchFlashBorrower.sol"; - - -interface IERC3234BatchFlashLender { - - /** - * @dev The amount of currency available to be lended. - * @param tokens The currency for each loan in the batch. - * @return The maximum amount that can be borrowed for each loan in the batch. - */ - function maxFlashLoan( - address[] calldata tokens - ) external view returns (uint256[]); - - /** - * @dev The fees to be charged for a given batch loan. - * @param tokens The loan currencies. - * @param amounts The amounts of tokens lent. - * @return The amount of each `token` to be charged for each loan, on top of the returned principal. - */ - function flashFee( - address[] calldata tokens, - uint256[] calldata amounts - ) external view returns (uint256[]); - - /** - * @dev Initiate a batch flash loan. - * @param receiver The receiver of the tokens in the loan, and the receiver of the callback. - * @param tokens The loan currencies. - * @param amounts The amount of tokens lent. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - */ - function batchFlashLoan( - IERC3234BatchFlashBorrower receiver, - address[] calldata tokens, - uint256[] calldata amounts, - bytes[] calldata data - ) external returns (bool); -} -``` - -The `maxFlashLoan` function MUST return the maximum loan possible for each `token`. If a `token` is not currently supported `maxFlashLoan` MUST return 0, instead of reverting. - -The `flashFee` function MUST return the fees charged for each loan of `amount` `token`. If a token is not supported `flashFee` MUST revert. - -The `batchFlashLoan` function MUST include a callback to the `onBatchFlashLoan` function in a `IERC3234BatchFlashBorrower` contract. - -``` -function batchFlashLoan( - IERC3234BatchFlashBorrower receiver, - address[] calldata tokens, - uint256[] calldata amounts, - bytes calldata data -) external returns (bool) { - ... - require( - receiver.onBatchFlashLoan( - msg.sender, - tokens, - amounts, - fees, - data - ) == keccak256("ERC3234BatchFlashBorrower.onBatchFlashLoan"), - "IERC3234: Callback failed" - ); - ... -} -``` - -The `batchFlashLoan` function MUST transfer `amounts[i]` of each `tokens[i]` to `receiver` before the callback to the borrower. - -The `batchFlashLoan` function MUST include `msg.sender` as the `initiator` to `onBatchFlashLoan`. - -The `batchFlashLoan` function MUST NOT modify the `tokens`, `amounts` and `data` parameters received, and MUST pass them on to `onBatchFlashLoan`. - -The `lender` MUST verify that the `onBatchFlashLoan` callback returns the keccak256 hash of "ERC3234BatchFlashBorrower.onBatchFlashLoan". - -The `batchFlashLoan` function MUST include a `fees` argument to `onBatchFlashLoan` with the fee to pay for each individual `token` and `amount` lent, ensuring that `fees[i] == flashFee(tokens[i], amounts[i])`. - -After the callback, for each `token` in `tokens`, the `batchFlashLoan` function MUST take the `amounts[i] + fees[i]` of `tokens[i]` from the `receiver`, or revert if this is not successful. - -If successful, `batchFlashLoan` MUST return `true`. - -### Receiver Specification - -A `receiver` of flash loans MUST implement the IERC3234BatchFlashBorrower interface: - -``` -pragma solidity ^0.7.0 || ^0.8.0; - - -interface IERC3234BatchFlashBorrower { - - /** - * @dev Receive a flash loan. - * @param initiator The initiator of the loan. - * @param tokens The loan currency. - * @param amounts The amount of tokens lent. - * @param fees The additional amount of tokens to repay. - * @param data Arbitrary data structure, intended to contain user-defined parameters. - * @return The keccak256 hash of "ERC3234BatchFlashBorrower.onBatchFlashLoan" - */ - function onBatchFlashLoan( - address initiator, - address[] calldata tokens, - uint256[] calldata amounts, - uint256[] calldata fees, - bytes calldata data - ) external returns (bytes32); -} -``` - -For the transaction to not revert, for each `token` in `tokens`, `receiver` MUST approve `amounts[i] + fees[i]` of `tokens[i]` to be taken by `msg.sender` before the end of `onBatchFlashLoan`. - -If successful, `onBatchFlashLoan` MUST return the keccak256 hash of "ERC3156BatchFlashBorrower.onBatchFlashLoan". - -## Rationale - -The interfaces described in this ERC have been chosen as to cover the known flash lending use cases, while allowing for safe and gas efficient implementations. - -`flashFee` reverts on unsupported tokens, because returning a numerical value would be incorrect. - -`batchFlashLoan` has been chosen as a function name as descriptive enough, unlikely to clash with other functions in the lender, and including both the use cases in which the tokens lended are held or minted by the lender. - -`receiver` is taken as a parameter to allow flexibility on the implementation of separate loan initiators and receivers. - -Existing flash lenders (Aave, dYdX and Uniswap) all provide flash loans of several token types from the same contract (LendingPool, SoloMargin and UniswapV2Pair). Providing a `token` parameter in both the `batchFlashLoan` and `onBatchFlashLoan` functions matches closely the observed functionality. - -A `bytes calldata data` parameter is included for the caller to pass arbitrary information to the `receiver`, without impacting the utility of the `batchFlashLoan` standard. - -`onBatchFlashLoan` has been chosen as a function name as descriptive enough, unlikely to clash with other functions in the `receiver`, and following the `onAction` naming pattern used as well in EIP-667. - -An `initiator` will often be required in the `onBatchFlashLoan` function, which the lender knows as `msg.sender`. An alternative implementation which would embed the `initiator` in the `data` parameter by the caller would require an additional mechanism for the receiver to verify its accuracy, and is not advisable. - -The `amounts` will be required in the `onBatchFlashLoan` function, which the lender took as a parameter. An alternative implementation which would embed the `amounts` in the `data` parameter by the caller would require an additional mechanism for the receiver to verify its accuracy, and is not advisable. - -The `fees` will often be calculated in the `batchFlashLoan` function, which the `receiver` must be aware of for repayment. Passing the `fees` as a parameter instead of appended to `data` is simple and effective. - -The `amount + fee` are pulled from the `receiver` to allow the `lender` to implement other features that depend on using `transferFrom`, without having to lock them for the duration of a flash loan. An alternative implementation where the repayment is transferred to the `lender` is also possible, but would need all other features in the lender to be also based in using `transfer` instead of `transferFrom`. Given the lower complexity and prevalence of a "pull" architecture over a "push" architecture, "pull" was chosen. - -## Security Considerations - -### Verification of callback arguments - -The arguments of `onBatchFlashLoan` are expected to reflect the conditions of the flash loan, but cannot be trusted unconditionally. They can be divided in two groups, that require different checks before they can be trusted to be genuine. - -0. No arguments can be assumed to be genuine without some kind of verification. `initiator`, `tokens` and `amounts` refer to a past transaction that might not have happened if the caller of `onBatchFlashLoan` decides to lie. `fees` might be false or calculated incorrectly. `data` might have been manipulated by the caller. -1. To trust that the value of `initiator`, `tokens`, `amounts` and `fees` are genuine a reasonable pattern is to verify that the `onBatchFlashLoan` caller is in a whitelist of verified flash lenders. Since often the caller of `batchFlashLoan` will also be receiving the `onBatchFlashLoan` callback this will be trivial. In all other cases flash lenders will need to be approved if the arguments in `onBatchFlashLoan` are to be trusted. -2. To trust that the value of `data` is genuine, in addition to the check in point 1, it is recommended that the `receiver` verifies that the `initiator` is in some list of trusted addresses. Trusting the `lender` and the `initiator` is enough to trust that the contents of `data` are genuine. - -### Flash lending security considerations - -#### Automatic approvals for untrusted borrowers -The safest approach is to implement an approval for `amount+fee` before the `batchFlashLoan` is executed. - -Including in `onBatchFlashLoan` the approval for the `lender` to take the `amount + fee` needs to be combined with a mechanism to verify that the borrower is trusted, such as those described above. - -If an unsuspecting contract with a non-reverting fallback function, or an EOA, would approve a `lender` implementing ERC3156, and not immediately use the approval, and if the `lender` would not verify the return value of `onBatchFlashLoan`, then the unsuspecting contract or EOA could be drained of funds up to their allowance or balance limit. This would be executed by a `borrower` calling `batchFlashLoan` on the victim. The flash loan would be executed and repaid, plus any fees, which would be accumulated by the `lender`. For this reason, it is important that the `lender` implements the specification in full and reverts if `onBatchFlashLoan` doesn't return the keccak256 hash for "ERC3156FlashBorrower.onBatchFlashLoan". - -### Flash minting external security considerations - -The typical quantum of tokens involved in flash mint transactions will give rise to new innovative attack vectors. - -#### Example 1 - interest rate attack -If there exists a lending protocol that offers stable interests rates, but it does not have floor/ceiling rate limits and it does not rebalance the fixed rate based on flash-induced liquidity changes, then it could be susceptible to the following scenario: - -FreeLoanAttack.sol -1. Flash mint 1 quintillion DAI -2. Deposit the 1 quintillion DAI + $1.5 million worth of ETH collateral -3. The quantum of your total deposit now pushes the stable interest rate down to 0.00001% stable interest rate -4. Borrow 1 million DAI on 0.00001% stable interest rate based on the 1.5M ETH collateral -5. Withdraw and burn the 1 quint DAI to close the original flash mint -6. You now have a 1 million DAI loan that is practically interest free for perpetuity ($0.10 / year in interest) - -The key takeaway being the obvious need to implement a flat floor/ceiling rate limit and to rebalance the rate based on short term liquidity changes. - -#### Example 2 - arithmetic overflow and underflow -If the flash mint provider does not place any limits on the amount of flash mintable tokens in a transaction, then anyone can flash mint 2^256-1 amount of tokens. - -The protocols on the receiving end of the flash mints will need to ensure their contracts can handle this. One obvious way is to leverage OpenZeppelin's SafeMath libraries as a catch-all safety net, however consideration should be given to when it is or isn't used given the gas tradeoffs. - -If you recall there was a series of incidents in 2018 where exchanges such as OKEx, Poloniex, HitBTC and Huobi had to shutdown deposits and withdrawls of ERC20 tokens due to integer overflows within the ERC20 token contracts. - - -### Flash minting internal security considerations - -The coupling of flash minting with business specific features in the same platform can easily lead to unintended consequences. - -#### Example - Treasury draining -In early implementations of the Yield Protocol flash loaned fyDai could be redeemed for Dai, which could be used to liquidate the Yield Protocol CDP vault in MakerDAO: -1. Flash mint a very large amount of fyDai. -2. Redeem for Dai as much fyDai as the Yield Protocol collateral would allow. -3. Trigger a stability rate increase with a call to `jug.drip` which would make the Yield Protocol uncollateralized. -4. Liquidate the Yield Protocol CDP vault in MakerDAO. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3234.md diff --git a/EIPS/eip-3386.md b/EIPS/eip-3386.md index 81558c2b38439f..d956b44bc6de5e 100644 --- a/EIPS/eip-3386.md +++ b/EIPS/eip-3386.md @@ -1,274 +1 @@ ---- -eip: 3386 -title: ERC-721 and ERC-1155 to ERC-20 Wrapper -author: Calvin Koder (@ashrowz) -discussions-to: https://github.com/ethereum/EIPs/issues/3384 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-03-12 -requires: 165 ---- - -## Simple Summary -A standard interface for contracts that create generic ERC-20 tokens which derive from a pool of unique ERC-721/ERC-1155 tokens. - -## Abstract - -This standard outlines a smart contract interface to wrap identifiable tokens with fungible tokens. This allows for derivative [ERC-20](./eip-20.md) tokens to be minted by locking the base [ERC-721](./eip-721.md) non-fungible tokens and [ERC-1155](./eip-1155.md) multi tokens into a pool. The derivative tokens can be burned to redeem base tokens out of the pool. These derivatives have no reference to the unique id of these base tokens, and should have a proportional rate of exchange with the base tokens. As representatives of the base tokens, these generic derivative tokens can be traded and otherwise utilized according to ERC-20, such that the unique identifier of each base token is irrelevant. - -ERC-721 and ERC-1155 tokens are considered valid base, tokens because they have unique identifiers and are transferred according to similar rules. This allows for both ERC-721 NFTs and ERC-1155 Multi-Tokens to be wrapped under a single common interface. - -## Motivation - -The ERC-20 token standard is the most widespread and liquid token standard on Ethereum. ERC-721 and ERC-1155 tokens on the other hand can only be transferred by their individual ids, in whole amounts. Derivative tokens allow for exposure to the base asset while benefiting from contracts which utilize ERC-20 tokens. This allows for the base tokens to be fractionalized, traded and pooled generically on AMMs, collateralized, and be used for any other ERC-20 type contract. Several implementations of this proposal already exist without a common standard. - -Given a fixed exchange rate between base and derivative tokens, the value of the derivative token is proportional to the floor price of the pooled tokens. With the derivative tokens being used in AMMs, there is opportunity for arbitrage between derived token markets and the base NFT markets. By specifying a subset of base tokens which may be pooled, the difference between the lowest and highest value token in the pool may be minimized. This allows for higher value tokens within a larger set to be poolable. Additionally, price calculations using methods such as Dutch auctions, as implemented by NFT20, allow for price discovery of subclasses of base tokens. This allows the provider of a higher value base token to receive a proportionally larger number of derivative tokens than a token worth the floor price would receive. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). - -**Every IWrapper compliant contract must implement the `IWrapper` and `ERC165` interfaces** : - - -```solidity -pragma solidity ^0.8.0; - -/** - @title IWrapper Identifiable Token Wrapper Standard - @dev {Wrapper} refers to any contract implementing this interface. - @dev {Base} refers to any ERC-721 or ERC-1155 contract. It MAY be the {Wrapper}. - @dev {Pool} refers to the contract which holds the {Base} tokens. It MAY be the {Wrapper}. - @dev {Derivative} refers to the ERC-20 contract which is minted/burned by the {Wrapper}. It MAY be the {Wrapper}. - @dev All uses of "single", "batch" refer to the number of token ids. This includes individual ERC-721 tokens by id, and multiple ERC-1155 by id. An ERC-1155 `TransferSingle` event may emit with a `value` greater than `1`, but it is still considered a single token. - @dev All parameters named `_amount`, `_amounts` refer to the `value` parameters in ERC-1155. When using this interface with ERC-721, `_amount` MUST be 1, and `_amounts` MUST be either an empty list or a list of 1 with the same length as `_ids`. -*/ -interface IWrapper /* is ERC165 */ { - /** - * @dev MUST emit when a mint occurs where a single {Base} token is received by the {Pool}. - * The `_from` argument MUST be the address of the account that sent the {Base} token. - * The `_to` argument MUST be the address of the account that received the {Derivative} token(s). - * The `_id` argument MUST be the id of the {Base} token transferred. - * The `_amount` argument MUST be the number of {Base} tokens transferred. - * The `_value` argument MUST be the number of {Derivative} tokens minted. - */ - event MintSingle (address indexed _from, address indexed _to, uint256 _id, uint256 _amount, uint256 _value); - - /** - * @dev MUST emit when a mint occurs where multiple {Base} tokens are received by the {Wrapper}. - * The `_from` argument MUST be the address of the account that sent the {Base} tokens. - * The `_to` argument MUST be the address of the account that received the {Derivative} token(s). - * The `_ids` argument MUST be the list ids of the {Base} tokens transferred. - * The `_amounts` argument MUST be the list of the numbers of {Base} tokens transferred. - * The `_value` argument MUST be the number of {Derivative} tokens minted. - */ - event MintBatch (address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts, uint256 _value); - - /** - * @dev MUST emit when a burn occurs where a single {Base} token is sent by the {Wrapper}. - * The `_from` argument MUST be the address of the account that sent the {Derivative} token(s). - * The `_to` argument MUST be the address of the account that received the {Base} token. - * The `_id` argument MUST be the id of the {Base} token transferred. - * The `_amount` argument MUST be the number of {Base} tokens transferred. - * The `_value` argument MUST be the number of {Derivative} tokens burned. - */ - event BurnSingle (address indexed _from, address indexed _to, uint256 _id, uint256 _amount, uint256 _value); - - /** - * @dev MUST emit when a mint occurs where multiple {Base} tokens are sent by the {Wrapper}. - * The `_from` argument MUST be the address of the account that sent the {Derivative} token(s). - * The `_to` argument MUST be the address of the account that received the {Base} tokens. - * The `_ids` argument MUST be the list of ids of the {Base} tokens transferred. - * The `_amounts` argument MUST be the list of the numbers of {Base} tokens transferred. - * The `_value` argument MUST be the number of {Derivative} tokens burned. - */ - event BurnBatch (address indexed _from, address indexed _to, uint256[] _ids, uint256[] _amounts, uint256 _value); - - /** - * @notice Transfers the {Base} token with `_id` from `msg.sender` to the {Pool} and mints {Derivative} token(s) to `_to`. - * @param _to Target address. - * @param _id Id of the {Base} token. - * @param _amount Amount of the {Base} token. - * - * Emits a {MintSingle} event. - */ - function mint( - address _to, - uint256 _id, - uint256 _amount - ) external; - - /** - * @notice Transfers `_amounts[i]` of the {Base} tokens with `_ids[i]` from `msg.sender` to the {Pool} and mints {Derivative} token(s) to `_to`. - * @param _to Target address. - * @param _ids Ids of the {Base} tokens. - * @param _amounts Amounts of the {Base} tokens. - * - * Emits a {MintBatch} event. - */ - function batchMint( - address _to, - uint256[] calldata _ids, - uint256[] calldata _amounts - ) external; - - /** - * @notice Burns {Derivative} token(s) from `_from` and transfers `_amounts` of some {Base} token from the {Pool} to `_to`. No guarantees are made as to what token is withdrawn. - * @param _from Source address. - * @param _to Target address. - * @param _amount Amount of the {Base} tokens. - * - * Emits either a {BurnSingle} or {BurnBatch} event. - */ - function burn( - address _from, - address _to, - uint256 _amount - ) external; - - /** - * @notice Burns {Derivative} token(s) from `_from` and transfers `_amounts` of some {Base} tokens from the {Pool} to `_to`. No guarantees are made as to what tokens are withdrawn. - * @param _from Source address. - * @param _to Target address. - * @param _amounts Amounts of the {Base} tokens. - * - * Emits either a {BurnSingle} or {BurnBatch} event. - */ - function batchBurn( - address _from, - address _to, - uint256[] calldata _amounts - ) external; - - /** - * @notice Burns {Derivative} token(s) from `_from` and transfers `_amounts[i]` of the {Base} tokens with `_ids[i]` from the {Pool} to `_to`. - * @param _from Source address. - * @param _to Target address. - * @param _id Id of the {Base} token. - * @param _amount Amount of the {Base} token. - * - * Emits either a {BurnSingle} or {BurnBatch} event. - */ - function idBurn( - address _from, - address _to, - uint256 _id, - uint256 _amount - ) external; - - /** - * @notice Burns {Derivative} tokens from `_from` and transfers `_amounts[i]` of the {Base} tokens with `_ids[i]` from the {Pool} to `_to`. - * @param _from Source address. - * @param _to Target address. - * @param _ids Ids of the {Base} tokens. - * @param _amounts Amounts of the {Base} tokens. - * - * Emits either a {BurnSingle} or {BurnBatch} event. - */ - function batchIdBurn( - address _from, - address _to, - uint256[] calldata _ids, - uint256[] calldata _amounts - ) external; -} -``` - -## Rationale - -### Naming - -The ERC-721/ERC-1155 tokens which are pooled are called {Base} tokens. Alternative names include: -- Underlying. -- NFT. However, ERC-1155 tokens may be considered "semi-fungible". - -The ERC-20 tokens which are minted/burned are called {Derivative} tokens. Alternative names include: -- Wrapped. -- Generic. - -The function names `mint` and `burn` are borrowed from the minting and burning extensions to ERC-20. Alternative names include: -- `mint`/`redeem` ([NFTX](https://nftx.org)) -- `deposit`/`withdraw` ([WrappedKitties](https://wrappedkitties.com/)) -- `wrap`/`unwrap` ([MoonCatsWrapped](https://etherscan.io/address/0x7c40c393dc0f283f318791d746d894ddd3693572)) - -The function names `*idBurn` are chosen to reduce confusion on what is being burned. That is, the {Derivative} tokens are burned in order to redeem the id(s). - -The wrapper/pool itself can be called an "Index fund" according to NFTX, or a "DEX" according to [NFT20](https://nft20.io). However, the {NFT20Pair} contract allows for direct NFT-NFT swaps which are out of the scope of this standard. - -### Minting -Minting requires the transfer of the {Base} tokens into the {Pool} in exchange for {Derivative} tokens. The {Base} tokens deposited in this way MUST NOT be transferred again except through the burning functions. This ensures the value of the {Derivative} tokens is representative of the value of the {Base} tokens. - -Alternatively to transferring the {Base} tokens into the {Pool}, the tokens may be locked as collateral in exchange for {Derivative} loans, as proposed in NFTX litepaper, similarly to Maker vaults. This still follows the general minting pattern of removing transferability of the {Base} tokens in exchange for {Derivative} tokens. - -### Burning -Burning requires the transfer of {Base} tokens out of the {Pool} in exchange for burning {Derivative} tokens. The burn functions are distinguished by the quantity and quality of {Base} tokens redeemed. -- For burning without specifying the `id`: `burn`, `batchBurn`. -- For burning with specifying the `id`(s): `idBurn`, `batchIdBurn`. - -By allowing for specific ids to be targeted, higher value {Base} tokens may be selected out of the pool. NFTX proposes an additional fee to be applied for such targeted withdrawals, to offset the desire to drain the {Pool} of {Base} tokens worth more than the floor price. - -### Pricing -Prices should not be necessarily fixed. therefore, Mint/Burn events MUST include the ERC-20 `_value` minted/burned. - -Existing pricing implementations are as follows (measured in base:derivative): -- Equal: Every {Base} costs 1 {Derivative} - - NFTX - - Wrapped Kitties -- Proportional - - NFT20 sets a fixed rate of 100 {Base} tokens per {Derivative} token. -- Variable - - NFT20 also allows for Dutch auctions when minting. - - NFTX proposes an additional fee to be paid when targeting the id of the {Base} token. - -Due to the variety of pricing implementations, the Mint\* and Burn\* events MUST include the number {Derivative} tokens minted/burned. - -### Inheritance -#### ERC-20 -The {Wrapper} MAY inherit from {ERC20}, in order to directly call `super.mint` and `super.burn`. -If the {Wrapper} does not inherit from {ERC20}, the {Derivative} contract MUST be limited such that the {Wrapper} has the sole power to `mint`, `burn`, and otherwise change the supply of tokens. - -#### ERC721Receiver, ERC1155Receiver -If not inheriting from {ERC721Receiver} and/or {ERC1155Receiver}, the pool MUST be limited such that the base tokens can only be transferred via the Wrapper's `mint`, `burn`. - -There exists only one of each ERC-721 token of with a given (address, id) pair. However, ERC-1155 tokens of a given (address, id) may have quantities greater than 1. Accordingly, the meaning of "Single" and "Batch" in each standard varies. In both standards, "single" refers to a single id, and "batch" refers to multiple ids. In ERC-1155, a single id event/function may involve multiple tokens, according to the `value` field. - -In building a common set of events and functions, we must be aware of these differences in implementation. The current implementation treats ERC-721 tokens as a special case where, in reference to the quantity of each {Base} token: -- All parameters named `_amount`, MUST be `1`. -- All parameters named `_amounts` MUST be either an empty list or a list of `1` with the same length as `_ids`. - -This keeps a consistent enumeration of tokens along with ERC-1155. Alternative implementations include: -- A common interface with specialized functions. EX: `mintFromERC721`. -- Separate interfaces for each type. EX: `ERC721Wrapper`, `ERC1155Wrapper`. - -#### ERC721, ERC1155 -The {Wrapper} MAY inherit from {ERC721} and/or {ERC1155} in order to call `super.mint`, directly. This is optional as minting {Base} tokens is not required in this standard. An "Initial NFT Offering" could use this to create a set of {Base} tokens within the contract, and directly distribute {Derivative} tokens. - -If the {Wrapper} does not inherit from {ERC721} or {ERC1155}, it MUST include calls to {IERC721} and {IERC1155} in order to transfer {Base} tokens. - -### Approval -All of the underlying transfer methods are not tied to the {Wrapper}, but rather call the ERC-20/721/1155 transfer methods. Implementations of this standard MUST: -- Either implement {Derivative} transfer approval for burning, and {Base} transfer approval for minting. -- Or check for Approval outside of the {Wrapper} through {IERC721} / {IERC1155} before attempting to execute. - -## Backwards Compatibility -Most existing implementations inherit from ERC-20, using functions `mint` and `burn`. -Events: -- Mint - - WK: DepositKittyAndMintToken - - NFTX: Mint - -- Burn - - WK: BurnTokenAndWithdrawKity - - NFTX: Redeem - -## Reference Implementation -[ERC-3386 Reference Implementation](https://github.com/ashrowz/erc-3386) - -## Security Considerations -Wrapper contracts are RECOMMENDED to inherit from burnable ERC-20 tokens. If they are not, the supply of the {Derivative} tokens MUST be controlled by the Wrapper. Similarly, price implementations MUST ensure that the supply of {Base} tokens is reflected by the {Derivative} tokens. - -With the functions `idBurn`, `idBurns`, users may target the most valuable NFT within the generic lot. If there is a significant difference between tokens values of different ids, the contract SHOULD consider creating specialized pools (NFTX) or pricing (NFT20) to account for this. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3386.md diff --git a/EIPS/eip-3440.md b/EIPS/eip-3440.md index a343038554a60f..0f08ed9fa6e274 100644 --- a/EIPS/eip-3440.md +++ b/EIPS/eip-3440.md @@ -1,300 +1 @@ ---- -eip: 3440 -title: ERC-721 Editions Standard -author: Nathan Ginnever (@nginnever) -discussions-to: https://ethereum-magicians.org/t/eip-3340-nft-editions-standard-extension/6044 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-04-20 -requires: 712, 721 ---- - -## Simple Summary - -This standard addresses an extension to the [ERC-721 specification](./eip-721.md) by allowing signatures on NFTs representing works of art. This provides improved provenance by creating functionality for an artist to designate an original and signed limited-edition prints of their work. - -## Abstract - -ERC-3440 is an ERC-721 extension specifically designed to make NFTs more robust for works of art. This extends the original ERC-721 spec by providing the ability to designate the original and limited-edition prints with a specialized enumeration extension similar to the [original 721 extension](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/extensions/ERC721Enumerable.sol) built-in. The key improvement of this extension is allowing artists to designate the limited nature of their prints and provide a signed piece of data that represents their unique signature to a given token Id, much like an artist would sign a print of their work. - -## Motivation -Currently the link between a NFT and the digital work of art is only enforced in the token metadata stored in the shared `tokenURI` state of a NFT. While the blockchain provides an immutable record of history back to the origin of an NFT, often the origin is not a key that an artist maintains as closely as they would a hand written signature. - -An edition is a printed replica of an original piece of art. ERC-721 is not specifically designed to be used for works of art, such as digital art and music. ERC-721 (NFT) was originally created to handle deeds and other contracts. Eventually ERC-721 evolved into gaming tokens, where metadata hosted by servers may be sufficient. This proposal takes the position that we can create a more tangible link between the NFT, digital art, owner, and artist. By making a concise standard for art, it will be easier for an artist to maintain a connection with the Ethereum blockchain as well as their fans that purchase their tokens. - -The use cases for NFTs have evolved into works of digital art, and there is a need to designate an original NFT and printed editions with signatures in a trustless manner. ERC-721 contracts may or may not be deployed by artists, and currently, the only way to understand that something is uniquely touched by an artist is to display it on 3rd party applications that assume a connection via metadata that exists on servers, external to the blockchain. This proposal helps remove that distance with readily available functionality for artists to sign their work and provides a standard for 3rd party applications to display the uniqueness of a NFT for those that purchase them. The designation of limited-editions combined with immutable signatures, creates a trustlessly enforced link. This signature is accompanied by view functions that allow applications to easily display these signatures and limited-edition prints as evidence of uniqueness by showing that artists specifically used their key to designate the total supply and sign each NFT. - -## Specification -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -ERC-721 compliant contracts MAY implement this ERC for editions to provide a standard method for designating the original and limited-edition prints with signatures from the artist. - -Implementations of ERC-3440 MUST designate which token Id is the original NFT (defaulted to Id 0), and which token Id is a unique replica. The original print SHOULD be token Id number 0 but MAY be assigned to a different Id. The original print MUST only be designated once. The implementation MUST designate a maximum number of minted editions, after which new Ids MUST NOT be printed / minted. - -Artists MAY use the signing feature to sign the original or limited edition prints but this is OPTIONAL. A standard message to sign is RECOMMENDED to be simply a hash of the integer of the token Id. - -Signature messages MUST use the [EIP-712](https://eips.ethereum.org/EIPS/eip-712) standard. - -A contract that is compliant with ERC-3440 shall implement the following abstract contract (referred to as ERC3440.sol): - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -/** - * @dev ERC721 token with editions extension. - */ -abstract contract ERC3440 is ERC721URIStorage { - - // eip-712 - struct EIP712Domain { - string name; - string version; - uint256 chainId; - address verifyingContract; - } - - // Contents of message to be signed - struct Signature { - address verificationAddress; // ensure the artists signs only address(this) for each piece - string artist; - address wallet; - string contents; - } - - // type hashes - bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ); - - bytes32 constant SIGNATURE_TYPEHASH = keccak256( - "Signature(address verifyAddress,string artist,address wallet, string contents)" - ); - - bytes32 public DOMAIN_SEPARATOR; - - // Optional mapping for signatures - mapping (uint256 => bytes) private _signatures; - - // A view to display the artist's address - address public artist; - - // A view to display the total number of prints created - uint public editionSupply = 0; - - // A view to display which ID is the original copy - uint public originalId = 0; - - // A signed token event - event Signed(address indexed from, uint256 indexed tokenId); - - /** - * @dev Sets `artist` as the original artist. - * @param `address _artist` the wallet of the signing artist (TODO consider multiple - * signers and contract signers (non-EOA) - */ - function _designateArtist(address _artist) internal virtual { - require(artist == address(0), "ERC721Extensions: the artist has already been set"); - - // If there is no special designation for the artist, set it. - artist = _artist; - } - - /** - * @dev Sets `tokenId as the original print` as the tokenURI of `tokenId`. - * @param `uint256 tokenId` the nft id of the original print - */ - function _designateOriginal(uint256 _tokenId) internal virtual { - require(msg.sender == artist, "ERC721Extensions: only the artist may designate originals"); - require(_exists(_tokenId), "ERC721Extensions: Original query for nonexistent token"); - require(originalId == 0, "ERC721Extensions: Original print has already been designated as a different Id"); - - // If there is no special designation for the original, set it. - originalId = _tokenId; - } - - - /** - * @dev Sets total number printed editions of the original as the tokenURI of `tokenId`. - * @param `uint256 _maxEditionSupply` max supply - */ - function _setLimitedEditions(uint256 _maxEditionSupply) internal virtual { - require(msg.sender == artist, "ERC721Extensions: only the artist may designate max supply"); - require(editionSupply == 0, "ERC721Extensions: Max number of prints has already been created"); - - // If there is no max supply of prints, set it. Leaving supply at 0 indicates there are no prints of the original - editionSupply = _maxEditionSupply; - } - - /** - * @dev Creates `tokenIds` representing the printed editions. - * @param `string memory _tokenURI` the metadata attached to each nft - */ - function _createEditions(string memory _tokenURI) internal virtual { - require(msg.sender == artist, "ERC721Extensions: only the artist may create prints"); - require(editionSupply > 0, "ERC721Extensions: the edition supply is not set to more than 0"); - for(uint i=0; i < editionSupply; i++) { - _mint(msg.sender, i); - _setTokenURI(i, _tokenURI); - } - } - - /** - * @dev internal hashing utility - * @param `Signature memory _message` the signature message struct to be signed - * the address of this contract is enforced in the hashing - */ - function _hash(Signature memory _message) internal view returns (bytes32) { - return keccak256(abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256(abi.encode( - SIGNATURE_TYPEHASH, - address(this), - _message.artist, - _message.wallet, - _message.contents - )) - )); - } - - /** - * @dev Signs a `tokenId` representing a print. - * @param `uint256 _tokenId` id of the NFT being signed - * @param `Signature memory _message` the signed message - * @param `bytes memory _signature` signature bytes created off-chain - * - * Requirements: - * - * - `tokenId` must exist. - * - * Emits a {Signed} event. - */ - function _signEdition(uint256 _tokenId, Signature memory _message, bytes memory _signature) internal virtual { - require(msg.sender == artist, "ERC721Extensions: only the artist may sign their work"); - require(_signatures[_tokenId].length == 0, "ERC721Extensions: this token is already signed"); - bytes32 digest = hash(_message); - address recovered = ECDSA.recover(digest, _signature); - require(recovered == artist, "ERC721Extensions: artist signature mismatch"); - _signatures[_tokenId] = _signature; - emit Signed(artist, _tokenId); - } - - - /** - * @dev displays a signature from the artist. - * @param `uint256 _tokenId` NFT id to verify isSigned - * @returns `bytes` gets the signature stored on the token - */ - function getSignature(uint256 _tokenId) external view virtual returns (bytes memory) { - require(_signatures[_tokenId].length != 0, "ERC721Extensions: no signature exists for this Id"); - return _signatures[_tokenId]; - } - - /** - * @dev returns `true` if the message is signed by the artist. - * @param `Signature memory _message` the message signed by an artist and published elsewhere - * @param `bytes memory _signature` the signature on the message - * @param `uint _tokenId` id of the token to be verified as being signed - * @returns `bool` true if signed by artist - * The artist may broadcast signature out of band that will verify on the nft - */ - function isSigned(Signature memory _message, bytes memory _signature, uint _tokenId) external view virtual returns (bool) { - bytes32 messageHash = hash(_message); - address _artist = ECDSA.recover(messageHash, _signature); - return (_artist == artist && _equals(_signatures[_tokenId], _signature)); - } - - /** - * @dev Utility function that checks if two `bytes memory` variables are equal. This is done using hashing, - * which is much more gas efficient then comparing each byte individually. - * Equality means that: - * - 'self.length == other.length' - * - For 'n' in '[0, self.length)', 'self[n] == other[n]' - */ - function _equals(bytes memory _self, bytes memory _other) internal pure returns (bool equal) { - if (_self.length != _other.length) { - return false; - } - uint addr; - uint addr2; - uint len = _self.length; - assembly { - addr := add(_self, /*BYTES_HEADER_SIZE*/32) - addr2 := add(_other, /*BYTES_HEADER_SIZE*/32) - } - assembly { - equal := eq(keccak256(addr, len), keccak256(addr2, len)) - } - } -} -``` - -## Rationale - -A major role of NFTs is to display uniqueness in digital art. Provenance is a desired feature of works of art, and this standard will help improve a NFT by providing a better way to verify uniqueness. Taking this extra step by an artist to explicitly sign tokens provides a better connection between the artists and their work on the blockchain. Artists can now retain their private key and sign messages in the future showing that the same signature is present on a unique NFT. - -## Backwards Compatibility - -This proposal combines already available 721 extensions and is backwards compatible with the ERC-721 standard. - -## Test Cases -An example implementation including tests can be found [here](https://github.com/nginnever/NFT-editions). - -## Reference Implementation -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./ERC3440.sol"; - -/** - * @dev ERC721 token with editions extension. - */ -contract ArtToken is ERC3440 { - - /** - * @dev Sets `address artist` as the original artist to the account deploying the NFT. - */ - constructor ( - string memory _name, - string memory _symbol, - uint _numberOfEditions, - string memory tokenURI, - uint _originalId - ) ERC721(_name, _symbol) { - _designateArtist(msg.sender); - _setLimitedEditions(_numberOfEditions); - _createEditions(tokenURI); - _designateOriginal(_originalId); - - DOMAIN_SEPARATOR = keccak256(abi.encode( - EIP712DOMAIN_TYPEHASH, - keccak256(bytes("Artist's Editions")), - keccak256(bytes("1")), - 1, - address(this) - )); - } - - /** - * @dev Signs a `tokenId` representing a print. - */ - function sign(uint256 _tokenId, Signature memory _message, bytes memory _signature) public { - _signEdition(_tokenId, _message, _signature); - } -} - -``` - -## Security Considerations -This extension gives an artist the ability to designate an original edition, set the maximum supply of editions as well as print the editions and uses the `tokenURI` extension to supply a link to the art work. To minimize the risk of an artist changing this value after selling an original piece this function can only happen once. Ensuring that these functions can only happen once provides consistency with uniqueness and verifiability. Due to this, the reference implementation handles these features in the constructor function. An edition may only be signed once, and care should be taken that the edition is signed correctly before release of the token/s. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3440.md diff --git a/EIPS/eip-3448.md b/EIPS/eip-3448.md index e28d15f1ff013b..57fc25bb537086 100644 --- a/EIPS/eip-3448.md +++ b/EIPS/eip-3448.md @@ -1,208 +1 @@ ---- -eip: 3448 -title: MetaProxy Standard -description: A minimal bytecode implementation for creating proxy contracts with immutable metadata attached to the bytecode -author: pinkiebell (@pinkiebell) -discussions-to: https://ethereum-magicians.org/t/erc-3448-metaproxy-factory/5834 -status: Final -type: Standards Track -category: ERC -created: 2021-03-29 ---- - -## Abstract -By standardizing on a known minimal bytecode proxy implementation with support for immutable metadata, this standard allows users and third party tools (e.g. Etherscan) to: -(a) simply discover that a contract will always redirect in a known manner and -(b) depend on the behavior of the code at the destination contract as the behavior of the redirecting contract and -(c) verify/view the attached metadata. - -Tooling can interrogate the bytecode at a redirecting address to determine the location of the code that will run along with the associated metadata - and can depend on representations about that code (verified source, third-party audits, etc). -This implementation forwards all calls via `DELEGATECALL` and any (calldata) input plus the metadata at the end of the bytecode to the implementation contract and then relays the return value back to the caller. -In the case where the implementation reverts, the revert is passed back along with the payload data. - -## Motivation -This standard supports use-cases wherein it is desirable to clone exact contract functionality with different parameters at another address. - -## Specification -The exact bytecode of the MetaProxy contract is: -``` - 20 bytes target contract address - ---------------------------------------- -363d3d373d3d3d3d60368038038091363936013d7300000000000000000000000000000000000000005af43d3d93803e603457fd5bf3 -``` -wherein the bytes at indices 21 - 41 (inclusive) are replaced with the 20 byte address of the master functionality contract. -Additionally, everything after the MetaProxy bytecode can be arbitrary metadata and the last 32 bytes (one word) of the bytecode must indicate the length of the metadata in bytes. - -``` -<54 bytes metaproxy> -``` - -## Rationale -The goals of this effort have been the following: -- a cheap way of storing immutable metadata for each child instead of using storage slots -- inexpensive deployment of clones -- handles error return bubbling for revert messages - -## Backwards Compatibility -There are no backwards compatibility issues. - -## Test Cases -Tested with: -- invocation with no arguments -- invocation with arguments -- invocation with return values -- invocation with revert (confirming reverted payload is transferred) - -A solidity contract with the above test cases can be found [in the EIP asset directory](../assets/eip-3448/MetaProxyTest.sol). - -## Reference Implementation -A reference implementation can be found [in the EIP asset directory](../assets/eip-3448/MetaProxyFactory.sol). - -### Deployment bytecode -A annotated version of the deploy bytecode: -``` -// PUSH1 11; -// CODESIZE; -// SUB; -// DUP1; -// PUSH1 11; -// RETURNDATASIZE; -// CODECOPY; -// RETURNDATASIZE; -// RETURN; -``` - -### MetaProxy -A annotated version of the MetaProxy bytecode: -``` -// copy args -// CALLDATASIZE; calldatasize -// RETURNDATASIZE; 0, calldatasize -// RETURNDATASIZE; 0, 0, calldatasize -// CALLDATACOPY; - -// RETURNDATASIZE; 0 -// RETURNDATASIZE; 0, 0 -// RETURNDATASIZE; 0, 0, 0 -// RETURNDATASIZE; 0, 0, 0, 0 - -// PUSH1 54; 54, 0, 0, 0, 0 -// DUP1; 54, 54, 0, 0, 0, 0 -// CODESIZE; codesize, 54, 54, 0, 0, 0, 0 -// SUB; codesize-54, 54, 0, 0, 0, 0 -// DUP1; codesize-54, codesize-54, 54, 0, 0, 0, 0 -// SWAP2; 54, codesize-54, codesize-54, 0, 0, 0, 0 -// CALLDATASIZE; calldatasize, 54, codesize-54, codesize-54, 0, 0, 0, 0 -// CODECOPY; codesize-54, 0, 0, 0, 0 - -// CALLDATASIZE; calldatasize, codesize-54, 0, 0, 0, 0 -// ADD; calldatasize+codesize-54, 0, 0, 0, 0 -// RETURNDATASIZE; 0, calldatasize+codesize-54, 0, 0, 0, 0 -// PUSH20 0; addr, 0, calldatasize+codesize-54, 0, 0, 0, 0 - zero is replaced with shl(96, address()) -// GAS; gas, addr, 0, calldatasize+codesize-54, 0, 0, 0, 0 -// DELEGATECALL; (gas, addr, 0, calldatasize() + metadata, 0, 0) delegatecall to the target contract; -// -// RETURNDATASIZE; returndatasize, retcode, 0, 0 -// RETURNDATASIZE; returndatasize, returndatasize, retcode, 0, 0 -// SWAP4; 0, returndatasize, retcode, 0, returndatasize -// DUP1; 0, 0, returndatasize, retcode, 0, returndatasize -// RETURNDATACOPY; (0, 0, returndatasize) - Copy everything into memory that the call returned - -// stack = retcode, 0, returndatasize # this is for either revert(0, returndatasize()) or return (0, returndatasize()) - -// PUSH1 _SUCCESS_; push jumpdest of _SUCCESS_ -// JUMPI; jump if delegatecall returned `1` -// REVERT; (0, returndatasize()) if delegatecall returned `0` -// JUMPDEST _SUCCESS_; -// RETURN; (0, returndatasize()) if delegatecall returned non-zero (1) -``` - -### Examples -The following code snippets serve only as suggestions and are not a discrete part of this standard. - -#### Proxy construction with bytes from abi.encode -```solidity -/// @notice MetaProxy construction via abi encoded bytes. -function createFromBytes ( - address a, - uint256 b, - uint256[] calldata c -) external payable returns (address proxy) { - // creates a new proxy where the metadata is the result of abi.encode() - proxy = MetaProxyFactory._metaProxyFromBytes(address(this), abi.encode(a, b, c)); - require(proxy != address(0)); - // optional one-time setup, a constructor() substitute - MyContract(proxy).init{ value: msg.value }(); -} -``` - -#### Proxy construction with bytes from calldata -```solidity -/// @notice MetaProxy construction via calldata. -function createFromCalldata ( - address a, - uint256 b, - uint256[] calldata c -) external payable returns (address proxy) { - // creates a new proxy where the metadata is everything after the 4th byte from calldata. - proxy = MetaProxyFactory._metaProxyFromCalldata(address(this)); - require(proxy != address(0)); - // optional one-time setup, a constructor() substitute - MyContract(proxy).init{ value: msg.value }(); -} -``` - -#### Retrieving the metadata from calldata and abi.decode -```solidity -/// @notice Returns the metadata of this (MetaProxy) contract. -/// Only relevant with contracts created via the MetaProxy standard. -/// @dev This function is aimed to be invoked with- & without a call. -function getMetadataWithoutCall () public pure returns ( - address a, - uint256 b, - uint256[] memory c -) { - bytes memory data; - assembly { - let posOfMetadataSize := sub(calldatasize(), 32) - let size := calldataload(posOfMetadataSize) - let dataPtr := sub(posOfMetadataSize, size) - data := mload(64) - // increment free memory pointer by metadata size + 32 bytes (length) - mstore(64, add(data, add(size, 32))) - mstore(data, size) - let memPtr := add(data, 32) - calldatacopy(memPtr, dataPtr, size) - } - return abi.decode(data, (address, uint256, uint256[])); -} -``` - -#### Retrieving the metadata via a call to self -```solidity -/// @notice Returns the metadata of this (MetaProxy) contract. -/// Only relevant with contracts created via the MetaProxy standard. -/// @dev This function is aimed to to be invoked via a call. -function getMetadataViaCall () public pure returns ( - address a, - uint256 b, - uint256[] memory c -) { - assembly { - let posOfMetadataSize := sub(calldatasize(), 32) - let size := calldataload(posOfMetadataSize) - let dataPtr := sub(posOfMetadataSize, size) - calldatacopy(0, dataPtr, size) - return(0, size) - } -} -``` - -Apart from the examples above, it is also possible to use Solidity Structures or any custom data encoding. - -## Security Considerations -This standard only covers the bytecode implementation and does not include any serious side effects of itself. -The reference implementation only serves as a example. It is highly recommended to research side effects depending on how the functionality is used and implemented in any project. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3448.md diff --git a/EIPS/eip-3450.md b/EIPS/eip-3450.md index 7030877a628f5c..2cbb906b6e4c7f 100644 --- a/EIPS/eip-3450.md +++ b/EIPS/eip-3450.md @@ -1,190 +1 @@ ---- -eip: 3450 -title: Standardized Shamir Secret Sharing Scheme for BIP-39 Mnemonics -author: Daniel Streit (@danielstreit) -discussions-to: https://ethereum-magicians.org/t/erc-3450-standard-for-applying-shamirs-to-bip-39-mnemonics/5844 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-03-29 ---- - -## Simple Summary - -A standardized algorithm for applying Shamir's Secret Sharing Scheme to BIP-39 mnemonics. - -## Abstract - -A standardized approach to splitting a BIP-39 mnemonic into _N_ BIP-39 mnemonics, called shares, so that _T_ shares are required to recover the original mnemonic and no information about the original mnemonic, other than its size, is leaked with less than _T_ shares. - -## Motivation - -We'd like to make it easier for less-technical users to store keys securely. - -Currently, many users use BIP-39 mnemonics to store entropy values underlying their keys. These mnemonics are a single point of failure. If lost, the user may never regain access to the assets locked by the keys. If stolen, a malicious actor can steal the assets. - -Shamir's Secret Sharing Scheme addresses this concern directly. It creates "shares" of the secret, such that a subset can be used to recover the secret, but only if a minimum threshold of shares is reached. Without the minimum, no information about the original secret is leaked. - -One concern with Shamir's Secret Sharing Scheme is there is no canonical, standard implementation. This puts recovery at risk, as tooling may change over time. - -Here, we propose a standardized implementation of Shamir's Secret Sharing Scheme applied specifically to BIP-39 mnemonics, so users can easily create shares of their mnemonic, destroy the original, store the shares appropriately, and confidently recover the original mnemonic at a later date. - -## Specification - -### Shamir's Secret Sharing Scheme - -Shamir's Secret Sharing Scheme is a cryptographic method to split a secret into _N_ unique parts, where any _T_ of them are required to reconstruct the secret. - -First, a polynomial _f_ of degree _T_ − 1 is constructed. Then, each share is a point on the polynomial's curve: an integer _x_, and its corresponding _y_ point _f_(_x_). - -With any set of _T_ shares (or points), the initial polynomial can be recovered using polynomial interpolation. - -When constructing the initial polynomial, the secret is stored as the coefficient of x0 and the rest of the coefficients are randomly generated. - -### BIP-39 Mnemonics - -BIP-39 is a common standard for storing entropy as a list of words. It is easier to work with for human interactions than raw binary or hexadecimal representations of entropy. - -BIP-39 mnemonics encode two pieces of data: the original entropy and a checksum of that entropy. The checksum allows the mnemonic to be validated, ensuring that the user entered it correctly. - -#### Generating the Mnemonic - -The mnemonic must encode entropy in a multiple of 32 bits. With more entropy security is improved but the sentence length increases. We refer to the initial entropy length as ENT. The allowed size of ENT is 128-256 bits. - -First, an initial entropy of ENT bits is generated. A checksum is generated by taking the first `ENT / 32` bits of its SHA256 hash. This checksum is appended to the end of the initial entropy. Next, these concatenated bits are split into groups of 11 bits, each encoding a number from 0-2047, serving as an index into a word list. Finally, we convert these numbers into words and use the joined words as a mnemonic sentence. - -The following table describes the relation between the initial entropy length (ENT), the checksum length (CS), and the length of the generated mnemonic sentence (MS) in words. - -``` -CS = ENT / 32 -MS = (ENT + CS) / 11 - -| ENT | CS | ENT+CS | MS | -+-------+----+--------+------+ -| 128 | 4 | 132 | 12 | -| 160 | 5 | 165 | 15 | -| 192 | 6 | 198 | 18 | -| 224 | 7 | 231 | 21 | -| 256 | 8 | 264 | 24 | -``` - -#### Recovering the Entropy - -The initial entropy can be recovered by reversing the process above. The mnemonic is converted to bits, where each word is converted to 11 bits representing its index in the word list. The entropy portion is defined in the table above, based on the size of the mnemonic. - -#### Word List - -This specification only supports the BIP-39 English word list, but this may be expanded in the future. - -See [word list](../assets/eip-3450/wordlist.txt). - -### Applying Shamir's Scheme to BIP-39 Mnemonics - -To ensure that the shares are valid BIP-39 mnemonics, we: - -1. Convert the target BIP-39 mnemonic to its underlying entropy -2. Apply Shamir's Scheme to the entropy -3. Convert each resulting share's _y_ value to a BIP-39 mnemonic - -By converting to entropy before applying Shamir's Scheme, we omit the checksum from the initial secret, allowing us to calculate a new checksum for each share when converting the share _y_ values to mnemonics, ensuring that they are valid according to BIP-39. - -When applying Shamir's Scheme to the entropy, we apply it separately to each byte of the entropy and GF(256) is used as the underlying finite field. Bytes are interpreted as elements of GF(256) using polynomial representation with operations modulo the Rijndael irreducible polynomial _x_8 + _x_4 + _x_3 + _x_ + 1, following AES. - -### Share Format - -A share represents a point on the curve described by the underlying polynomial used to split the secret. It includes two pieces of data: - -- An ID: the _x_ value of the share -- A BIP-39 mnemonic: the _y_ value of the share represented by a mnemonic - -### Creating Shares - -Inputs: BIP-39 mnemonic, number of shares (_N_), threshold (_T_) - -Output: N Shares, each share including an ID, { _x_ | 0 < _x_ < 256 }, and a BIP-39 mnemonic of the same length as the input one - -1. Check the following conditions: - - 1 < T <= N < 256 - - The mnemonic is valid according to [BIP-39](#generating-the-mnemonic) -2. [Recover the underlying entropy of the mnemonic](#recovering-the-entropy) as a vector of bytes -3. Define values: - - Let _E_ be the byte-vector representation of the mnemonic's entropy - - Let _n_ be the length of _E_ - - Let _coeff1_, ... , _coeffT - 1_ be byte-vectors belonging to GF(256)_n_ generated randomly, independently with uniform distribution from a source suitable for generating cryptographic keys -4. Evaluate the polynomial for each share - - For each _x_ from 1 to _N_, evaluate the polynomial _f(x)_ = _E_ + _coeff1x1_ + ... + _coeffT - 1xT - 1_, where _x_ is the share ID and _f(x)_ is the share value (as a vector of bytes) -5. Using _f(x)_ as the underlying entropy, [generate a mnemonic](#generating-the-mnemonic) for each share -6. Return the ID and mnemonic for each share - -### Recovering the Mnemonic - -To recover the original mnemonic, we interpolate a polynomial _f_ from the given set of shares (or points on the polynomial) and evaluate _f(0)_. - -#### Polynomial Interpolation - -Given a set of _m_ points (_xi_, _yi_), 1 ≤ _i_ ≤ _m_, such that no two _xi_ values equal, there exists a polynomial that assumes the value _yi_ at each point _xi_. The polynomial of lowest degree that satisfies these conditions is uniquely determined and can be obtained using the Lagrange interpolation formula given below. - -Since Shamir's Secret Sharing Scheme is applied separately to each of the _n_ bytes of the shared mnemonic's entropy, we work with _yi_ as a vector of _n_ values, where _yi_[_k_] = _fk_(_xi_), 1 ≤ _k_ ≤ _n_, and _fk_ is the polynomial in the _k_-th instance of the scheme. - -#### Interpolate(_x_, {(_xi_, _yi_), 1 ≤ _i_ ≤ _m_}) - -Input: the desired index _x_, a set of index/value-vector pairs {(_xi_, _y__i_), 1 ≤ _i_ ≤ _m_} ⊆ GF(256) × GF(256)_n_ - -Output: the value-vector (_f_1(_x_), ... , _fn_(_x_)) - -![f_k(x) = \sum_{i=1}^m y_i[k] \prod_{\underset{j \neq i}{j=1}}^m \frac{x - x_j}{x_i - x_j}](../assets/eip-3450/lagrange.gif) - -#### Recover the Mnemonic - -Input: A set of _m_ Shares - -Output: The original mnemonic - -1. [Recover the underlying entropy of each share's mnemonic](#recovering-the-entropy) as a vector of bytes -2. Calculate _E_ = Interpolate(0, [(_x1_, _y1_),...,(_xm_, _ym_)]), where _x_ is the share ID and _y_ is the byte-vector of the share's mnemonic's entropy -3. Using _E_ as the underlying entropy, [generate a mnemonic](#generating-the-mnemonic) and return it - -## Rationale - -### Choice of Field - -The field GF(256) was chosen, because the field arithmetic is easy to implement in any programming language and many implementations are already available since it is used in the AES cipher. Although using GF(256) requires that we convert the mnemonic to its underlying entropy as a byte-vector, this is also easy to implement and many implementations of it exist in a variety of programming languages. - -GF(2048) was also considered. Using GF(2048), we could have applied Shamir's Scheme directly to the mnemonic, using the word indexes as the values. This would have allowed us to avoid converting the mnemonic to its underlying entropy. But, the resulting shares would not have been valid BIP-39 mnemonics - the checksum portion would not be a valid checksum of the entropy. And, working around this would add considerable complexity. - -Another option was GF(2_n_) where _n_ is the size of the entropy in bits. We'd still convert the mnemonic to entropy, but then apply Shamir's Scheme over the entire entropy rather than on a vector of values. The downside of this approach is we'd need a different field for each mnemonic strength along with an associated irreducible polynomial. Additionally, this would require working with very large numbers that can be cumbersome to work with in some languages. - -### Valid Share Mnemonics and Share IDs - -The shares produced by the specification include an ID, in addition to the BIP-39 mnemonic. - -Other options could have encoded the share ID into the mnemonic, simplifying storage - only the mnemonic would need to be stored. - -One possibility would be to store the ID instead of the checksum in the mnemonic. The downside of this approach is that the shares would not be _valid_ BIP-39 mnemonics because the "checksum" section of the mnemonic would not match the "entropy" section. Shares with valid BIP-39 mnemonics are useful because they are indistinguishable from any other. And users could store the ID in a variety of ways that obscure it. - -### Validation on Recovery - -We decided _not_ to include a validation mechanism on recovering the original mnemonic. This leaks less information to a potential attacker. There is no indication they've gotten the requisite number of shares until they've obtained _T_ + 1 shares. - -We could provide recovery validation by replacing one of the random coefficients with a checksum of the original mnemonic. Then, when recovering the original mnemonic and the polynomial, we could validate that the checksum coefficient is the valid checksum of recovered mnemonic. - -## Test Cases - -Coming soon. - -All implementations must be able to: - -- Split and recover each `mnemonic` with the given `numShares` and `threshold`. -- Recover the `mnemonic` from the given `knownShares`. - -## Security Considerations - -The shares produced by the specification include an ID in addition to the BIP-39 mnemonic. This raises two security concerns: - -Users **must** keep this ID in order to recover the original mnemonic. If the ID is lost, or separated from the share mnemonic, it may not be possible to recover the original. (Brute force recovery may or may not be possible depending on how much is known about the number of shares and threshold) - -The additional data may hint to an attacker of the existence of other keys and the scheme under which they are stored. Therefore, the ID should be stored in a way that obscures its use. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3450.md diff --git a/EIPS/eip-3475.md b/EIPS/eip-3475.md index f55b6dc3e3334d..ad109f7a6f3136 100644 --- a/EIPS/eip-3475.md +++ b/EIPS/eip-3475.md @@ -1,445 +1 @@ ---- -eip: 3475 -title: Abstract Storage Bonds -description: Interface for creating tokenized obligations with abstract on-chain metadata storage -author: Yu Liu (@yuliu-debond), Varun Deshpande (@dr-chain), Cedric Ngakam (@drikssy), Dhruv Malik (@dhruvmalik007), Samuel Gwlanold Edoumou (@Edoumou), Toufic Batrice (@toufic0710) -discussions-to: https://ethereum-magicians.org/t/eip-3475-multiple-callable-bonds-standard/8691 -status: Final -type: Standards Track -category: ERC -created: 2021-04-05 -requires: 20, 721, 1155 ---- - -## Abstract - -- This EIP allows the creation of tokenized obligations with abstract on-chain metadata storage. Issuing bonds with multiple redemption data cannot be achieved with existing token standards. - -- This EIP enables each bond class ID to represent a new configurable token type and corresponding to each class, corresponding bond nonces to represent an issuing condition or any other form of data in uint256. Every single nonce of a bond class can have its metadata, supply, and other redemption conditions. - -- Bonds created by this EIP can also be batched for issuance/redemption conditions for efficiency on gas costs and UX side. And finally, bonds created from this standard can be divided and exchanged in a secondary market. - -## Motivation - -Current LP (Liquidity Provider) tokens are simple [EIP-20](./eip-20.md) tokens with no complex data structure. To allow more complex reward and redemption logic to be stored on-chain, we need a new token standard that: - -- Supports multiple token IDs -- Can store on-chain metadata -- Doesn't require a fixed storage pattern -- Is gas-efficient. - -Also Some benefits: - -- This EIP allows the creation of any obligation with the same interface. -- It will enable any 3rd party wallet applications or exchanges to read these tokens' balance and redemption conditions. -- These bonds can also be batched as tradeable instruments. Those instruments can then be divided and exchanged in secondary markets. - -## Specification - -**Definition** - -Bank: an entity that issues, redeems, or burns bonds after getting the necessary amount of liquidity. Generally, a single entity with admin access to the pool. - -**Functions** - -```solidity -pragma solidity ^0.8.0; - -/** -* transferFrom -* @param _from argument is the address of the bond holder whose balance is about to decrease. -* @param _to argument is the address of the bond recipient whose balance is about to increase. -* @param _transactions is the `Transaction[] calldata` (of type ['classId', 'nonceId', '_amountBonds']) structure defined in the rationale section below. -* @dev transferFrom MUST have the `isApprovedFor(_from, _to, _transactions[i].classId)` approval to transfer `_from` address to `_to` address for given classId (i.e for Transaction tuple corresponding to all nonces). -e.g: -* function transferFrom(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, 0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B, [IERC3475.Transaction(1,14,500)]); -* transfer from `_from` address, to `_to` address, `500000000` bonds of type class`1` and nonce `42`. -*/ - -function transferFrom(address _from, address _to, Transaction[] calldata _transactions) external; - -/** -* transferAllowanceFrom -* @dev allows the transfer of only those bond types and nonces being allotted to the _to address using allowance(). -* @param _from is the address of the holder whose balance is about to decrease. -* @param _to is the address of the recipient whose balance is about to increase. -* @param _transactions is the `Transaction[] calldata` structure defined in the section `rationale` below. -* @dev transferAllowanceFrom MUST have the `allowance(_from, msg.sender, _transactions[i].classId, _transactions[i].nonceId)` (where `i` looping for [ 0 ...Transaction.length - 1] ) -e.g: -* function transferAllowanceFrom(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, 0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B, [IERC3475.Transaction(1,14,500)]); -* transfer from `_from` address, to `_to` address, `500000000` bonds of type class`1` and nonce `42`. -*/ - -function transferAllowanceFrom(address _from,address _to, Transaction[] calldata _transactions) public ; - -/** -* issue -* @dev allows issuing any number of bond types (defined by values in Transaction tuple as param) to an address. -* @dev it MUST be issued by a single entity (for instance, a role-based ownable contract that has integration with the liquidity pool of the deposited collateral by `_to` address). -* @param `_to` argument is the address to which the bond will be issued. -* @param `_transactions` is the `Transaction[] calldata` (ie array of issued bond class, bond nonce and amount of bonds to be issued). -* @dev transferAllowanceFrom MUST have the `allowance(_from, msg.sender, _transactions[i].classId, _transactions[i].nonceId)` (where `i` looping for [ 0 ...Transaction.length - 1] ) -e.g: -example: issue(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,[IERC3475.Transaction(1,14,500)]); -issues `1000` bonds with a class of `0` to address `0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef` with a nonce of `5`. -*/ -function issue(address _to, Transaction[] calldata _transaction) external; - -/** -* redeem -* @dev permits redemption of bond from an address. -* @dev the calling of this function needs to be restricted to the bond issuer contract. -* @param `_from` is the address from which the bond will be redeemed. -* @param `_transactions` is the `Transaction[] calldata` structure (i.e., array of tuples with the pairs of (class, nonce and amount) of the bonds that are to be redeemed). Further defined in the rationale section. -* @dev redeem function for a given class, and nonce category MUST BE done after certain conditions for maturity (can be end time, total active liquidity, etc.) are met. -* @dev furthermore, it SHOULD ONLY be called by the bank or secondary market maker contract. -e.g: -* redeem(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, [IERC3475.Transaction(1,14,500)]); -means “redeem from wallet address(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef), 500000000 of bond class1 and nonce 42. -*/ - -function redeem(address _from, Transaction[] calldata _transactions) external; - -/** -* burn -* @dev permits nullifying of the bonds (or transferring given bonds to address(0)). -* @dev burn function for given class and nonce MUST BE called by only the controller contract. -* @param _from is the address of the holder whose bonds are about to burn. -* @param `_transactions` is the `Transaction[] calldata` structure (i.e., array of tuple with the pairs of (class, nonce and amount) of the bonds that are to be burned). further defined in the rationale. -* @dev burn function for a given class, and nonce category MUST BE done only after certain conditions for maturity (can be end time, total active liquidity, etc). -* @dev furthermore, it SHOULD ONLY be called by the bank or secondary market maker contract. -* e.g: -* burn(0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B,[IERC3475.Transaction(1,14,500)]); -* means burning 500000000 bonds of class 1 nonce 42 owned by address 0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B. -*/ -function burn(address _from, Transaction[] calldata _transactions) external; - -/** -* approve -* @dev Allows `_spender` to withdraw from the msg.sender the bonds of `_amount` and type (classId and nonceId). -* @dev If this function is called again, it overwrites the current allowance with the amount. -* @dev `approve()` should only be callable by the bank, or the owner of the account. -* @param `_spender` argument is the address of the user who is approved to transfer the bonds. -* @param `_transactions` is the `Transaction[] calldata` structure (ie array of tuple with the pairs of (class,nonce, and amount) of the bonds that are to be approved to be spend by _spender). Further defined in the rationale section. -* e.g: -* approve(0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B,[IERC3475.Transaction(1,14,500)]); -* means owner of address 0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B is approved to manage 500 bonds from class 1 and Nonce 14. -*/ - -function approve(address _spender, Transaction[] calldata _transactions) external; - -/** -* SetApprovalFor -* @dev enable or disable approval for a third party (“operator”) to manage all the Bonds in the given class of the caller’s bonds. -* @dev If this function is called again, it overwrites the current allowance with the amount. -* @dev `approve()` should only be callable by the bank or the owner of the account. -* @param `_operator` is the address to add to the set of authorized operators. -* @param `classId` is the class id of the bond. -* @param `_approved` is true if the operator is approved (based on the conditions provided), false meaning approval is revoked. -* @dev contract MUST define internal function regarding the conditions for setting approval and should be callable only by bank or owner. -* e.g: setApprovalFor(0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B,0,true); -* means that address 0x82a55a613429Aeb3D01fbE6841bE1AcA4fFD5b2B is authorized to transfer bonds from class 0 (across all nonces). -*/ - -function setApprovalFor(address _operator, bool _approved) external returns(bool approved); - -/** -* totalSupply -* @dev Here, total supply includes burned and redeemed supply. -* @param classId is the corresponding class Id of the bond. -* @param nonceId is the nonce Id of the given bond class. -* @return the supply of the bonds -* e.g: -* totalSupply(0, 1); -* it finds the total supply of the bonds of classid 0 and bond nonce 1. -*/ -function totalSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - -/** -* redeemedSupply -* @dev Returns the redeemed supply of the bond identified by (classId,nonceId). -* @param classId is the corresponding class id of the bond. -* @param nonceId is the nonce id of the given bond class. -* @return the supply of bonds redeemed. -*/ -function redeemedSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - -/** -* activeSupply -* @dev Returns the active supply of the bond defined by (classId,NonceId). -* @param classId is the corresponding classId of the bond. -* @param nonceId is the nonce id of the given bond class. -* @return the non-redeemed, active supply. -*/ -function activeSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - -/** -* burnedSupply -* @dev Returns the burned supply of the bond in defined by (classId,NonceId). -* @param classId is the corresponding classId of the bond. -* @param nonceId is the nonce id of the given bond class. -* @return gets the supply of bonds for given classId and nonceId that are already burned. -*/ -function burnedSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - -/** -* balanceOf -* @dev Returns the balance of the bonds (nonReferenced) of given classId and bond nonce held by the address `_account`. -* @param classId is the corresponding classId of the bond. -* @param nonceId is the nonce id of the given bond class. -* @param _account address of the owner whose balance is to be determined. -* @dev this also consists of bonds that are redeemed. -*/ -function balanceOf(address _account, uint256 classId, uint256 nonceId) external view returns (uint256); - -/** -* classMetadata -* @dev Returns the JSON metadata of the classes. -* @dev The metadata SHOULD follow a set of structures explained later in the metadata.md -* @param metadataId is the index-id given bond class information. -* @return the JSON metadata of the nonces. — e.g. `[title, type, description]`. -*/ -function classMetadata(uint256 metadataId) external view returns (Metadata memory); - -/** -* nonceMetadata -* @dev Returns the JSON metadata of the nonces. -* @dev The metadata SHOULD follow a set of structures explained later in metadata.md -* @param classId is the corresponding classId of the bond. -* @param nonceId is the nonce id of the given bond class. -* @param metadataId is the index of the JSON storage for given metadata information. more is defined in metadata.md. -* @returns the JSON metadata of the nonces. — e.g. `[title, type, description]`. -*/ -function nonceMetadata(uint256 classId, uint256 metadataId) external view returns (Metadata memory); - -/** -* classValues -* @dev allows anyone to read the values (stored in struct Values for different class) for given bond class `classId`. -* @dev the values SHOULD follow a set of structures as explained in metadata along with correct mapping corresponding to the given metadata structure -* @param classId is the corresponding classId of the bond. -* @param metadataId is the index of the JSON storage for given metadata information of all values of given metadata. more is defined in metadata.md. -* @returns the Values of the class metadata. — e.g. `[string, uint, address]`. -*/ -function classValues(uint256 classId, uint256 metadataId) external view returns (Values memory); - -/** -* nonceValues -* @dev allows anyone to read the values (stored in struct Values for different class) for given bond (`nonceId`,`classId`). -* @dev the values SHOULD follow a set of structures explained in metadata along with correct mapping corresponding to the given metadata structure -* @param classId is the corresponding classId of the bond. -* @param metadataId is the index of the JSON storage for given metadata information of all values of given metadata. More is defined in metadata.md. -* @returns the Values of the class metadata. — e.g. `[string, uint, address]`. -*/ -function nonceValues(uint256 classId, uint256 nonceId, uint256 metadataId) external view returns (Values memory); - -/** -* getProgress -* @dev Returns the parameters to determine the current status of bonds maturity. -* @dev the conditions of redemption SHOULD be defined with one or several internal functions. -* @param classId is the corresponding classId of the bond. -* @param nonceId is the nonceId of the given bond class . -* @returns progressAchieved defines the metric (either related to % liquidity, time, etc.) that defines the current status of the bond. -* @returns progressRemaining defines the metric that defines the remaining time/ remaining progress. -*/ -function getProgress(uint256 classId, uint256 nonceId) external view returns (uint256 progressAchieved, uint256 progressRemaining); - -/** -* allowance -* @dev Authorizes to set the allowance for given `_spender` by `_owner` for all bonds identified by (classId, nonceId). -* @param _owner address of the owner of bond(and also msg.sender). -* @param _spender is the address authorized to spend the bonds held by _owner of info (classId, nonceId). -* @param classId is the corresponding classId of the bond. -* @param nonceId is the nonceId of the given bond class. -* @notice Returns the _amount which spender is still allowed to withdraw from _owner. -*/ -function allowance(address _owner, address _spender, uint256 classId, uint256 nonceId) external returns(uint256); - -/** -* isApprovedFor -* @dev returns true if address _operator is approved for managing the account’s bonds class. -* @notice Queries the approval status of an operator for a given owner. -* @dev _owner is the owner of bonds. -* @dev _operator is the EOA /contract, whose status for approval on bond class for this approval is checked. -* @returns “true” if the operator is approved, “false” if not. -*/ -function isApprovedFor(address _owner, address _operator) external view returns (bool); -``` - -### Events - -```solidity -/** -* Issue -* @notice Issue MUST trigger when Bonds are issued. This SHOULD not include zero value Issuing. -* @dev This SHOULD not include zero value issuing. -* @dev Issue MUST be triggered when the operator (i.e Bank address) contract issues bonds to the given entity. -* eg: emit Issue(_operator, 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,[IERC3475.Transaction(1,14,500)]); -* issue by address(operator) 500 Bonds(nonce14,class 1) to address 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. -*/ - -event Issue(address indexed _operator, address indexed _to, Transaction[] _transactions); - -/** -* Redeem -* @notice Redeem MUST trigger when Bonds are redeemed. This SHOULD not include zero value redemption. -*e.g: emit Redeem(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,0x492Af743654549b12b1B807a9E0e8F397E44236E,[IERC3475.Transaction(1,14,500)]); -* emit event when 5000 bonds of class 1, nonce 14 owned by address 0x492Af743654549b12b1B807a9E0e8F397E44236E are being redeemed by 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. -*/ - -event Redeem(address indexed _operator, address indexed _from, Transaction[] _transactions); - - -/** -* Burn. -* @dev `Burn` MUST trigger when the bonds are being redeemed via staking (or being invalidated) by the bank contract. -* @dev `Burn` MUST trigger when Bonds are burned. This SHOULD not include zero value burning. -* e.g : emit Burn(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,0x492Af743654549b12b1B807a9E0e8F397E44236E,[IERC3475.Transaction(1,14,500)]); -* emits event when 500 bonds of owner 0x492Af743654549b12b1B807a9E0e8F397E44236E of type (class 1, nonce 14) are burned by operator 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. -*/ - -event burn(address _operator, address _owner, Transaction[] _transactions); - -/** -* Transfer -* @dev its emitted when the bond is transferred by address(operator) from owner address(_from) to address(_to) with the bonds transferred, whose params are defined by _transactions struct array. -* @dev Transfer MUST trigger when Bonds are transferred. This SHOULD not include zero value transfers. -* @dev Transfer event with the _from `0x0` MUST not create this event(use `event Issued` instead). -* e.g emit Transfer(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, 0x492Af743654549b12b1B807a9E0e8F397E44236E, _to, [IERC3475.Transaction(1,14,500)]); -* transfer by address(_operator) amount 500 bonds with (Class 1 and Nonce 14) from 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, to address(_to). -*/ - -event Transfer(address indexed _operator, address indexed _from, address indexed _to, Transaction[] _transactions); - -/** -* ApprovalFor -* @dev its emitted when address(_owner) approves the address(_operator) to transfer his bonds. -* @notice Approval MUST trigger when bond holders are approving an _operator. This SHOULD not include zero value approval. -* eg: emit ApprovalFor(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, 0x492Af743654549b12b1B807a9E0e8F397E44236E, true); -* this means 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef gives 0x492Af743654549b12b1B807a9E0e8F397E44236E access permission for transfer of its bonds. -*/ - -event ApprovalFor(address indexed _owner, address indexed _operator, bool _approved); -``` - -**Metadata**: -The metadata of a bond class or nonce is stored as an array of JSON objects, represented by the following types. - -**NOTE: all of the metadata schemas are referenced from [here](../assets/eip-3475/Metadata.md)** - -### 1. Description: - -This defines the additional information about the nature of data being stored in the nonce/class metadata structures. They are defined using the structured explained [here](../assets/eip-3475/Metadata.md#1-description-metadata). this will then be used by the frontend of the respective entities participating in the bond markets to interpret the data which is compliant with their jurisdiction. - -### 2. Nonce: - -The key value for indexing the information is the 'class' field. Following are the rules: - -- The title can be any alphanumeric type that is differentiated by the description of metadata (although it can be dependent on certain jurisdictions). -- The title SHOULD not be EMPTY. - -Some specific examples of metadata can be the localization of bonds, jurisdiction details etc., and they can be found in the [metadata.md](../assets/eip-3475/Metadata.md) example description. - -### 3. Class metadata: - -This structure defines the details of the class information (symbol, risk information, etc.). the example is explained [here](../assets/eip-3475/Metadata.md) in the class metadata section. - -### 4. Decoding data - -First, the functions for analyzing the metadata (i.e `ClassMetadata` and `NonceMetadata`) are to be used by the corresponding frontend to decode the information of the bond. - -This is done via overriding the function interface for functions `classValues` and `nonceValues` by defining the key (which SHOULD be an index) to read the corresponding information stored as a JSON object. - -```JSON -{ -"title": "symbol", -"_type": "string", -"description": "defines the unique identifier name in following format: (symbol, bondType, maturity in months)", -"values": ["Class Name 1","Class Name 2","DBIT Fix 6M"], -} -``` - -e.g. In the above example, to get the `symbol` of the given class id, we can use the class id as a key to get the `symbol` value in the values, which then can be used for fetching the detail for instance. - -## Rationale - -### Metadata structure - -Instead of storing the details about the class and their issuances to the user (ie nonce) externally, we store the details in the respective structures. Classes represent the different bond types, and nonces represent the various period of issuances. Nonces under the same class share the same metadata. Meanwhile, nonces are non-fungible. Each nonce can store a different set of metadata. Thus, upon transfer of a bond, all the metadata will be transferred to the new owner of the bond. - -```solidity - struct Values{ - string stringValue; - uint uintValue; - address addressValue; - bool boolValue; - bytes bytesValue; - } -``` - -```solidity - struct Metadata { - string title; - string _type; - string description; - } -``` - -### Batch function - - This EIP supports batch operations. It allows the user to transfer different bonds along with their metadata to a new address instantaneously in a single transaction. After execution, the new owner holds the right to reclaim the face value of each of the bonds. This mechanism helps with the "packaging" of bonds–helpful in use cases like trades on a secondary market. - -```solidity - struct Transaction { - uint256 classId; - uint256 nonceId; - uint256 _amount; - } -``` - -Where: -The `classId` is the class id of the bond. - -The `nonceId` is the nonce id of the given bond class. This param is for distinctions of the issuing conditions of the bond. - -The `_amount` is the amount of the bond for which the spender is approved. - -### AMM optimization - - One of the most obvious use cases of this EIP is the multilayered pool. The early version of AMM uses a separate smart contract and an [EIP-20](./eip-20.md) LP token to manage a pair. By doing so, the overall liquidity inside of one pool is significantly reduced and thus generates unnecessary gas spent and slippage. Using this EIP standard, one can build a big liquidity pool with all the pairs inside (thanks to the presence of the data structures consisting of the liquidity corresponding to the given class and nonce of bonds). Thus by knowing the class and nonce of the bonds, the liquidity can be represented as the percentage of a given token pair for the owner of the bond in the given pool. Effectively, the [EIP-20](./eip-20.md) LP token (defined by a unique smart contract in the pool factory contract) is aggregated into a single bond and consolidated into a single pool. - -- The reason behind the standard's name (abstract storage bond) is its ability to store all the specifications (metadata/values and transaction as defined in the following sections) without needing external storage on-chain/off-chain. - -## Backwards Compatibility - -Any contract that inherits the interface of this EIP is compatible. This compatibility exists for issuer and receiver of the bonds. Also any client EOA wallet can be compatible with the standard if they are able to sign `issue()` and `redeem()` commands. - -However, any existing [EIP-20](./eip-20.md) token contract can issue its bonds by delegating the minting role to a bank contract with the interface of this standard built-in. Check out our reference implementation for the correct interface definition. - -To ensure the indexing of transactions throughout the bond lifecycle (i.e "Issue", "Redeem" and "Transfer" functions), events cited in specification section MUST be emitted when such transaction is passed. - -**Note that the this standard interface is also compatible with [EIP-20](./eip-20.md) and [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md)interface.** - -However, creating a separate bank contract is recommended for reading the bonds and future upgrade needs. - -Acceptable collateral can be in the form of fungible (like [EIP-20](./eip-20.md)), non-fungible ([EIP-721](./eip-721.md), [EIP-1155](./eip-1155.md)) , or other bonds represented by this standard. - -## Test Cases - -Test-case for the minimal reference implementation is [here](../assets/eip-3475/ERC3475.test.ts). Use the Truffle box to compile and test the contracts. - -## Reference Implementation - -- [Interface](../assets/eip-3475/interfaces/IERC3475.sol). - -- [Basic Example](../assets/eip-3475/ERC3475.sol). - - This demonstration shows only minimalist implementation. - -## Security Considerations - -- The `function setApprovalFor(address _operatorAddress)` gives the operator role to `_operatorAddress`. It has all the permissions to transfer, burn and redeem bonds by default. - -- If the owner wants to give a one-time allocation to an address for specific bonds(classId,bondsId), he should call the `function approve()` giving the `Transaction[]` allocated rather than approving all the classes using `setApprovalFor`. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3475.md diff --git a/EIPS/eip-3525.md b/EIPS/eip-3525.md index 93bb1fc1d4c420..5763e4677fc1e1 100644 --- a/EIPS/eip-3525.md +++ b/EIPS/eip-3525.md @@ -1,575 +1 @@ ---- -eip: 3525 -title: Semi-Fungible Token -description: Defines a specification where ERC-721 compatible tokens with the same SLOT and different IDs are fungible. -author: Will Wang (@will42w), Mike Meng , Yi Cai (@YeeTsai) , Ryan Chow , Zhongxin Wu (@Nerverwind), AlvisDu (@AlvisDu) -discussions-to: https://ethereum-magicians.org/t/eip-3525-the-semi-fungible-token -status: Final -type: Standards Track -category: ERC -created: 2020-12-01 -requires: 20, 165, 721 ---- - -## Abstract - -This is a standard for semi-fungible tokens. The set of smart contract interfaces described in this document defines an [ERC-721](./eip-721.md) compatible token standard. This standard introduces an `` triple scalar model that represents the semi-fungible structure of a token. It also introduces new transfer models as well as approval models that reflect the semi-fungible nature of the tokens. - -Token contains an ERC-721 equivalent ID property to identify itself as a universally unique entity, so that the tokens can be transferred between addresses and approved to be operated in ERC-721 compatible way. - -Token also contains a `value` property, representing the quantitative nature of the token. The meaning of the 'value' property is quite like that of the 'balance' property of an [ERC-20](./eip-20.md) token. Each token has a 'slot' attribute, ensuring that the value of two tokens with the same slot be treated as fungible, adding fungibility to the value property of the tokens. - -This EIP introduces new token transfer models for semi-fungibility, including value transfer between two tokens of the same slot and value transfer from a token to an address. - -## Motivation - -Tokenization is one of the most important trends by which to use and control digital assets in crypto. Traditionally, there have been two approaches to do so: fungible and non-fungible tokens. Fungible tokens generally use the ERC-20 standard, where every unit of an asset is identical to each other. ERC-20 is a flexible and efficient way to manipulate fungible tokens. Non-fungible tokens are predominantly ERC-721 tokens, a standard capable of distinguishing digital assets from one another based on identity. - -However, both have significant drawbacks. For example, ERC-20 requires that users create a separate ERC-20 contract for each individual data structure or combination of customizable properties. In practice, this results in an extraordinarily large amount of ERC-20 contracts that need to be created. On the other hand, ERC-721 tokens provide no quantitative feature, significantly undercutting their computability, liquidity, and manageability. For example, if one was to create financial instruments such as bonds, insurance policy, or vesting plans using ERC-721, no standard interfaces are available for us to control the value in them, making it impossible, for example, to transfer a portion of the equity in the contract represented by the token. - -A more intuitive and straightforward way to solve the problem is to create a semi-fungible token that has the quantitative features of ERC-20 and qualitative attributes of ERC-721. The backwards-compatibility with ERC-721 of such semi-fungible tokens would help utilize existing infrastructures already in use and lead to faster adoption. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -**Every [ERC-3525](./eip-3525.md) compliant contract must implement the ERC-3525, ERC-721 and [ERC-165](./eip-165.md) interfaces** - -```solidity -pragma solidity ^0.8.0; - -/** - * @title ERC-3525 Semi-Fungible Token Standard - * Note: the ERC-165 identifier for this interface is 0xd5358140. - */ -interface IERC3525 /* is IERC165, IERC721 */ { - /** - * @dev MUST emit when value of a token is transferred to another token with the same slot, - * including zero value transfers (_value == 0) as well as transfers when tokens are created - * (`_fromTokenId` == 0) or destroyed (`_toTokenId` == 0). - * @param _fromTokenId The token id to transfer value from - * @param _toTokenId The token id to transfer value to - * @param _value The transferred value - */ - event TransferValue(uint256 indexed _fromTokenId, uint256 indexed _toTokenId, uint256 _value); - - /** - * @dev MUST emit when the approval value of a token is set or changed. - * @param _tokenId The token to approve - * @param _operator The operator to approve for - * @param _value The maximum value that `_operator` is allowed to manage - */ - event ApprovalValue(uint256 indexed _tokenId, address indexed _operator, uint256 _value); - - /** - * @dev MUST emit when the slot of a token is set or changed. - * @param _tokenId The token of which slot is set or changed - * @param _oldSlot The previous slot of the token - * @param _newSlot The updated slot of the token - */ - event SlotChanged(uint256 indexed _tokenId, uint256 indexed _oldSlot, uint256 indexed _newSlot); - - /** - * @notice Get the number of decimals the token uses for value - e.g. 6, means the user - * representation of the value of a token can be calculated by dividing it by 1,000,000. - * Considering the compatibility with third-party wallets, this function is defined as - * `valueDecimals()` instead of `decimals()` to avoid conflict with ERC-20 tokens. - * @return The number of decimals for value - */ - function valueDecimals() external view returns (uint8); - - /** - * @notice Get the value of a token. - * @param _tokenId The token for which to query the balance - * @return The value of `_tokenId` - */ - function balanceOf(uint256 _tokenId) external view returns (uint256); - - /** - * @notice Get the slot of a token. - * @param _tokenId The identifier for a token - * @return The slot of the token - */ - function slotOf(uint256 _tokenId) external view returns (uint256); - - /** - * @notice Allow an operator to manage the value of a token, up to the `_value`. - * @dev MUST revert unless caller is the current owner, an authorized operator, or the approved - * address for `_tokenId`. - * MUST emit the ApprovalValue event. - * @param _tokenId The token to approve - * @param _operator The operator to be approved - * @param _value The maximum value of `_toTokenId` that `_operator` is allowed to manage - */ - function approve( - uint256 _tokenId, - address _operator, - uint256 _value - ) external payable; - - /** - * @notice Get the maximum value of a token that an operator is allowed to manage. - * @param _tokenId The token for which to query the allowance - * @param _operator The address of an operator - * @return The current approval value of `_tokenId` that `_operator` is allowed to manage - */ - function allowance(uint256 _tokenId, address _operator) external view returns (uint256); - - /** - * @notice Transfer value from a specified token to another specified token with the same slot. - * @dev Caller MUST be the current owner, an authorized operator or an operator who has been - * approved the whole `_fromTokenId` or part of it. - * MUST revert if `_fromTokenId` or `_toTokenId` is zero token id or does not exist. - * MUST revert if slots of `_fromTokenId` and `_toTokenId` do not match. - * MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the - * operator. - * MUST emit `TransferValue` event. - * @param _fromTokenId The token to transfer value from - * @param _toTokenId The token to transfer value to - * @param _value The transferred value - */ - function transferFrom( - uint256 _fromTokenId, - uint256 _toTokenId, - uint256 _value - ) external payable; - - - /** - * @notice Transfer value from a specified token to an address. The caller should confirm that - * `_to` is capable of receiving ERC-3525 tokens. - * @dev This function MUST create a new ERC-3525 token with the same slot for `_to`, - * or find an existing token with the same slot owned by `_to`, to receive the transferred value. - * MUST revert if `_fromTokenId` is zero token id or does not exist. - * MUST revert if `_to` is zero address. - * MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the - * operator. - * MUST emit `Transfer` and `TransferValue` events. - * @param _fromTokenId The token to transfer value from - * @param _to The address to transfer value to - * @param _value The transferred value - * @return ID of the token which receives the transferred value - */ - function transferFrom( - uint256 _fromTokenId, - address _to, - uint256 _value - ) external payable returns (uint256); -} -``` - -The slot's enumeration extension is OPTIONAL. This allows your contract to publish its full list of `SLOT`s and make them discoverable. - -```solidity -pragma solidity ^0.8.0; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for slot enumeration - * @dev Interfaces for any contract that wants to support enumeration of slots as well as tokens - * with the same slot. - * Note: the ERC-165 identifier for this interface is 0x3b741b9e. - */ -interface IERC3525SlotEnumerable is IERC3525 /* , IERC721Enumerable */ { - - /** - * @notice Get the total amount of slots stored by the contract. - * @return The total amount of slots - */ - function slotCount() external view returns (uint256); - - /** - * @notice Get the slot at the specified index of all slots stored by the contract. - * @param _index The index in the slot list - * @return The slot at `index` of all slots. - */ - function slotByIndex(uint256 _index) external view returns (uint256); - - /** - * @notice Get the total amount of tokens with the same slot. - * @param _slot The slot to query token supply for - * @return The total amount of tokens with the specified `_slot` - */ - function tokenSupplyInSlot(uint256 _slot) external view returns (uint256); - - /** - * @notice Get the token at the specified index of all tokens with the same slot. - * @param _slot The slot to query tokens with - * @param _index The index in the token list of the slot - * @return The token ID at `_index` of all tokens with `_slot` - */ - function tokenInSlotByIndex(uint256 _slot, uint256 _index) external view returns (uint256); -} -``` - -The slot level approval is OPTIONAL. This allows any contract that wants to support approval for slots, which allows an operator to manage one's tokens with the same slot. - -```solidity -pragma solidity ^0.8.0; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for approval of slot level - * @dev Interfaces for any contract that wants to support approval of slot level, which allows an - * operator to manage one's tokens with the same slot. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xb688be58. - */ -interface IERC3525SlotApprovable is IERC3525 { - /** - * @dev MUST emit when an operator is approved or disapproved to manage all of `_owner`'s - * tokens with the same slot. - * @param _owner The address whose tokens are approved - * @param _slot The slot to approve, all of `_owner`'s tokens with this slot are approved - * @param _operator The operator being approved or disapproved - * @param _approved Identify if `_operator` is approved or disapproved - */ - event ApprovalForSlot(address indexed _owner, uint256 indexed _slot, address indexed _operator, bool _approved); - - /** - * @notice Approve or disapprove an operator to manage all of `_owner`'s tokens with the - * specified slot. - * @dev Caller SHOULD be `_owner` or an operator who has been authorized through - * `setApprovalForAll`. - * MUST emit ApprovalSlot event. - * @param _owner The address that owns the ERC-3525 tokens - * @param _slot The slot of tokens being queried approval of - * @param _operator The address for whom to query approval - * @param _approved Identify if `_operator` would be approved or disapproved - */ - function setApprovalForSlot( - address _owner, - uint256 _slot, - address _operator, - bool _approved - ) external payable; - - /** - * @notice Query if `_operator` is authorized to manage all of `_owner`'s tokens with the - * specified slot. - * @param _owner The address that owns the ERC-3525 tokens - * @param _slot The slot of tokens being queried approval of - * @param _operator The address for whom to query approval - * @return True if `_operator` is authorized to manage all of `_owner`'s tokens with `_slot`, - * false otherwise. - */ - function isApprovedForSlot( - address _owner, - uint256 _slot, - address _operator - ) external view returns (bool); -} -``` - - -### ERC-3525 Token Receiver - -If a smart contract wants to be informed when they receive values from other addresses, it should implement all of the functions in the `IERC3525Receiver` interface, in the implementation it can decide whether to accept or reject the transfer. See "Transfer Rules" for further detail. - -```solidity - pragma solidity ^0.8.0; - -/** - * @title ERC-3525 token receiver interface - * @dev Interface for a smart contract that wants to be informed by ERC-3525 contracts when receiving values from ANY addresses or ERC-3525 tokens. - * Note: the ERC-165 identifier for this interface is 0x009ce20b. - */ -interface IERC3525Receiver { - /** - * @notice Handle the receipt of an ERC-3525 token value. - * @dev An ERC-3525 smart contract MUST check whether this function is implemented by the recipient contract, if the - * recipient contract implements this function, the ERC-3525 contract MUST call this function after a - * value transfer (i.e. `transferFrom(uint256,uint256,uint256,bytes)`). - * MUST return 0x009ce20b (i.e. `bytes4(keccak256('onERC3525Received(address,uint256,uint256, - * uint256,bytes)'))`) if the transfer is accepted. - * MUST revert or return any value other than 0x009ce20b if the transfer is rejected. - * @param _operator The address which triggered the transfer - * @param _fromTokenId The token id to transfer value from - * @param _toTokenId The token id to transfer value to - * @param _value The transferred value - * @param _data Additional data with no specified format - * @return `bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)'))` - * unless the transfer is rejected. - */ - function onERC3525Received(address _operator, uint256 _fromTokenId, uint256 _toTokenId, uint256 _value, bytes calldata _data) external returns (bytes4); - -} -``` - -### Token Manipulation - -#### Scenarios - -**_Transfer:_** - -Besides ERC-721 compatible token transfer methods, this EIP introduces two new transfer models: value transfer from ID to ID, and value transfer from ID to address. - -```solidity -function transferFrom(uint256 _fromTokenId, uint256 _toTokenId, uint256 _value) external payable; - -function transferFrom(uint256 _fromTokenId, address _to, uint256 _value) external payable returns (uint256 toTokenId_); -``` - -The first one allows value transfers from one token (specified by `_fromTokenId`) to another token (specified by `_toTokenId`) within the same slot, resulting in the `_value` being subtracted from the value of the source token and added to the value of the destination token; - -The second one allows value transfers from one token (specified by `_fromTokenId`) to an address (specified by `_to`), the value is actually transferred to a token owned by the address, and the id of the destination token should be returned. Further explanation can be found in the 'design decision' section for this method. - -#### Rules - -**_approving rules:_** - -This EIP provides four kinds of approving functions indicating different levels of approvals, which can be described as full level approval, slot level approval, token ID level approval as well as value level approval. - -- `setApprovalForAll`, compatible with ERC-721, SHOULD indicate the full level of approval, which means that the authorized operators are capable of managing all the tokens, including their values, owned by the owner. -- `setApprovalForSlot` (optional) SHOULD indicate the slot level of approval, which means that the authorized operators are capable of managing all the tokens with the specified slot, including their values, owned by the owner. -- The token ID level `approve` function, compatible with ERC-721, SHOULD indicate that the authorized operator is capable of managing only the specified token ID, including its value, owned by the owner. -- The value level `approve` function, SHOULD indicate that the authorized operator is capable of managing the specified maximum value of the specified token owned by the owner. -- For any approving function, the caller MUST be the owner or has been approved with a higher level of authority. - -**_transferFrom rules:_** - -- The `transferFrom(uint256 _fromTokenId, uint256 _toTokenId, uint256 _value)` function, SHOULD indicate value transfers from one token to another token, in accordance with the rules below: - - - MUST revert unless `msg.sender` is the owner of `_fromTokenId`, an authorized operator or an operator who has been approved the whole token or at least `_value` of it. - - MUST revert if `_fromTokenId` or `_toTokenId` is zero token id or does not exist. - - MUST revert if slots of `_fromTokenId` and `_toTokenId` do not match. - - MUST revert if `_value` exceeds the value of `_fromTokenId` or its allowance to the operator. - - MUST check for the `onERC3525Received` function if the owner of _toTokenId is a smart contract, if the function exists, MUST call this function after the value transfer, MUST revert if the result is not equal to 0x009ce20b; - - MUST emit `TransferValue` event. - -- The `transferFrom(uint256 _fromTokenId, address _to, uint256 _value)` function, which transfers value from one token ID to an address, SHOULD follow the rule below: - - - MUST either find a ERC-3525 token owned by the address `_to` or create a new ERC-3525 token, with the same slot of `_fromTokenId`, to receive the transferred value. - - MUST revert unless `msg.sender` is the owner of `_fromTokenId`, an authorized operator or an operator who has been approved the whole token or at least `_value` of it. - - MUST revert if `_fromTokenId` is zero token id or does not exist. - - MUST revert if `_to` is zero address. - - MUST revert if `_value` exceeds the value of `_fromTokenId` or its allowance to the operator. - - MUST check for the `onERC3525Received` function if the _to address is a smart contract, if the function exists, MUST call this function after the value transfer, MUST revert if the result is not equal to 0x009ce20b; - - MUST emit `Transfer` and `TransferValue` events. - - -### Metadata - -#### Metadata Extensions - -ERC-3525 metadata extensions are compatible ERC-721 metadata extensions. - -This optional interface can be identified with the ERC-165 Standard Interface Detection. - -```solidity -pragma solidity ^0.8.0; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for metadata - * @dev Interfaces for any contract that wants to support query of the Uniform Resource Identifier - * (URI) for the ERC-3525 contract as well as a specified slot. - * Because of the higher reliability of data stored in smart contracts compared to data stored in - * centralized systems, it is recommended that metadata, including `contractURI`, `slotURI` and - * `tokenURI`, be directly returned in JSON format, instead of being returned with a url pointing - * to any resource stored in a centralized system. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xe1600902. - */ -interface IERC3525Metadata is - IERC3525 /* , IERC721Metadata */ -{ - /** - * @notice Returns the Uniform Resource Identifier (URI) for the current ERC-3525 contract. - * @dev This function SHOULD return the URI for this contract in JSON format, starting with - * header `data:application/json;`. - * See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for contract URI. - * @return The JSON formatted URI of the current ERC-3525 contract - */ - function contractURI() external view returns (string memory); - - /** - * @notice Returns the Uniform Resource Identifier (URI) for the specified slot. - * @dev This function SHOULD return the URI for `_slot` in JSON format, starting with header - * `data:application/json;`. - * See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for slot URI. - * @return The JSON formatted URI of `_slot` - */ - function slotURI(uint256 _slot) external view returns (string memory); -} -``` - -#### ERC-3525 Metadata URI JSON Schema - -This is the "ERC-3525 Metadata JSON Schema for `contractURI()`" referenced above. - -```json -{ - "title": "Contract Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Contract Name" - }, - "description": { - "type": "string", - "description": "Describes the contract" - }, - "image": { - "type": "string", - "description": "Optional. Either a base64 encoded imgae data or a URI pointing to a resource with mime type image/* representing what this contract represents." - }, - "external_link": { - "type": "string", - "description": "Optional. A URI pointing to an external resource." - }, - "valueDecimals": { - "type": "integer", - "description": "The number of decimal places that the balance should display - e.g. 18, means to divide the token value by 1000000000000000000 to get its user representation." - } - } -} -``` - -This is the "ERC-3525 Metadata JSON Schema for `slotURI(uint)`" referenced above. - -```json -{ - "title": "Slot Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset category to which this slot represents" - }, - "description": { - "type": "string", - "description": "Describes the asset category to which this slot represents" - }, - "image": { - "type": "string", - "description": "Optional. Either a base64 encoded imgae data or a URI pointing to a resource with mime type image/* representing the asset category to which this slot represents." - }, - "properties": { - "type": "array", - "description": "Each item of `properties` SHOULD be organized in object format, including name, description, value, order (optional), display_type (optional), etc." - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of this property." - }, - "description": { - "type": "string", - "description": "Describes this property." - } - "value": { - "description": "The value of this property, which may be a string or a number." - }, - "is_intrinsic": { - "type": "boolean", - "description": "According to the definition of `slot`, one of the best practice to generate the value of a slot is utilizing the `keccak256` algorithm to calculate the hash value of multi properties. In this scenario, the `properties` field should contain all the properties that are used to calculate the value of `slot`, and if a property is used in the calculation, is_intrinsic must be TRUE." - }, - "order": { - "type": "integer", - "description": "Optional, related to the value of is_intrinsic. If is_intrinsic is TRUE, it must be the order of this property appeared in the calculation method of the slot." - }, - "display_type": { - "type": "string", - "description": "Optional. Specifies in what form this property should be displayed." - } - } - } - } - } -} -``` - - -This is the "ERC-3525 Metadata JSON Schema for `tokenURI(uint)`" referenced above. - -```json -{ - "title": "Token Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this token represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this token represents" - }, - "image": { - "type": "string", - "description": "Either a base64 encoded imgae data or a URI pointing to a resource with mime type image/* representing the asset to which this token represents." - }, - "balance": { - "type": "integer", - "description": "THe value held by this token." - }, - "slot": { - "type": "integer", - "description": "The id of the slot that this token belongs to." - }, - "properties": { - "type": "object", - "description": "Arbitrary properties. Values may be strings, numbers, objects or arrays. Optional, you can use the same schema as the properties section of ERC-3525 Metadata JSON Schema for slotURI(uint) if you need a better description attribute." - } - } -} -``` - - -## Rationale - -### Metadata generation - -This token standard is designed to represent semi-fungible assets, which are most suited for financial instruments rather than collectibles or in-game items. For maximum transparency and safety of digital assets, we strongly recommend that all implementations should generate metadata directly from contract code rather than giving out an off-chain server URL. - -### Design decision: Value transfer from token to address - -The 'value' of a token is a property of the token and is not linked to an address, so to transfer the value to an address would be actually transferring it to a token owned by that address, not the address itself. - -From the implementation perspective, the process of transferring values from token to address could be done as follows: (1) create a new token for the recipient's address, (2) transfer the value to the new token from the 'source token'. So that this method is not fully independent from the ID-to-ID transfer method, and can be viewed as syntactic sugar that wraps the process described above. - -In a special case, if the destination address owns one or more tokens with the same slot value as the source token, this method will have an alternative implementation as follows: (1) find one token owned by the address with the same slot value of the source token, (2) transfer the value to the found token. - -Both implementations described above should be treated as compliant with this standard. - -The purpose of maintaining id-to-address transfer function is to maximize the compatibility with most wallet apps, since for most of the token standards, the destination of token transfer are addresses. This syntactic wrapping will help wallet apps easily implement the value transfer function from a token to any address. - -### Design decision: Notification/acceptance mechanism instead of 'Safe Transfer' - -ERC-721 and some later token standards introduced 'Safe Transfer' model, for better control of the 'safety' when transferring tokens, this mechanism leaves the choice of different transfer modes (safe/unsafe) to the sender, and may cause some potential problems: - -1. In most situations the sender does not know how to choose between two kinds of transfer methods (safe/unsafe); -2. If the sender calls the `safeTransferFrom` method, the transfer may fail if the recipient contract did not implement the callback function, even if that contract is capable of receiving and manipulating the token without issue. - -This EIP defines a simple 'Check, Notify and Response' model for better flexibility as well as simplicity: - -1. No extra `safeTransferFrom` methods are needed, all callers only need to call one kind of transfer; -2. All ERC-3525 contracts MUST check for the existence of `onERC3525Received` on the recipient contract and call the function when it exists; -3. Any smart contract can implement `onERC3525Received` function for the purpose of being notified after receiving values; this function MUST return 0x009ce20b (i.e. `bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)'))`) if the transfer is accepted, or any other value if the transfer is rejected. - -There is a special case for this notification/acceptance mechanism: since ERC-3525 allows value transfer from an address to itself, when a smart contract which implements `onERC3525Received` transfers value to itself, `onERC3525Received` will also be called. This allows for the contract to implement different rules of acceptance between self-value-transfer and receiving value from other addresses. - -### Design decision: Relationship between different approval models - -For semantic compatibility with ERC-721 as well as the flexibility of value manipulation of tokens, we decided to define the relationships between some of the levels of approval like that: - -1. Approval of an id will lead to the ability to partially transfer values from this id by the approved operator; this will simplify the value approval for an id. However, the approval of total values in a token should not lead to the ability to transfer the token entity by the approved operator. -2. `setApprovalForAll` will lead to the ability to partially transfer values from any token, as well as the ability to approve partial transfer of values from any token to a third party; this will simplify the value transfer and approval of all tokens owned by an address. - -## Backwards Compatibility - -As mentioned in the beginning, this EIP is backward compatible with ERC-721. - -## Reference Implementation - -- [ERC-3525 implementation](../assets/eip-3525/contracts/ERC3525.sol) - -## Security Considerations - -The value level approval and slot level approval (optional) is isolated from ERC-721 approval models, so that approving value should not affect ERC-721 level approvals. Implementations of this EIP must obey this principle. - -Since this EIP is ERC-721 compatible, any wallets and smart contracts that can hold and manipulate standard ERC-721 tokens will have no risks of asset loss for ERC-3525 tokens due to incompatible standards implementations. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3525.md diff --git a/EIPS/eip-3561.md b/EIPS/eip-3561.md index 91e868d8455ecf..ee9e7010ecd748 100644 --- a/EIPS/eip-3561.md +++ b/EIPS/eip-3561.md @@ -1,282 +1 @@ ---- -eip: 3561 -title: Trust Minimized Upgradeability Proxy -description: proxy with a delay before specified upgrade goes live -author: Sam Porter (@SamPorter1984) -discussions-to: https://ethereum-magicians.org/t/trust-minimized-proxy/5742 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-05-09 ---- - -## Abstract - -Removing trust from upgradeability proxy is necessary for anonymous developers. In order to accomplish this, instant and potentially malicious upgrades must be prevented. This EIP introduces additional storage slots for upgradeability proxy which are assumed to decrease trust in interaction with upgradeable smart contracts. Defined by the admin implementation logic can be made an active implementation logic only after Zero Trust Period allows. - -## Motivation - -Anonymous developers who utilize upgradeability proxies typically struggle to earn the trust of the community. - -Fairer, better future for humanity absolutely requires some developers to stay anonymous while still attract vital attention to solutions they propose and at the same time leverage the benefits of possible upgradeability. - -## Specification - -The specification is an addition to the standard [EIP-1967](./eip-1967.md) transparent proxy design. -The specification focuses on the slots it adds. All admin interactions with trust minimized proxy must emit an event to make admin actions trackable, and all admin actions must be guarded with `onlyAdmin()` modifier. - -### Next Logic Contract Address - -Storage slot `0x19e3fabe07b65998b604369d85524946766191ac9434b39e27c424c976493685` (obtained as `bytes32(uint256(keccak256('eip3561.proxy.next.logic')) - 1)`). -Desirable implementation logic address must be first defined as next logic, before it can function as actual logic implementation stored in EIP-1967 `IMPLEMENTATION_SLOT`. -Admin interactions with next logic contract address correspond with these methods and events: - -```solidity -// Sets next logic contract address. Emits NextLogicDefined -// If current implementation is address(0), then upgrades to IMPLEMENTATION_SLOT -// immedeatelly, therefore takes data as an argument -function proposeTo(address implementation, bytes calldata data) external IfAdmin -// As soon UPGRADE_BLOCK_SLOT allows, sets the address stored as next implementation -// as current IMPLEMENTATION_SLOT and initializes it. -function upgrade(bytes calldata data) external IfAdmin -// cancelling is possible for as long as upgrade() for given next logic was not called -// emits NextLogicCanceled -function cancelUpgrade() external onlyAdmin; - -event NextLogicDefined(address indexed nextLogic, uint earliestArrivalBlock); // important to have -event NextLogicCanceled(address indexed oldLogic); -``` - -### Upgrade Block - -Storage slot `0xe3228ec3416340815a9ca41bfee1103c47feb764b4f0f4412f5d92df539fe0ee` (obtained as `bytes32(uint256(keccak256('eip3561.proxy.next.logic.block')) - 1)`). -On/after this block next logic contract address can be set to EIP-1967 `IMPLEMENTATION_SLOT` or, in other words, `upgrade()` can be called. Updated automatically according to Zero Trust Period, shown as `earliestArrivalBlock` in the event `NextLogicDefined`. - -### Propose Block - -Storage slot `0x4b50776e56454fad8a52805daac1d9fd77ef59e4f1a053c342aaae5568af1388` (obtained as `bytes32(uint256(keccak256('eip3561.proxy.propose.block')) - 1)`). -Defines after/on which block *proposing* next logic is possible. Required for convenience, for example can be manually set to a year from given time. Can be set to maximum number to completely seal the code. -Admin interactions with this slot correspond with this method and event: - -```solidity -function prolongLock(uint b) external onlyAdmin; -event ProposingUpgradesRestrictedUntil(uint block, uint nextProposedLogicEarliestArrival); -``` - -### Zero Trust Period - -Storage slot `0x7913203adedf5aca5386654362047f05edbd30729ae4b0351441c46289146720` (obtained as `bytes32(uint256(keccak256('eip3561.proxy.zero.trust.period')) - 1)`). -Zero Trust Period in amount of blocks, can only be set higher than previous value. While it is at default value(0), the proxy operates exactly as standard EIP-1967 transparent proxy. After zero trust period is set, all above specification is enforced. -Admin interactions with this slot should correspond with this method and event: - -```solidity -function setZeroTrustPeriod(uint blocks) external onlyAdmin; -event ZeroTrustPeriodSet(uint blocks); -``` - -### Implementation Example - -```solidity -pragma solidity >=0.8.0; //important - -// EIP-3561 trust minimized proxy implementation https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3561.md -// Based on EIP-1967 upgradeability proxy: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1967.md - -contract TrustMinimizedProxy { - event Upgraded(address indexed toLogic); - event AdminChanged(address indexed previousAdmin, address indexed newAdmin); - event NextLogicDefined(address indexed nextLogic, uint earliestArrivalBlock); - event ProposingUpgradesRestrictedUntil(uint block, uint nextProposedLogicEarliestArrival); - event NextLogicCanceled(); - event ZeroTrustPeriodSet(uint blocks); - - bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; - bytes32 internal constant LOGIC_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - bytes32 internal constant NEXT_LOGIC_SLOT = 0x19e3fabe07b65998b604369d85524946766191ac9434b39e27c424c976493685; - bytes32 internal constant NEXT_LOGIC_BLOCK_SLOT = 0xe3228ec3416340815a9ca41bfee1103c47feb764b4f0f4412f5d92df539fe0ee; - bytes32 internal constant PROPOSE_BLOCK_SLOT = 0x4b50776e56454fad8a52805daac1d9fd77ef59e4f1a053c342aaae5568af1388; - bytes32 internal constant ZERO_TRUST_PERIOD_SLOT = 0x7913203adedf5aca5386654362047f05edbd30729ae4b0351441c46289146720; - - constructor() payable { - require( - ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) && - LOGIC_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) && - NEXT_LOGIC_SLOT == bytes32(uint256(keccak256('eip3561.proxy.next.logic')) - 1) && - NEXT_LOGIC_BLOCK_SLOT == bytes32(uint256(keccak256('eip3561.proxy.next.logic.block')) - 1) && - PROPOSE_BLOCK_SLOT == bytes32(uint256(keccak256('eip3561.proxy.propose.block')) - 1) && - ZERO_TRUST_PERIOD_SLOT == bytes32(uint256(keccak256('eip3561.proxy.zero.trust.period')) - 1) - ); - _setAdmin(msg.sender); - } - - modifier IfAdmin() { - if (msg.sender == _admin()) { - _; - } else { - _fallback(); - } - } - - function _logic() internal view returns (address logic) { - assembly { - logic := sload(LOGIC_SLOT) - } - } - - function _nextLogic() internal view returns (address nextLogic) { - assembly { - nextLogic := sload(NEXT_LOGIC_SLOT) - } - } - - function _proposeBlock() internal view returns (uint b) { - assembly { - b := sload(PROPOSE_BLOCK_SLOT) - } - } - - function _nextLogicBlock() internal view returns (uint b) { - assembly { - b := sload(NEXT_LOGIC_BLOCK_SLOT) - } - } - - function _zeroTrustPeriod() internal view returns (uint ztp) { - assembly { - ztp := sload(ZERO_TRUST_PERIOD_SLOT) - } - } - - function _admin() internal view returns (address adm) { - assembly { - adm := sload(ADMIN_SLOT) - } - } - - function _setAdmin(address newAdm) internal { - assembly { - sstore(ADMIN_SLOT, newAdm) - } - } - - function changeAdmin(address newAdm) external IfAdmin { - emit AdminChanged(_admin(), newAdm); - _setAdmin(newAdm); - } - - function upgrade(bytes calldata data) external IfAdmin { - require(block.number >= _nextLogicBlock(), 'too soon'); - address logic; - assembly { - logic := sload(NEXT_LOGIC_SLOT) - sstore(LOGIC_SLOT, logic) - } - (bool success, ) = logic.delegatecall(data); - require(success, 'failed to call'); - emit Upgraded(logic); - } - - fallback() external payable { - _fallback(); - } - - receive() external payable { - _fallback(); - } - - function _fallback() internal { - require(msg.sender != _admin()); - _delegate(_logic()); - } - - function cancelUpgrade() external IfAdmin { - address logic; - assembly { - logic := sload(LOGIC_SLOT) - sstore(NEXT_LOGIC_SLOT, logic) - } - emit NextLogicCanceled(); - } - - function prolongLock(uint b) external IfAdmin { - require(b > _proposeBlock(), 'can be only set higher'); - assembly { - sstore(PROPOSE_BLOCK_SLOT, b) - } - emit ProposingUpgradesRestrictedUntil(b, b + _zeroTrustPeriod()); - } - - function setZeroTrustPeriod(uint blocks) external IfAdmin { - // before this set at least once acts like a normal eip 1967 transparent proxy - uint ztp; - assembly { - ztp := sload(ZERO_TRUST_PERIOD_SLOT) - } - require(blocks > ztp, 'can be only set higher'); - assembly { - sstore(ZERO_TRUST_PERIOD_SLOT, blocks) - } - _updateNextBlockSlot(); - emit ZeroTrustPeriodSet(blocks); - } - - function _updateNextBlockSlot() internal { - uint nlb = block.number + _zeroTrustPeriod(); - assembly { - sstore(NEXT_LOGIC_BLOCK_SLOT, nlb) - } - } - - function _setNextLogic(address nl) internal { - require(block.number >= _proposeBlock(), 'too soon'); - _updateNextBlockSlot(); - assembly { - sstore(NEXT_LOGIC_SLOT, nl) - } - emit NextLogicDefined(nl, block.number + _zeroTrustPeriod()); - } - - function proposeTo(address newLogic, bytes calldata data) external payable IfAdmin { - if (_zeroTrustPeriod() == 0 || _logic() == address(0)) { - _updateNextBlockSlot(); - assembly { - sstore(LOGIC_SLOT, newLogic) - } - (bool success, ) = newLogic.delegatecall(data); - require(success, 'failed to call'); - emit Upgraded(newLogic); - } else { - _setNextLogic(newLogic); - } - } - - function _delegate(address logic_) internal { - assembly { - calldatacopy(0, 0, calldatasize()) - let result := delegatecall(gas(), logic_, 0, calldatasize(), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } -} -``` - -## Rationale - -An argument "just don't make such contracts upgadeable at all" fails when it comes to complex systems which do or do not heavily rely on human factor, which might manifest itself in unprecedented ways. It might be impossible to model some systems right on first try. Using decentralized governance for upgrade management coupled with EIP-1967 proxy might become a serious bottleneck for certain protocols before they mature and data is at hand. - -A proxy without a time delay before an actual upgrade is obviously abusable. A time delay is probably unavoidable, even if it means that inexperienced developers might not have confidence using it. Albeit this is a downside of this EIP, it's a critically important option to have in smart contract development today. - -## Security Considerations - -Users must ensure that a trust-minimized proxy they interact with does not allow overflows, ideally represents the exact copy of the code in implementation example above, and also they must ensure that Zero Trust Period length is reasonable(at the very least two weeks if upgrades are usually being revealed beforehand, and in most cases at least a month). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3561.md diff --git a/EIPS/eip-3569.md b/EIPS/eip-3569.md index e2419c3d9f8f7b..510ddbf4c46834 100644 --- a/EIPS/eip-3569.md +++ b/EIPS/eip-3569.md @@ -1,139 +1 @@ ---- -eip: 3569 -title: Sealed NFT Metadata Standard -author: Sean Papanikolas (@pizzarob) -discussions-to: https://ethereum-magicians.org/t/eip-3569-sealed-nft-metadata-standard/7130 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-05-07 ---- - -## Simple Summary - -The Sealed NFT Metadata Extension provides a mechanism to immortalize NFT metadata in a cost-effective manner. - -## Abstract - -This standard accomplishes three things; it provides a way for potential collectors to verify that the NFT metadata will not change, allows creators to immortalize metadata for multiple tokens at one time, and allows metadata for many NFTs to be read and cached from one file. A creator can call the `seal` function for a range of one or many sequential NFTs. Included as an argument is a URI which points to a decentralized storage service like IPFS and will be stored in the smart contract. The URI will return a JSON object in which the keys are token IDs and the values are either a string which is a URI pointing to a metadata file stored on a decentralized file system, or raw metadata JSON for each token ID. The token ID(s) will then be marked as sealed in the smart contract and cannot be sealed again. The `seal` function can be called after NFT creation, or during the NFT creation process. - -## Motivation - -In the original ERC-721 standard, the metadata extension specifies a `tokenURI` function which returns a URI for a single token ID. This may be hosted on IPFS or might be hosted on a centralized server. There's no guarantee that the NFT metadata will not change. This is the same for the ERC-1155 metadata extension. In addition to that - if you want to update the metadata for many NFTs you would need to do so in O(n) time, which as we know is not financially feasible at scale. By allowing for a decentralized URI to point to a JSON object of many NFT IDs we can solve this issue by providing metadata for many tokens at one time rather than one at a time. We can also provide methods which give transparency into whether the NFT has be explicitly "sealed" and that the metadata is hosted on a decentralized storage space. - -There is not a way for the smart contract layer to communicate with a storage layer and as such we need a solution which provides a way for potential NFT collectors on Ethereum to verify that their NFT will not be "rug pulled". This standard provides a solution for that. By allowing creators to seal their NFTs during or after creation, they are provided with full flexibility when it comes to creating their NFTs. Decentralized storage means permanence - in the fast-moving world of digital marketing campaigns, or art projects mistakes can happen. As such, it is important for creators to have flexibility when creating their projects. Therefore, this standard allows creators to opt in at a time of their choosing. Mistakes do happen and metadata should be flexible enough so that creators can fix mistakes or create dynamic NFTs (see Beeple's CROSSROAD NFT). If there comes a time when the NFT metadata should be immortalized, then the creator can call the `seal` method. Owners, potential owners, or platforms can verify that the NFT was sealed and can check the returned URI. If the `sealedURI` return value is not hosted on a decentralized storage platform, or the `isSealed` method does not return `true` for the given NFT ID then it can be said that one cannot trust that these NFTs will not change at a future date and can then decide if they want to proceed with collecting the given NFT. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -``` -interface SealedMetadata { - /** - @notice This function is used to set a sealed URI for the given range of tokens. - @dev - - If the sealed URI is being set for one token then the fromTokenId and toTokenId - values MUST be the same. - - - If any token within the range of tokens specified has already - been sealed then this function MUST throw. - - - This function MAY be called at the time of NFT creation, or after the NFTs have been created. - - - It is RECOMMENDED that this function only be executable by either the creator of the smart contract, - or the creator of the NFTs, but this is OPTIONAL and should be implemented based on use case. - - - This function MUST emit the Sealed event - - - The URI argument SHOULD point to a JSON file hosted within a decentralized file system like IPFS - - @param fromTokenId The first token in a consecutive range of tokens - @param toTokenId The ending token in a consecutive range of tokens - @param uri A URI which points to a JSON file hosted on a decentralized file system. - */ - function seal(uint256 fromTokenId, uint256 toTokenId, string memory uri) external; - - /** - @notice This function returns the URI which the sealed metadata can be found for the given token ID - @dev - - This function MUST throw if the token ID does not exist, or is not sealed - - @param tokenId Token ID to retrieve the sealed URI for - - @return The sealed URI in which the metadata for the given token ID can be found - */ - function sealedURI(uint256 tokenId) external view returns (string); - - /** - @notice This function returns a boolean stating if the token ID is sealed or not - @dev This function should throw if the token ID does not exist - - @param tokenId The token ID that will be checked if sealed or not - - @return Boolean stating if token ID is sealed - */ - function isSealed(uint256 tokenId) external view returns (bool) - - /// @dev This emits when a range of tokens is sealed - event Sealed(uint256 indexed fromTokenId, uint256 indexed toTokenId, string memory uri); - -} -``` - -### Sealed Metadata JSON Format - -The sealed metadata JSON file MAY contain metadata for many different tokens. The top level keys of the JSON object MUST be token IDs. - -``` - -type ERC721Metadata = { - name?: string; - image?: string; - description?: string; -} - -type SealedMetaDataJson = { - [tokenId: string]: string | ERC721Metadata; -} - -const sealedMetadata: SealedMetaDataJson = { - '1': { - name: 'Metadata for token with ID 1' - }, - '2': { - name: 'Metadata for token with ID 2' - }, - // Example pointing to another file - '3': 'ipfs://SOME_HASH_ON_IPFS' -}; -``` - -## Rationale - -**Rationale for rule not explicitly requiring that sealed URI be hosted on decentralized filestorage** - -In order for this standard to remain future proof there is no validation within the smart contract that would verify the sealed URI is hosted on IPFS or another decentralized file storage system. The standard allows potential collectors and platforms to validate the URI on the client. - -**Rationale to include many NFT metadata objects, or URIs in one JSON file** - -By including metadata for many NFTs in one JSON file we can eliminate the need for many transactions to set the metadata for multiple NFTs. Given that this file should not change NFT platforms, or explorers can cache the metadata within the file. - -**Rationale for emitting `Sealed` event** - -Platforms and explorers can use the `Sealed` event to automatically cache metadata, or update information regarding specified NFTs. - -**Rationale for allowing URIs as values in the JSON file** - -If a token's metadata is very large, or there are many tokens you can save file space by referencing another URI rather than storing the metadata JSON within the top level metadata file. - -## Backwards Compatibility - -There is no backwards compatibility with existing standards. This is an extension which could be added to existing NFT standards. - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3569.md diff --git a/EIPS/eip-3589.md b/EIPS/eip-3589.md index 1bfeb2edead5bd..d21a2b82551ee2 100644 --- a/EIPS/eip-3589.md +++ b/EIPS/eip-3589.md @@ -1,198 +1 @@ ---- -eip: 3589 -title: Assemble assets into NFTs -author: Zhenyu Sun (@Ungigdu), Xinqi Yang (@xinqiyang) -discussions-to: https://github.com/ethereum/EIPs/issues/3590 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-05-24 -requires: 721 ---- - -## Simple Summary -This standard defines a ERC-721 token called assembly token which can represent a combination of assets. - -## Abstract -The ERC-1155 multi-token contract defines a way to batch transfer tokens, but those tokens must be minted by the ERC-1155 contract itself. This EIP is an ERC-721 extension with ability to assemble assets such as ether, ERC-20 tokens, ERC-721 tokens and ERC-1155 tokens into one ERC-721 token whose token id is also the asset's signature. As assets get assembled into one, batch transfer or swap can be implemented very easily. - -## Motivation -As NFT arts and collectors rapidly increases, some collectors are not satisfied with traditional trading methods. When two collectors want to swap some of their collections, currently they can list their NFTs on the market and notify the other party to buy, but this is inefficient and gas-intensive. Instead, some collectors turn to social media or chat group looking for a trustworthy third party to swap NFTs for them. The third party takes NFTs from both collector A and B, and transfer A's collections to B and B's to A. This is very risky. - -The safest way to do batch swap, is to transform batch swap into atomic swap, i.e. one to one swap. But first we should "assemble" those ether, ERC-20 tokens, ERC-721 tokens and ERC-1155 tokens together, and this is the main purpose of this EIP. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -ERC-721 compliant contracts MAY implement this ERC to provide a standard method to assemble assets. - -`mint` and `safeMint` assemble assets into one ERC-721 token. `mint` SHOULD be implemented for normal ERC-20 tokens whose `_transfer` is lossless. `safeMint` MUST takes care for lossy token such as PIG token whose `_transfer` function is taxed. - -`_salt` of `hash` function MAY be implemented other way, even provided as user input. But the token id MUST be generated by `hash` function. - -Implementations of the standard MAY supports different set of assets. - -Implementers of this standard MUST have all of the following functions: - -``` -pragma solidity ^0.8.0; - -interface AssemblyNFTInterface { - - event AssemblyAsset(address indexed firstHolder, - uint256 indexed tokenId, - uint256 salt, - address[] addresses, - uint256[] numbers); - - /** - * @dev hash function assigns the combination of assets with salt to bytes32 signature that is also the token id. - * @param `_salt` prevents hash collision, can be chosen by user input or increasing nonce from contract. - * @param `_addresses` concat assets addresses, e.g. [ERC-20_address1, ERC-20_address2, ERC-721_address_1, ERC-1155_address_1, ERC-1155_address_2] - * @param `_numbers` describes how many eth, ERC-20 token addresses length, ERC-721 token addresses length, ERC-1155 token addresses length, - * ERC-20 token amounts, ERC-721 token ids, ERC-1155 token ids and amounts. - */ - function hash(uint256 _salt, address[] memory _addresses, uint256[] memory _numbers) external pure returns (uint256 tokenId); - - /// @dev to assemble lossless assets - /// @param `_to` the receiver of the assembly token - function mint(address _to, address[] memory _addresses, uint256[] memory _numbers) payable external returns(uint256 tokenId); - - /// @dev mint with additional logic that calculates the actual received value for tokens. - function safeMint(address _to, address[] memory _addresses, uint256[] memory _numbers) payable external returns(uint256 tokenId); - - /// @dev burn this token and releases assembled assets - /// @param `_to` to which address the assets is released - function burn(address _to, uint256 _tokenId, uint256 _salt, address[] calldata _addresses, uint256[] calldata _numbers) external; - -} - -``` - -## Rationale -There are many reasons why people want to pack their NFTs together. For example, a collector want to pack a set of football players into a football team; a collector has hundreds of of NFTs with no categories to manage them; a collector wants to buy a full collection of NFTs or none of them. They all need a way a assemble those NFTs together. - -The reason for choosing ERC-721 standard as a wrapper is ERC-721 token is already widely used and well supported by NFT wallets. And assembly token itself can also be assembled again. Assembly token is easier for smart contract to use than a batch of assets, in scenarios like batch trade, batch swap or collections exchange. - -This standard has AssemblyAsset event which records the exact kinds and amounts of assets the assembly token represents. The wallet can easily display those NFTs to user just by the token id. - -## Backwards Compatibility -This proposal combines already available 721 extensions and is backwards compatible with the ERC-721 standard. - -## Implementation -``` -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import "./AssemblyNFTInterface.sol"; - -abstract contract AssemblyNFT is ERC721, ERC721Holder, ERC1155Holder, AssemblyNFTInterface{ - using SafeERC20 for IERC20; - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC1155Receiver) returns (bool) { - return ERC721.supportsInterface(interfaceId) || ERC1155Receiver.supportsInterface(interfaceId); - } - - uint256 nonce; - - /** - * layout of _addresses: - * erc20 addresses | erc721 addresses | erc1155 addresses - * layout of _numbers: - * eth | erc20.length | erc721.length | erc1155.length | erc20 amounts | erc721 ids | erc1155 ids | erc1155 amounts - */ - - function hash(uint256 _salt, address[] memory _addresses, uint256[] memory _numbers) public pure override returns (uint256 tokenId){ - bytes32 signature = keccak256(abi.encodePacked(_salt)); - for(uint256 i=0; i< _addresses.length; i++){ - signature = keccak256(abi.encodePacked(signature, _addresses[i])); - } - for(uint256 j=0; j<_numbers.length; j++){ - signature = keccak256(abi.encodePacked(signature, _numbers[j])); - } - assembly { - tokenId := signature - } - } - - function mint(address _to, address[] memory _addresses, uint256[] memory _numbers) payable external override returns(uint256 tokenId){ - require(_to != address(0), "can't mint to address(0)"); - require(msg.value == _numbers[0], "value not match"); - require(_addresses.length == _numbers[1] + _numbers[2] + _numbers[3], "2 array length not match"); - require(_addresses.length == _numbers.length -4 - _numbers[3], "numbers length not match"); - uint256 pointerA; //points to first erc20 address, if there is any - uint256 pointerB =4; //points to first erc20 amount, if there is any - for(uint256 i = 0; i< _numbers[1]; i++){ - require(_numbers[pointerB] > 0, "transfer erc20 0 amount"); - IERC20(_addresses[pointerA++]).safeTransferFrom(_msgSender(), address(this), _numbers[pointerB++]); - } - for(uint256 j = 0; j< _numbers[2]; j++){ - IERC721(_addresses[pointerA++]).safeTransferFrom(_msgSender(), address(this), _numbers[pointerB++]); - } - for(uint256 k =0; k< _numbers[3]; k++){ - IERC1155(_addresses[pointerA++]).safeTransferFrom(_msgSender(), address(this), _numbers[pointerB], _numbers[_numbers[3] + pointerB++], ""); - } - tokenId = hash(nonce, _addresses, _numbers); - super._mint(_to, tokenId); - emit AssemblyAsset(_to, tokenId, nonce, _addresses, _numbers); - nonce ++; - } - - function safeMint(address _to, address[] memory _addresses, uint256[] memory _numbers) payable external override returns(uint256 tokenId){ - require(_to != address(0), "can't mint to address(0)"); - require(msg.value == _numbers[0], "value not match"); - require(_addresses.length == _numbers[1] + _numbers[2] + _numbers[3], "2 array length not match"); - require(_addresses.length == _numbers.length -4 - _numbers[3], "numbers length not match"); - uint256 pointerA; //points to first erc20 address, if there is any - uint256 pointerB =4; //points to first erc20 amount, if there is any - for(uint256 i = 0; i< _numbers[1]; i++){ - require(_numbers[pointerB] > 0, "transfer erc20 0 amount"); - IERC20 token = IERC20(_addresses[pointerA++]); - uint256 orgBalance = token.balanceOf(address(this)); - token.safeTransferFrom(_msgSender(), address(this), _numbers[pointerB]); - _numbers[pointerB++] = token.balanceOf(address(this)) - orgBalance; - } - for(uint256 j = 0; j< _numbers[2]; j++){ - IERC721(_addresses[pointerA++]).safeTransferFrom(_msgSender(), address(this), _numbers[pointerB++]); - } - for(uint256 k =0; k< _numbers[3]; k++){ - IERC1155(_addresses[pointerA++]).safeTransferFrom(_msgSender(), address(this), _numbers[pointerB], _numbers[_numbers[3] + pointerB++], ""); - } - tokenId = hash(nonce, _addresses, _numbers); - super._mint(_to, tokenId); - emit AssemblyAsset(_to, tokenId, nonce, _addresses, _numbers); - nonce ++; - } - - function burn(address _to, uint256 _tokenId, uint256 _salt, address[] calldata _addresses, uint256[] calldata _numbers) override external { - require(_msgSender() == ownerOf(_tokenId), "not owned"); - require(_tokenId == hash(_salt, _addresses, _numbers)); - super._burn(_tokenId); - payable(_to).transfer(_numbers[0]); - uint256 pointerA; //points to first erc20 address, if there is any - uint256 pointerB =4; //points to first erc20 amount, if there is any - for(uint256 i = 0; i< _numbers[1]; i++){ - require(_numbers[pointerB] > 0, "transfer erc20 0 amount"); - IERC20(_addresses[pointerA++]).safeTransfer(_to, _numbers[pointerB++]); - } - for(uint256 j = 0; j< _numbers[2]; j++){ - IERC721(_addresses[pointerA++]).safeTransferFrom(address(this), _to, _numbers[pointerB++]); - } - for(uint256 k =0; k< _numbers[3]; k++){ - IERC1155(_addresses[pointerA++]).safeTransferFrom(address(this), _to, _numbers[pointerB], _numbers[_numbers[3] + pointerB++], ""); - } - } - -} -``` - -## Security Considerations -Before using `mint` or `safeMint` functions, user should be aware that some implementations of tokens are pausable. If one of the assets get paused after assembled into one NFT, the `burn` function may not be executed successfully. Platforms using this standard should make support lists or block lists to avoid this situation. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3589.md diff --git a/EIPS/eip-3643.md b/EIPS/eip-3643.md index 68efd27186b544..e6df20fe84eab3 100644 --- a/EIPS/eip-3643.md +++ b/EIPS/eip-3643.md @@ -1,408 +1 @@ ---- -eip: 3643 -title: T-REX - Token for Regulated EXchanges -description: An institutional grade security token contract that provides interfaces for the management and compliant transfer of security tokens. -author: Joachim Lebrun (@Joachim-Lebrun), Tony Malghem (@TonyMalghem), Kevin Thizy (@Nakasar), Luc Falempin (@lfalempin), Adam Boudjemaa (@Aboudjem) -discussions-to: https://ethereum-magicians.org/t/eip-3643-proposition-of-the-t-rex-token-standard-for-securities/6844 -status: Review -type: Standards Track -category: ERC -created: 2021-07-09 -requires: 20, 173 ---- - -## Abstract - -The T-REX token is an institutional grade security token standard. This standard provides a library of interfaces for the management and compliant transfer of security tokens, using an automated onchain validator system leveraging onchain identities for eligibility checks. - -The standard defines several interfaces that are described hereunder: - -- Token -- Identity Registry -- Identity Registry Storage -- Compliance -- Trusted Issuers Registry -- Claim Topics Registry - -## Motivation - -The advent of blockchain technology has brought about a new era of efficiency, accessibility, and liquidity in the world of asset transfer. This is particularly evident in the realm of cryptocurrencies, where users can transfer token ownership peer-to-peer without intermediaries. However, when it comes to tokenized securities or security tokens, the situation is more complex due to the need for compliance with securities laws. These tokens cannot be permissionless like utility tokens; they must be permissioned to track ownership and ensure that only eligible investors can hold tokens. - -The existing Ethereum protocol, while powerful and versatile, does not fully address the unique challenges posed by security tokens. There is a need for a standard that supports compliant issuance and management of permissioned tokens, suitable for representing a wide range of asset classes, including small businesses and real estate. - -The proposed [ERC-3643](./eip-3643.md) standard is motivated by this need. It aims to provide a comprehensive framework for managing the lifecycle of security tokens, from issuance to transfers between eligible investors, while enforcing compliance rules at every stage. The standard also supports additional features such as token pausing and freezing, which can be used to manage the token in response to regulatory requirements or changes in the status of the token or its holders. - -Moreover, the standard is designed to work in conjunction with an on-chain Identity system, allowing for the validation of the identities and credentials of investors through signed attestations issued by trusted claim issuers. This ensures compliance with legal and regulatory requirements for the trading of security tokens. - -In summary, the motivation behind the proposed standard is to bring the benefits of blockchain technology to the world of securities, while ensuring compliance with existing securities laws. It aims to provide a robust, flexible, and efficient framework for the issuance and management of security tokens, thereby accelerating the evolution of capital markets. - -## Specification - -The proposed standard has the following requirements: - -- **MUST** be [ERC-20](./eip-20.md) compatible. -- **MUST** be used in combination with an onchain Identity system -- **MUST** be able to apply any rule of compliance that is required by the regulator or by the token issuer (about the factors of eligibility of an identity or about the rules of the token itself) -- **MUST** have a standard interface to pre-check if a transfer is going to pass or fail before sending it to the blockchain -- **MUST** have a recovery system in case an investor loses access to his private key -- **MUST** be able to freeze tokens on the wallet of investors if needed, partially or totally -- **MUST** have the possibility to pause the token -- **MUST** be able to mint and burn tokens -- **MUST** define an Agent role and an Owner (token issuer) role -- **MUST** be able to force transfers from an Agent wallet -- **MUST** be able to issue transactions in batch (to save gas and to have all the transactions performed in the same block) - -While this standard is backwards compatible with ERC-20 and all ERC-20 functions can be called on an ERC-3643 token, the implementation of these functions differs due to the permissioned nature of ERC-3643. Each token transfer under this standard involves a compliance check to validate the transfer and the eligibility of the stakeholder’s identities. - -### Agent Role Interface - -The standard defines an Agent role, which is crucial for managing access to various functions of the smart contracts. The interface for the Agent role is as follows: - -```solidity -interface IAgentRole { - - // events - event AgentAdded(address indexed _agent); - event AgentRemoved(address indexed _agent); - - // functions - // setters - function addAgent(address _agent) external; - function removeAgent(address _agent) external; - - // getters - function isAgent(address _agent) external view returns (bool); -} - ``` - -The `IAgentRole` interface allows for the addition and removal of agents, as well as checking if an address is an agent. In this standard, it is the owner role, as defined by [ERC-173](./eip-173.md), that has the responsibility of appointing and removing agents. Any contract that fulfills the role of a Token contract or an Identity Registry within the context of this standard must be compatible with the `IAgentRole` interface. - -### Main functions - -#### Transfer - -To be able to perform a transfer on T-REX you need to fulfill several conditions : - -- The sender **MUST** hold enough free balance (total balance - frozen tokens, if any) -- The receiver **MUST** be whitelisted on the Identity Registry and verified (hold the necessary claims on his onchain Identity) -- The sender's wallet **MUST NOT** be frozen -- The receiver's wallet **MUST NOT** be frozen -- The token **MUST NOT** be paused -- The transfer **MUST** respect all the rules of compliance defined in the Compliance smart contract (canTransfer needs to return TRUE) - -Here is an example of `transfer` function implementation : - -```solidity -function transfer(address _to, uint256 _amount) public override whenNotPaused returns (bool) { - require(!_frozen[_to] && !_frozen[msg.sender], "ERC-3643: Frozen wallet"); - require(_amount <= balanceOf(msg.sender) - (_frozenTokens[msg.sender]), "ERC-3643: Insufficient Balance"); - require( _tokenIdentityRegistry.isVerified(to), "ERC-3643: Invalid identity" ); - require( _tokenCompliance.canTransfer(from, to, amount), "ERC-3643: Compliance failure" ); - _transfer(msg.sender, _to, _amount); - _tokenCompliance.transferred(msg.sender, _to, _amount); - return true; - } - ``` - -The `transferFrom` function works the same way while the `mint` function and the `forcedTransfer` function only require the receiver to be whitelisted and verified on the Identity Registry (they bypass the compliance rules). The `burn` function bypasses all checks on eligibility. - -#### isVerified - -The `isVerified` function is called from within the transfer functions `transfer`, `transferFrom`, `mint` and -`forcedTransfer` to instruct the `Identity Registry` to check if the receiver is a valid investor, i.e. if his -wallet address is in the `Identity Registry` of the token, and if the `Identity`contract linked to his wallet -contains the claims (see [Claim Holder](../assets/eip-3643/ONCHAINID/IERC735.sol)) required in the `Claim Topics Registry` and -if these claims are signed by an authorized Claim Issuer as required in the `Trusted Issuers Registry`. -If all the requirements are fulfilled, the `isVerified` function returns `TRUE`, otherwise it returns `FALSE`. An -implementation of this function can be found on the T-REX repository of Tokeny. - -#### canTransfer - -The `canTransfer` function is also called from within transfer functions. This function checks if the transfer is compliant with global compliance rules applied to the token, in opposition with `isVerified` that only checks the eligibility of an investor to hold and receive tokens, the `canTransfer` function is looking at global compliance rules, e.g. check if the transfer is compliant in the case there is a fixed maximum number of token holders to respect (can be a limited number of holders per country as well), check if the transfer respects rules setting a maximum amount of tokens per investor, ... -If all the requirements are fulfilled, the `canTransfer` function will return `TRUE` otherwise it will return -`FALSE` and the transfer will not be allowed to happen. An implementation of this function can be found on the T-REX -repository of Tokeny. - -#### Other functions - -Description of other functions of the ERC-3643 can be found in the `interfaces` folder. An implementation of the -ERC-3643 suite of smart contracts can be found on the T-REX repository of Tokeny. - -### Token interface - -ERC-3643 permissioned tokens build upon the standard ERC-20 structure, but with additional functions to ensure compliance in the transactions of the security tokens. The functions `transfer` and `transferFrom` are implemented in a conditional way, allowing them to proceed with a transfer only if the transaction is valid. The permissioned tokens are allowed to be transferred only to validated counterparties, in order to avoid tokens being held in wallets/Identity contracts of ineligible/unauthorized investors. The ERC-3643 standard also supports the recovery of security tokens in case an investor loses access to their wallet private key. A history of recovered tokens is maintained on the blockchain for transparency reasons. - -ERC-3643 tokens implement a range of additional functions to enable the owner or their appointed agents to manage supply, transfer rules, lockups, and any other requirements in the management of a security. The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents. Any contract that fulfills the role of a Token contract within the context of this standard must be compatible with the `IAgentRole` interface. - -A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IERC3643.sol). - -```solidity -interface IERC3643 is IERC20 { - - // events - event UpdatedTokenInformation(string _newName, string _newSymbol, uint8 _newDecimals, string _newVersion, address _newOnchainID); - event IdentityRegistryAdded(address indexed _identityRegistry); - event ComplianceAdded(address indexed _compliance); - event RecoverySuccess(address _lostWallet, address _newWallet, address _investorOnchainID); - event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner); - event TokensFrozen(address indexed _userAddress, uint256 _amount); - event TokensUnfrozen(address indexed _userAddress, uint256 _amount); - event Paused(address _userAddress); - event Unpaused(address _userAddress); - - - // functions - // getters - function onchainID() external view returns (address); - function version() external view returns (string memory); - function identityRegistry() external view returns (IIdentityRegistry); - function compliance() external view returns (ICompliance); - function paused() external view returns (bool); - function isFrozen(address _userAddress) external view returns (bool); - function getFrozenTokens(address _userAddress) external view returns (uint256); - - // setters - function setName(string calldata _name) external; - function setSymbol(string calldata _symbol) external; - function setOnchainID(address _onchainID) external; - function pause() external; - function unpause() external; - function setAddressFrozen(address _userAddress, bool _freeze) external; - function freezePartialTokens(address _userAddress, uint256 _amount) external; - function unfreezePartialTokens(address _userAddress, uint256 _amount) external; - function setIdentityRegistry(address _identityRegistry) external; - function setCompliance(address _compliance) external; - - // transfer actions - function forcedTransfer(address _from, address _to, uint256 _amount) external returns (bool); - function mint(address _to, uint256 _amount) external; - function burn(address _userAddress, uint256 _amount) external; - function recoveryAddress(address _lostWallet, address _newWallet, address _investorOnchainID) external returns (bool); - - // batch functions - function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external; - function batchForcedTransfer(address[] calldata _fromList, address[] calldata _toList, uint256[] calldata _amounts) external; - function batchMint(address[] calldata _toList, uint256[] calldata _amounts) external; - function batchBurn(address[] calldata _userAddresses, uint256[] calldata _amounts) external; - function batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external; - function batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; - function batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; -} - -``` - -### Identity Registry Interface - -The Identity Registry is linked to storage that contains a dynamic whitelist of identities. It establishes the link between a wallet address, an Identity smart contract, and a country code corresponding to the investor's country of residence. This country code is set in accordance with the ISO-3166 standard. The Identity Registry also includes a function called `isVerified()`, which returns a status based on the validity of claims (as per the security token requirements) in the user’s Identity contract. - -The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents. Any contract that fulfills the role of an Identity Registry within the context of this standard must be compatible with the `IAgentRole` interface. The Identity Registry is managed by the agent wallet(s), meaning only the agent(s) can add or remove identities in the registry. Note that the agent role on the Identity Registry is set by the owner, therefore the owner could set themselves as the agent if they want to maintain full control. There is a specific identity registry for each security token. - -A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IIdentityRegistry.sol). - -Note that [`IClaimIssuer`](../assets/eip-3643/ONCHAINID/IClaimIssuer.sol) and [`IIdentity`](../assets/eip-3643/ONCHAINID/IIdentity.sol) are needed in this interface as they are required for the Identity eligibility checks. - -```solidity -interface IIdentityRegistry { - - - // events - event ClaimTopicsRegistrySet(address indexed claimTopicsRegistry); - event IdentityStorageSet(address indexed identityStorage); - event TrustedIssuersRegistrySet(address indexed trustedIssuersRegistry); - event IdentityRegistered(address indexed investorAddress, IIdentity indexed identity); - event IdentityRemoved(address indexed investorAddress, IIdentity indexed identity); - event IdentityUpdated(IIdentity indexed oldIdentity, IIdentity indexed newIdentity); - event CountryUpdated(address indexed investorAddress, uint16 indexed country); - - - // functions - // identity registry getters - function identityStorage() external view returns (IIdentityRegistryStorage); - function issuersRegistry() external view returns (ITrustedIssuersRegistry); - function topicsRegistry() external view returns (IClaimTopicsRegistry); - - //identity registry setters - function setIdentityRegistryStorage(address _identityRegistryStorage) external; - function setClaimTopicsRegistry(address _claimTopicsRegistry) external; - function setTrustedIssuersRegistry(address _trustedIssuersRegistry) external; - - // registry actions - function registerIdentity(address _userAddress, IIdentity _identity, uint16 _country) external; - function deleteIdentity(address _userAddress) external; - function updateCountry(address _userAddress, uint16 _country) external; - function updateIdentity(address _userAddress, IIdentity _identity) external; - function batchRegisterIdentity(address[] calldata _userAddresses, IIdentity[] calldata _identities, uint16[] calldata _countries) external; - - // registry consultation - function contains(address _userAddress) external view returns (bool); - function isVerified(address _userAddress) external view returns (bool); - function identity(address _userAddress) external view returns (IIdentity); - function investorCountry(address _userAddress) external view returns (uint16); -} -``` - -### Identity Registry Storage Interface - -The Identity Registry Storage stores the identity addresses of all the authorized investors in the security token(s) linked to the storage contract. These are all identities of investors who have been authorized to hold the token(s) after having gone through the appropriate KYC and eligibility checks. The Identity Registry Storage can be bound to one or several Identity Registry contract(s). The goal of the Identity Registry storage is to separate the Identity Registry functions and specifications from its storage. This way, it is possible to keep one single Identity Registry contract per token, with its own Trusted Issuers Registry and Claim Topics Registry, but with a shared whitelist of investors used by the `isVerifed()` function implemented in the Identity Registries to check the eligibility of the receiver in a transfer transaction. - -The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents(in this case through the `bindIdentityRegistry` function). Any contract that fulfills the role of an Identity Registry Storage within the context of this standard must be compatible with the `IAgentRole` interface. The Identity Registry Storage is managed by the agent addresses (i.e. the bound Identity Registries), meaning only the agent(s) can add or remove identities in the registry. Note that the agent role on the Identity Registry Storage is set by the owner, therefore the owner could set themselves as the agent if they want to modify the storage manually. Otherwise it is the bound Identity Registries that are using the agent role to write in the Identity Registry Storage. - -A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IIdentityRegistryStorage.sol). - -```solidity -interface IIdentityRegistryStorage { - - //events - event IdentityStored(address indexed investorAddress, IIdentity indexed identity); - event IdentityUnstored(address indexed investorAddress, IIdentity indexed identity); - event IdentityModified(IIdentity indexed oldIdentity, IIdentity indexed newIdentity); - event CountryModified(address indexed investorAddress, uint16 indexed country); - event IdentityRegistryBound(address indexed identityRegistry); - event IdentityRegistryUnbound(address indexed identityRegistry); - - //functions - // storage related functions - function storedIdentity(address _userAddress) external view returns (IIdentity); - function storedInvestorCountry(address _userAddress) external view returns (uint16); - function addIdentityToStorage(address _userAddress, IIdentity _identity, uint16 _country) external; - function removeIdentityFromStorage(address _userAddress) external; - function modifyStoredInvestorCountry(address _userAddress, uint16 _country) external; - function modifyStoredIdentity(address _userAddress, IIdentity _identity) external; - - // role setter - function bindIdentityRegistry(address _identityRegistry) external; - function unbindIdentityRegistry(address _identityRegistry) external; - - // getter for bound IdentityRegistry role - function linkedIdentityRegistries() external view returns (address[] memory); -} -``` - -### Compliance Interface - -The Compliance contract is used to set the rules of the offering itself and ensures these rules are respected during the whole lifecycle of the token. For example, the Compliance contract will define the maximum amount of investors per country, the maximum amount of tokens per investor, and the accepted countries for the circulation of the token (using the country code corresponding to each investor in the Identity Registry). The Compliance smart contract can be either “tailor-made”, following the legal requirements of the token issuer, or can be deployed under a generic modular form, which can then add and remove external compliance `Modules` to fit the legal requirements of the token in the same way as a custom "tailor-made" contract would. - -This contract is triggered at every transaction by the Token and returns `TRUE` if the transaction is compliant with the rules of the offering and `FALSE` otherwise. - -The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of setting the Compliance parameters and binding the Compliance to a Token contract. - -A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/ICompliance.sol). - -```solidity -interface ICompliance { - - // events - event TokenBound(address _token); - event TokenUnbound(address _token); - - // functions - // initialization of the compliance contract - function bindToken(address _token) external; - function unbindToken(address _token) external; - - // check the parameters of the compliance contract - function isTokenBound(address _token) external view returns (bool); - function getTokenBound() external view returns (address); - - // compliance check and state update - function canTransfer(address _from, address _to, uint256 _amount) external view returns (bool); - function transferred(address _from, address _to, uint256 _amount) external; - function created(address _to, uint256 _amount) external; - function destroyed(address _from, uint256 _amount) external; -} -``` - -### Trusted Issuer's Registry Interface - -The Trusted Issuer's Registry stores the contract addresses ([IClaimIssuer](../assets/eip-3643/ONCHAINID/IClaimIssuer.sol)) of all the trusted claim issuers for a specific security token. The Identity contract ([IIdentity](../assets/eip-3643/ONCHAINID/IIdentity.sol)) of token owners (the investors) must have claims signed by the claim issuers stored in this smart contract in order to be able to hold the token. - -The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of managing this registry as per their requirements. This includes the ability to add, remove, and update the list of Trusted Issuers. - -A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol). - -```solidity -interface ITrustedIssuersRegistry { - - // events - event TrustedIssuerAdded(IClaimIssuer indexed trustedIssuer, uint[] claimTopics); - event TrustedIssuerRemoved(IClaimIssuer indexed trustedIssuer); - event ClaimTopicsUpdated(IClaimIssuer indexed trustedIssuer, uint[] claimTopics); - - // functions - // setters - function addTrustedIssuer(IClaimIssuer _trustedIssuer, uint[] calldata _claimTopics) external; - function removeTrustedIssuer(IClaimIssuer _trustedIssuer) external; - function updateIssuerClaimTopics(IClaimIssuer _trustedIssuer, uint[] calldata _claimTopics) external; - - // getters - function getTrustedIssuers() external view returns (IClaimIssuer[] memory); - function isTrustedIssuer(address _issuer) external view returns(bool); - function getTrustedIssuerClaimTopics(IClaimIssuer _trustedIssuer) external view returns(uint[] memory); - function getTrustedIssuersForClaimTopic(uint256 claimTopic) external view returns (IClaimIssuer[] memory); - function hasClaimTopic(address _issuer, uint _claimTopic) external view returns(bool); -} -``` - -### Claim Topics Registry Interface - -The Claim Topics Registry stores all the trusted claim topics for the security token. The Identity contract ([IIdentity](../assets/eip-3643/ONCHAINID/IIdentity.sol)) of token owners must contain claims of the claim topics stored in this smart contract. - -The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of managing this registry as per their requirements. This includes the ability to add and remove required Claim Topics. - -A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IClaimTopicsRegistry.sol). - -```solidity -interface IClaimTopicsRegistry { - - // events - event ClaimTopicAdded(uint256 indexed claimTopic); - event ClaimTopicRemoved(uint256 indexed claimTopic); - - // functions - // setters - function addClaimTopic(uint256 _claimTopic) external; - function removeClaimTopic(uint256 _claimTopic) external; - - // getter - function getClaimTopics() external view returns (uint256[] memory); -} -``` - -## Rationale - -### Transfer Restrictions - -Transfers of securities can fail for a variety of reasons. This is in direct contrast to utility tokens, which generally only require the sender to have a sufficient balance. These conditions can be related to the status of an investor’s wallet, the identity of the sender and receiver of the securities (i.e., whether they have been through a KYC process, whether they are accredited or an affiliate of the issuer) or for reasons unrelated to the specific transfer but instead set at the token level (i.e., the token contract enforces a maximum number of investors or a cap on the percentage held by any single investor). For ERC-20 tokens, the `balanceOf` and `allowance` functions provide a way to check that a transfer is likely to succeed before executing the transfer, which can be executed both on-chain and off-chain. For tokens representing securities, the T-REX standard introduces a function `canTransfer` which provides a more general-purpose way to achieve this. I.e., when the reasons for failure are related to the compliance rules of the token and a function `isVerified` which allows checking the eligibility status of the identity of the investor. Transfers can also fail if the address of the sender and/or receiver is frozen, or if the free balance of the sender (total balance - frozen tokens) is lower than the amount to transfer. Ultimately, the transfer could be blocked if the token is `paused`. - -### Identity Management - -Security and compliance of transfers are enforced through the management of on-chain identities. These include: - -- Identity contract: A unique identifier for each investor, which is used to manage their identity and claims. -- Claim: Signed attestations issued by a trusted claim issuer that confirm certain attributes or qualifications of the token holders, such as their identity, location, investor status, or KYC/AML clearance. -- Identity Storage/Registry: A storage system for all Identity contracts and their associated wallets, which is used to - verify the eligibility of investors during transfers. - -### Token Lifecycle Management - -The T-REX standard provides a comprehensive framework for managing the lifecycle of security tokens. This includes the issuance of tokens, transfers between eligible investors, and the enforcement of compliance rules at every stage of the token's lifecycle. The standard also supports additional features such as token pausing and freezing, which can be used to manage the token in response to regulatory requirements or changes in the status of the token or its holders. - -### Additional Compliance Rules - -The T-REX standard supports the implementation of additional compliance rules through modular compliance. These modules can be used to enforce a wide range of rules and restrictions, such as caps on the number of investors or the percentage of tokens held by a single investor, restrictions on transfers between certain types of investors, and more. This flexibility allows issuers to tailor the compliance rules of their tokens to their specific needs and regulatory environment. - -## Backwards Compatibility - -T-REX tokens should be backwards compatible with ERC-20 and ERC-173 -and should be able to interact with a [Claim Holder contract](../assets/eip-3643/ONCHAINID/IERC735.sol) to validate -the claims linked to an [Identity contract](../assets/eip-3643/ONCHAINID/IIdentity.sol). - - -## Security Considerations - -This specification has been audited by Kapersky and Hacken, and no notable security considerations were found. -While the audits were primarily focused on the specific implementation by Tokeny, they also challenged and validated the core principles of the T-REX standard. The auditing teams approval of these principles provides assurance that the standard itself is robust and does not present any significant security concerns. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3643.md diff --git a/EIPS/eip-3668.md b/EIPS/eip-3668.md index b04d3dc51311c3..9694ed9d6e59dd 100644 --- a/EIPS/eip-3668.md +++ b/EIPS/eip-3668.md @@ -1,410 +1 @@ ---- -eip: 3668 -title: "CCIP Read: Secure offchain data retrieval" -description: CCIP Read provides a mechanism to allow a contract to fetch external data. -author: Nick Johnson (@arachnid) -discussions-to: https://ethereum-magicians.org/t/durin-secure-offchain-data-retrieval/6728 -status: Final -type: Standards Track -category: ERC -created: 2020-07-19 ---- - -## Abstract -Contracts wishing to support lookup of data from external sources may, instead of returning the data directly, revert using `OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData)`. Clients supporting this specification then make an RPC call to a URL from `urls`, supplying `callData`, and getting back an opaque byte string `response`. Finally, clients call the function specified by `callbackFunction` on the contract, providing `response` and `extraData`. The contract can then decode and verify the returned data using an implementation-specific method. - -This mechanism allows for offchain lookups of data in a way that is transparent to clients, and allows contract authors to implement whatever validation is necessary; in many cases this can be provided without any additional trust assumptions over and above those required if data is stored onchain. - -## Motivation -Minimising storage and transaction costs on Ethereum has driven contract authors to adopt a variety of techniques for moving data offchain, including hashing, recursive hashing (eg Merkle Trees/Tries) and L2 solutions. While each solution has unique constraints and parameters, they all share in common the fact that enough information is stored onchain to validate the externally stored data when required. - -Thus far, applications have tended to devise bespoke solutions rather than trying to define a universal standard. This is practical - although inefficient - when a single offchain data storage solution suffices, but rapidly becomes impractical in a system where multiple end-users may wish to make use of different data storage and availability solutions based on what suits their needs. - -By defining a common specification allowing smart contract to fetch data from offchain, we facilitate writing clients that are entirely agnostic to the storage solution being used, which enables new applications that can operate without knowing about the underlying storage details of the contracts they interact with. - -Examples of this include: - - Interacting with 'airdrop' contracts that store a list of recipients offchain in a merkle trie. - - Viewing token information for tokens stored on an L2 solution as if they were native L1 tokens. - - Allowing delegation of data such as ENS domains to various L2 solutions, without requiring clients to support each solution individually. - - Allowing contracts to proactively request external data to complete a call, without requiring the caller to be aware of the details of that data. - -## Specification -### Overview -Answering a query via CCIP read takes place in three steps: - - 1. Querying the contract. - 2. Querying the gateway using the URL provided in (1). - 3. Querying or sending a transaction to the contract using the data from (1) and (2). - -In step 1, a standard blockchain call operation is made to the contract. The contract reverts with an error that specifies the data to complete the call can be found offchain, and provides the url to a service that can provide the answer, along with additional contextual information required for the call in step (3). - -In step 2, the client calls the gateway service with the `callData` from the revert message in step (1). The gateway responds with an answer `response`, whose content is opaque to the client. - -In step 3, the client calls the original contract, supplying the `response` from step (2) and the `extraData` returned by the contract in step (1). The contract decodes the provided data and uses it to validate the response and act on it - by returning information to the client or by making changes in a transaction. The contract could also revert with a new error to initiate another lookup, in which case the protocol starts again at step 1. - -``` -┌──────┐ ┌────────┐ ┌─────────────┐ -│Client│ │Contract│ │Gateway @ url│ -└──┬───┘ └───┬────┘ └──────┬──────┘ - │ │ │ - │ somefunc(...) │ │ - ├─────────────────────────────────────────────────►│ │ - │ │ │ - │ revert OffchainData(sender, urls, callData, │ │ - │ callbackFunction, extraData) │ │ - │◄─────────────────────────────────────────────────┤ │ - │ │ │ - │ HTTP request (sender, callData) │ │ - ├──────────────────────────────────────────────────┼────────────►│ - │ │ │ - │ Response (result) │ │ - │◄─────────────────────────────────────────────────┼─────────────┤ - │ │ │ - │ callbackFunction(result, extraData) │ │ - ├─────────────────────────────────────────────────►│ │ - │ │ │ - │ answer │ │ - │◄─────────────────────────────────────────────────┤ │ - │ │ │ -``` - -### Contract interface - -A CCIP read enabled contract MUST revert with the following error whenever a function that requires offchain data is called: - -```solidity -error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData) -``` - -`sender` is the address of the contract that raised the error, and is used to determine if the error was thrown by the contract the client called, or 'bubbled up' from a nested call. - -`urls` specifies a list of URL templates to services (known as gateways) that implement the CCIP read protocol and can formulate an answer to the query. `urls` can be the empty list `[]`, in which case the client MUST specify the URL template. The order in which URLs are tried is up to the client, but contracts SHOULD return them in order of priority, with the most important entry first. - -Each URL may include two substitution parameters, `{sender}` and `{data}`. Before a call is made to the URL, `sender` is replaced with the lowercase 0x-prefixed hexadecimal formatted `sender` parameter, and `data` is replaced by the the 0x-prefixed hexadecimal formatted `callData` parameter. - -`callData` specifies the data to call the gateway with. This value is opaque to the client. Typically this will be ABI-encoded, but this is an implementation detail that contracts and gateways can standardise on as desired. - -`callbackFunction` is the 4-byte function selector for a function on the original contract to which a callback should be sent. - -`extraData` is additional data that is required by the callback, and MUST be retained by the client and provided unmodified to the callback function. This value is opaque to the client. - -The contract MUST also implement a callback method for decoding and validating the data returned by the gateway. The name of this method is implementation-specific, but it MUST have the signature `(bytes response, bytes extraData)`, and MUST have the same return type as the function that reverted with `OffchainLookup`. - -If the client successfully calls the gateway, the callback function specified in the `OffchainLookup` error will be invoked by the client, with `response` set to the value returned by the gateway, and `extraData` set to the value returned in the contract's `OffchainLookup` error. The contract MAY initiate another CCIP read lookup in this callback, though authors should bear in mind that the limits on number of recursive invocations will vary from client to client. - -In a call context (as opposed to a transaction), the return data from this call will be returned to the user as if it was returned by the function that was originally invoked. - -#### Example - -Suppose a contract has the following method: - -```solidity -function balanceOf(address addr) public view returns(uint balance); -``` - -Data for these queries is stored offchain in some kind of hashed data structure, the details of which are not important for this example. The contract author wants the gateway to fetch the proof information for this query and call the following function with it: - -```solidity -function balanceOfWithProof(bytes calldata response, bytes calldata extraData) public view returns(uint balance); -``` - -One example of a valid implementation of `balanceOf` would thus be: - -```solidity -function balanceOf(address addr) public view returns(uint balance) { - revert OffchainLookup( - address(this), - [url], - abi.encodeWithSelector(Gateway.getSignedBalance.selector, addr), - ContractName.balanceOfWithProof.selector, - abi.encode(addr) - ); -} -``` - -Note that in this example the contract is returning `addr` in both `callData` and `extraData`, because it is required both by the gateway (in order to look up the data) and the callback function (in order to verify it). The contract cannot simply pass it to the gateway and rely on it being returned in the response, as this would give the gateway an opportunity to respond with an answer to a different query than the one that was initially issued. - -#### Recursive calls in CCIP-aware contracts - -When a CCIP-aware contract wishes to make a call to another contract, and the possibility exists that the callee may implement CCIP read, the calling contract MUST catch all `OffchainLookup` errors thrown by the callee, and revert with a different error if the `sender` field of the error does not match the callee address. - -The contract MAY choose to replace all `OffchainLookup` errors with a different error. Doing so avoids the complexity of implementing support for nested CCIP read calls, but renders them impossible. - -Where the possibility exists that a callee implements CCIP read, a CCIP-aware contract MUST NOT allow the default solidity behaviour of bubbling up reverts from nested calls. This is to prevent the following situation: - - 1. Contract A calls non-CCIP-aware contract B. - 2. Contract B calls back to A. - 3. In the nested call, A reverts with `OffchainLookup`. - 4. Contract B does not understand CCIP read and propagates the `OffchainLookup` to its caller. - 5. Contract A also propagates the `OffchainLookup` to its caller. - -The result of this sequence of operations would be an `OffchainLookup` that looks valid to the client, as the `sender` field matches the address of the contract that was called, but does not execute correctly, as it only completes a nested invocation. - -#### Example - -The code below demonstrates one way that a contract may support nested CCIP read invocations. For simplicity this is shown using Solidity's try/catch syntax, although as of this writing it does not yet support catching custom errors. - -```solidity -contract NestedLookup { - error InvalidOperation(); - error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData); - - function a(bytes calldata data) external view returns(bytes memory) { - try target.b(data) returns (bytes memory ret) { - return ret; - } catch OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData) { - if(sender != address(target)) { - revert InvalidOperation(); - } - revert OffchainLookup( - address(this), - urls, - callData, - NestedLookup.aCallback.selector, - abi.encode(address(target), callbackFunction, extraData) - ); - } - } - - function aCallback(bytes calldata response, bytes calldata extraData) external view returns(bytes memory) { - (address inner, bytes4 innerCallbackFunction, bytes memory innerExtraData) = abi.decode(extraData, (address, bytes4, bytes)); - return abi.decode(inner.call(abi.encodeWithSelector(innerCallbackFunction, response, innerExtraData)), (bytes)); - } -} -``` - -### Gateway Interface -The URLs returned by a contract may be of any schema, but this specification only defines how clients should handle HTTPS URLs. - -Given a URL template returned in an `OffchainLookup`, the URL to query is composed by replacing `sender` with the lowercase 0x-prefixed hexadecimal formatted `sender` parameter, and replacing `data` with the the 0x-prefixed hexadecimal formatted `callData` parameter. - -For example, if a contract returns the following data in an `OffchainLookup`: - -``` -urls = ["https://example.com/gateway/{sender}/{data}.json"] -sender = "0xaabbccddeeaabbccddeeaabbccddeeaabbccddee" -callData = "0x00112233" -``` - -The request URL to query is `https://example.com/gateway/0xaabbccddeeaabbccddeeaabbccddeeaabbccddee/0x00112233.json`. - -If the URL template contains the `{data}` substitution parameter, the client MUST send a GET request after replacing the substitution parameters as described above. - -If the URL template does not contain the `{data}` substitution parameter, the client MUST send a POST request after replacing the substitution parameters as described above. The POST request MUST be sent with a Content-Type of `application/json`, and a payload matching the following schema: - -``` -{ - "type": "object", - "properties": { - "data": { - "type": "string", - "description": "0x-prefixed hex string containing the `callData` from the contract" - }, - "sender": { - "type": "string", - "description": "0x-prefixed hex string containing the `sender` parameter from the contract" - } - } -} -``` - -Compliant gateways MUST respond with a Content-Type of `application/json`, with the body adhering to the following JSON schema: -``` -{ - "type": "object", - "properties": { - "data": { - "type": "string", - "description: "0x-prefixed hex string containing the result data." - } - } -} -``` - -Unsuccessful requests MUST return the appropriate HTTP status code - for example, 404 if the `sender` address is not supported by this gateway, 400 if the `callData` is in an invalid format, 500 if the server encountered an internal error, and so forth. If the Content-Type of a 4xx or 5xx response is `application/json`, it MUST adhere to the following JSON schema: -``` -{ - "type": "object", - "properties": { - "message": { - "type": "string", - "description: "A human-readable error message." - } - } -} -``` - -#### Examples - -***GET request*** - -``` -# Client returned a URL template `https://example.com/gateway/{sender}/{data}.json` -# Request -curl -D - https://example.com/gateway/0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8/0xd5fa2b00.json - -# Successful result - HTTP/2 200 - content-type: application/json; charset=UTF-8 - ... - - {"data": "0xdeadbeefdecafbad"} - -# Error result - HTTP/2 404 - content-type: application/json; charset=UTF-8 - ... - - {"message": "Gateway address not supported."} -} -``` - -***POST request*** - -``` -# Client returned a URL template `https://example.com/gateway/{sender}.json` -# Request -curl -D - -X POST -H "Content-Type: application/json" --data '{"data":"0xd5fa2b00","sender":"0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8"}' https://example.com/gateway/0x226159d592E2b063810a10Ebf6dcbADA94Ed68b8.json - -# Successful result - HTTP/2 200 - content-type: application/json; charset=UTF-8 - ... - - {"data": "0xdeadbeefdecafbad"} - -# Error result - HTTP/2 404 - content-type: application/json; charset=UTF-8 - ... - - {"message": "Gateway address not supported."} -} -``` - -Clients MUST support both GET and POST requests. Gateways may implement either or both as needed. - -### Client Lookup Protocol - -A client that supports CCIP read MUST make contract calls using the following process: - - 1. Set `data` to the call data to supply to the contract, and `to` to the address of the contract to call. - 2. Call the contract at address `to` function normally, supplying `data` as the input data. If the function returns a successful result, return it to the caller and stop. - 3. If the function returns an error other than `OffchainLookup`, return it to the caller in the usual fashion. - 4. Otherwise, decode the `sender`, `urls`, `callData`, `callbackFunction` and `extraData` arguments from the `OffchainLookup` error. - 5. If the `sender` field does not match the address of the contract that was called, return an error to the caller and stop. - 6. Construct a request URL by replacing `sender` with the lowercase 0x-prefixed hexadecimal formatted `sender` parameter, and replacing `data` with the the 0x-prefixed hexadecimal formatted `callData` parameter. The client may choose which URLs to try in which order, but SHOULD prioritise URLs earlier in the list over those later in the list. - 7. Make an HTTP GET request to the request URL. - 8. If the response code from step (5) is in the range 400-499, return an error to the caller and stop. - 9. If the response code from step (5) is in the range 500-599, go back to step (5) and pick a different URL, or stop if there are no further URLs to try. - 10. Otherwise, replace `data` with an ABI-encoded call to the contract function specified by the 4-byte selector `callbackFunction`, supplying the data returned from step (7) and `extraData` from step (4), and return to step (1). - -Clients MUST handle HTTP status codes appropriately, employing best practices for error reporting and retries. - -Clients MUST handle HTTP 4xx and 5xx error responses that have a content type other than application/json appropriately; they MUST NOT attempt to parse the response body as JSON. - -This protocol can result in multiple lookups being requested by the same contract. Clients MUST implement a limit on the number of lookups they permit for a single contract call, and this limit SHOULD be at least 4. - -The lookup protocol for a client is described with the following pseudocode: - -```javascript -async function httpcall(urls, to, callData) { - const args = {sender: to.toLowerCase(), data: callData.toLowerCase()}; - for(const url of urls) { - const queryUrl = url.replace(/\{([^}]*)\}/g, (match, p1) => args[p1]); - // First argument is URL to fetch, second is optional data for a POST request. - const response = await fetch(queryUrl, url.includes('{data}') ? undefined : args); - const result = await response.text(); - if(result.statusCode >= 400 && result.statusCode <= 499) { - throw new Error(data.error.message); - } - if(result.statusCode >= 200 && result.statusCode <= 299) { - return result; - } - } -} -async function durin_call(provider, to, data) { - for(let i = 0; i < 4; i++) { - try { - return await provider.call(to, data); - } catch(error) { - if(error.code !== "CALL_EXCEPTION") { - throw(error); - } - const {sender, urls, callData, callbackFunction, extraData} = error.data; - if(sender !== to) { - throw new Error("Cannot handle OffchainLookup raised inside nested call"); - } - const result = httpcall(urls, to, callData); - data = abi.encodeWithSelector(callbackFunction, result, extraData); - } - } - throw new Error("Too many CCIP read redirects"); -} -``` - -Where: - - `provider` is a provider object that facilitates Ethereum blockchain function calls. - - `to` is the address of the contract to call. - - `data` is the call data for the contract. - -If the function being called is a standard contract function, the process terminates after the original call, returning the same result as for a regular call. Otherwise, a gateway from `urls` is called with the `callData` returned by the `OffchainLookup` error, and is expected to return a valid response. The response and the `extraData` are then passed to the specified callback function. This process can be repeated if the callback function returns another `OffchainLookup` error. - -### Use of CCIP read for transactions -While the specification above is for read-only contract calls (eg, `eth_call`), it is simple to use this method for sending transactions (eg, `eth_sendTransaction` or `eth_sendRawTransaction`) that require offchain data. While 'preflighting' a transaction using `eth_estimateGas` or `eth_call`, a client that receives an `OffchainLookup` revert can follow the procedure described above in [Client lookup protocol](#client-lookup-protocol), substituting a transaction for the call in the last step. This functionality is ideal for applications such as making onchain claims supported by offchain proof data. - -### Glossary - - Client: A process, such as JavaScript executing in a web browser, or a backend service, that wishes to query a blockchain for data. The client understands how to fetch data using CCIP read. - - Contract: A smart contract existing on Ethereum or another blockchain. - - Gateway: A service that answers application-specific CCIP read queries, usually over HTTPS. - -## Rationale -### Use of `revert` to convey call information -For offchain data lookup to function as desired, clients must either have some way to know that a function depends on this specification for functionality - such as a specifier in the ABI for the function - or else there must be a way for the contract to signal to the client that data needs to be fetched from elsewhere. - -While specifying the call type in the ABI is a possible solution, this makes retrofitting existing interfaces to support offchain data awkward, and either results in contracts with the same name and arguments as the original specification, but with different return data - which will cause decoding errors for clients that do not expect this - or duplicating every function that needs support for offchain data with a different name (eg, `balanceOf -> offchainBalanceOf`). Neither solutions is particularly satisfactory. - -Using a revert, and conveying the required information in the revert data, allows any function to be retrofitted to support lookups via CCIP read so long as the client understands the specification, and so facilitates translation of existing specifications to use offchain data. - -### Passing contract address to the gateway service -`address` is passed to the gateway in order to facilitate the writing of generic gateways, thus reducing the burden on contract authors to provide their own gateway implementations. Supplying `address` allows the gateway to perform lookups to the original contract for information needed to assist with resolution, making it possible to operate one gateway for any number of contracts implementing the same interface. - -### Existence of `extraData` argument -`extraData` allows the original contract function to pass information to a subsequent invocation. Since contracts are not persistent, without this data a contract has no state from the previous invocation. Aside from allowing arbitrary contextual information to be propagated between the two calls, this also allows the contract to verify that the query the gateway answered is in fact the one the contract originally requested. - -### Use of GET and POST requests for the gateway interface -Using a GET request, with query data encoded in the URL, minimises complexity and enables entirely static implementations of gateways - in some applications a gateway can simply be an HTTP server or IPFS instance with a static set of responses in text files. - -However, URLs are limited to 2 kilobytes in size, which will impose issues for more complex uses of CCIP read. Thus, we provide for an option to use POST data. This is made at the contract's discretion (via the choice of URL template) in order to preserve the ability to have a static gateway operating exclusively using GET when desired. - -## Backwards Compatibility -Existing contracts that do not wish to use this specification are unaffected. Clients can add support for CCIP read to all contract calls without introducing any new overhead or incompatibilities. - -Contracts that require CCIP read will not function in conjunction with clients that do not implement this specification. Attempts to call these contracts from non-compliant clients will result in the contract throwing an exception that is propagaged to the user. - -## Security Considerations - -### Gateway Response Data Validation -In order to prevent a malicious gateway from causing unintended side-effects or faulty results, contracts MUST include sufficient information in the `extraData` argument to allow them to verify the relevance and validity of the gateway's response. For example, if the contract is requesting information based on an `address` supplied to the original call, it MUST include that address in the `extraData` so that the callback can verify the gateway is not providing the answer to a different query. - -Contracts must also implement sufficient validation of the data returned by the gateway to ensure it is valid. The validation required is application-specific and cannot be specified on a global basis. Examples would include verifying a Merkle proof of inclusion for an L2 or other Merkleized state, or verifying a signature by a trusted signer over the response data. - -### Client Extra Data Validation -In order to prevent a malicious client from causing unintended effects when making transactions using CCIP read, contracts MUST implement appropriate checks on the `extraData` returned to them in the callback. Any sanity/permission checks performed on input data for the initial call MUST be repeated on the data passed through the `extraData` field in the callback. For example, if a transaction should only be executable by an authorised account, that authorisation check MUST be done in the callback; it is not sufficient to perform it with the initial call and embed the authorised address in the `extraData`. - -### HTTP requests and fingerprinting attacks -Because CCIP read can cause a user's browser to make HTTP requests to an address controlled by the contract, there is the potential for this to be used to identify users - for example, to associate their wallet address with their IP address. - -The impact of this is application-specific; fingerprinting a user when they resolve an ENS domain may have little privacy impact, as the attacker will not learn the user's wallet address, only the fact that the user is resolving a given ENS name from a given IP address - information they can also learn from running a DNS server. On the other hand, fingerprinting a user when they attempt a transaction to transfer an NFT may give an attacker everything they need to identify the IP address of a user's wallet. - -To minimise the security impact of this, we make the following recommendations: - - 1. Client libraries should provide clients with a hook to override CCIP read calls - either by rewriting them to use a proxy service, or by denying them entirely. This mechanism or another should be written so as to easily facilitate adding domains to allowlists or blocklists. - 2. Client libraries should disable CCIP read for transactions (but not for calls) by default, and require the caller to explicitly enable this functionality. Enablement should be possible both on a per-contract, per-domain, or global basis. - 3. App authors should not supply a 'from' address for contract calls ('view' operations) where the call could execute untrusted code (that is, code not authored or trusted by the application author). As a precuationary principle it is safest to not supply this parameter at all unless the author is certain that no attacker-determined smart contract code will be executed. - 4. Wallet authors that are responsible for fetching user information - for example, by querying token contracts - should either ensure CCIP read is disabled for transactions, and that no contract calls are made with a 'from' address supplied, or operate a proxy on their users' behalf, rewriting all CCIP read calls to take place via the proxy, or both. - -We encourage client library authors and wallet authors not to disable CCIP read by default, as many applications can be transparently enhanced with this functionality, which is quite safe if the above precautions are observed. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3668.md diff --git a/EIPS/eip-3722.md b/EIPS/eip-3722.md index 1b17795bc5269e..2a5ac5f39ab624 100644 --- a/EIPS/eip-3722.md +++ b/EIPS/eip-3722.md @@ -1,197 +1 @@ ---- -eip: 3722 -title: Poster -description: A ridiculously simple general purpose social media smart contract. -author: Auryn Macmillan (@auryn-macmillan) -discussions-to: https://ethereum-magicians.org/t/eip-poster-a-ridiculously-simple-general-purpose-social-media-smart-contract/6751 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-07-31 ---- - -# Poster - -## Abstract -A ridiculously simple general purpose social media smart contract. -It takes two strings (`content` and `tag`) as parameters and emits those strings, along with msg.sender, as an event. That's it. -The EIP also includes a proposed standard json format for a Twitter-like application, where each `post()` call can include multiple posts and/or operations. The assumption being that application state will be constructed off-chain via some indexer. - -## Motivation -Poster is intended to be used as a base layer for decentralized social media. It can be deployed to the same address (via the singleton factory) on just about any EVM compatible network. Any Ethereum account can make posts to the deployment of Poster on its local network. - -## Specification - -### Contract - -```solidity -contract Poster { - - event NewPost(address indexed user, string content, string indexed tag); - - function post(string calldata content, string calldata tag) public { - emit NewPost(msg.sender, content, tag); - } -} -``` - -### ABI -```json -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "user", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "content", - "type": "string" - }, - { - "indexed": true, - "internalType": "string", - "name": "tag", - "type": "string" - } - ], - "name": "NewPost", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "content", - "type": "string" - }, - { - "internalType": "string", - "name": "tag", - "type": "string" - } - ], - "name": "post", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] -``` - -### Standard json format for Twitter-like posts - -```json -{ - "content": [ - { - "type": "microblog", - "text": "this is the first post in a thread" - }, - { - "type": "microblog", - "text": "this is the second post in a thread", - "replyTo": "this[0]" - }, - { - "type": "microblog", - "text": "this is a reply to some other post", - "replyTo": "some_post_id" - }, - { - "type": "microblog", - "text": "this is a post with an image", - "image": "ipfs://ipfs_hash" - }, - { - "type": "microblog", - "text": "this post replaces a previously posted post", - "edit": "some_post_id" - }, - { - "type": "delete", - "target": "some_post_id" - }, - { - "type": "like", - "target": "some_post_id" - }, - { - "type": "repost", - "target": "some_post_id" - }, - { - "type": "follow", - "target": "some_account" - }, - { - "type": "unfollow", - "target": "some_account" - }, - { - "type": "block", - "target": "some_account" - }, - { - "type": "report", - "target": "some_account or some_post_id" - }, - { - "type": "permissions", - "account": "", - "permissions": { - "post": true, - "delete": true, - "like": true, - "follow": true, - "block": true, - "report": true, - "permissions": true - } - }, - { - "type": "microblog", - "text": "This is a post from an account with permissions to post on behalf of another account.", - "from": "" - } - ] -} - -``` - -## Rationale -There was some discussion around whether or not an post ID should also be emitted, whether the content should be a string or bytes, and whether or not anything at all should actually be emitted. - -We decided not to emit an ID, since it meant adding state or complexity to the contract and there is a fairly common pattern of assigning IDs on the indexer layer based on transactionHash + logIndex. - -We decided to emit a string, rather than bytes, simply because that would make content human readable on many existing interfaces, like Etherscan for example. This did, unfortunately, eliminate some of the benefit that we might have gotten from a more compact encoding scheme like CBOR, rather than JSON. But this also would not have satisfied the human readable criteria. - -While there would have been some gas savings if we decided against emitting anything at all, it would have redically increased the node requirements to index posts. As such, we decided it was worth the extra gas to actually emit the content. - -## Reference Implementation - -Poster has been deployed at `0x000000000000cd17345801aa8147b8D3950260FF` on multiple networks using the [Singleton Factory](https://eips.ethereum.org/EIPS/eip-2470). If it is not yet deployed on your chosen network, you can use the Singleton Factory to deploy an instance of Poster at the same address on just about any EVM compatible network using these parameters: - -> **initCode:** `0x608060405234801561001057600080fd5b506101f6806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630ae1b13d14610030575b600080fd5b61004361003e3660046100fa565b610045565b005b8181604051610055929190610163565b60405180910390203373ffffffffffffffffffffffffffffffffffffffff167f6c7f3182d7e4cb876251f9ae1489975fdbbf15d9f35d393f2ac9b1ff57cec69f86866040516100a5929190610173565b60405180910390a350505050565b60008083601f8401126100c4578182fd5b50813567ffffffffffffffff8111156100db578182fd5b6020830191508360208285010111156100f357600080fd5b9250929050565b6000806000806040858703121561010f578384fd5b843567ffffffffffffffff80821115610126578586fd5b610132888389016100b3565b9096509450602087013591508082111561014a578384fd5b50610157878288016100b3565b95989497509550505050565b6000828483379101908152919050565b60006020825282602083015282846040840137818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016010191905056fea2646970667358221220ee0377bd266748c5dbaf0a3f15ebd97be153932f2d14d460d9dd4271fee541b564736f6c63430008000033` -> -> **salt:** `0x9245db59943806d06245bc7847b3efb2c899d11b621a0f01bb02fd730e33aed2` - -When verifying on the source code on a block explorer, make sure to set the optimizer to `yes` and the runs to `10000000`. - -The source code is available in the [Poster contract repo](https://github.com/ETHPoster/contract/blob/master/contracts/Poster.sol). - - -## Security Considerations -Given the ridiculously simple implementation of Poster, there does not appear to be any real security concerns at the contract level. - -At the application level, clients should confirm that posts including a `"from"` field that differs from `msg.sender` have been authorized by the `"from"` address via a `"permissions"` post, otherwise they should be considerred invalid or a post from `msg.sender`. - -Clients should also be sure to sanitize post data. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3722.md diff --git a/EIPS/eip-3754.md b/EIPS/eip-3754.md index bc4dcdcf2a1267..5382b8b5af13f5 100644 --- a/EIPS/eip-3754.md +++ b/EIPS/eip-3754.md @@ -1,74 +1 @@ ---- -eip: 3754 -title: A Vanilla Non-Fungible Token Standard -description: NFTs for representing abstract ownership -author: Simon Tian (@simontianx) -discussions-to: https://github.com/ethereum/EIPs/issues/3753 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-08-21 ---- - -## Abstract -In this standard, a non-fungible token stands as atomic existence and encourages -layers of abstraction built on top of it. Ideal for representing concepts like -rights, a form of abstract ownership. Such right can take the form of NFT options, -oracle membership, virtual coupons, etc., and can then be made liquid because of -this tokenization. - -## Motivation -Non-fungible tokens are popularized by the [ERC-721](./eip-721.md) NFT standard -for representing "ownership over digital or physical assets". Over the course of -development, reputable NFT projects are about crypto-assets, digital collectibles, -etc. The proposed standard aims to single out a special type of NFTs that are -ideal for representing abstract ownership such as rights. Examples include the -right of making a function call to a smart contract, an NFT option that gives -the owner the right, but not obligation, to purchase an ERC-721 NFT, and the prepaid -membership (time-dependent right) of accessing to data feeds provided by oracles -without having to pay the required token fees. An on-chain subscription business -model can then be made available by this standard. The conceptual clarity of an -NFT is hence improved by this standard. - -## Specification -``` -interface IERC3754 { - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - - function balanceOf(address owner) external view returns (uint256); - function ownerOf(uint256 tokenId) external view returns (address); - function approve(address to, uint256 tokenId) external; - function getApproved(uint256 tokenId) external view returns (address); - function setApprovalForAll(address operator, bool approved) external; - function isApprovedForAll(address owner, address operator) external view returns (bool); - function transferFrom(address from, address to, uint256 tokenId) external; - function safeTransferFrom(address from, address to, uint256 tokenId) external; - function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) external; -} -``` - -## Rationale -The NFTs defined in the [ERC-721](./eip-721.md) standard are already largely -accepted and known as representing ownership of digital assets, and the NFTs by -this standard aim to be accepted and known as representing abstract ownership. -This is achieved by allowing and encouraging layers of abstract utilities built -on top of them. Ownership of such NFTs is equivalent with having the rights to -perform functions assigned to such tokens. Transfer of such rights is also made -easier because of this tokenization. To further distinguish this standard -from [ERC-721](./eip-721.md), data fields and functions related to `URI` are -excluded. - -## Backwards Compatibility -There is no further backwards compatibility required. - -## Reference Implementation -https://github.com/simontianx/ERC3754 - -## Security Considerations -The security is enhanced from ERC721, given tokens are minted without having to -provide `URI`s. Errors in dealing with `URI`s can be avoided. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3754.md diff --git a/EIPS/eip-3770.md b/EIPS/eip-3770.md index 41334680d03365..2f4c55be37a5b5 100644 --- a/EIPS/eip-3770.md +++ b/EIPS/eip-3770.md @@ -1,65 +1 @@ ---- -eip: 3770 -title: Chain-specific addresses -description: Prepending chain-specific addresses with a human-readable chain identifier -author: Lukas Schor (@lukasschor), Richard Meissner (@rmeissner), Pedro Gomes (@pedrouid), ligi -discussions-to: https://ethereum-magicians.org/t/chain-specific-addresses/6449 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-08-26 ---- - -## Abstract - -[ERC-3770](./eip-3770.md) introduces a new address standard to be adapted by wallets and dApps to display chain-specific addresses by using a human-reacable prefix. - -## Motivation - -The need for this proposal emerges from the increasing adoption of non-Ethereum Mainnet chains that use the Ethereum Virtual Machine (EVM). In this context, addresses become ambiguous, as the same address may refer to an EOA on chain X or a smart contract on chain Y. This will eventually lead to Ethereum users losing funds due to human error. For example, users sending funds to a smart contract wallet address which was not deployed on a particular chain. - -Therefore we should prefix addresses with a unique identifier that signals to Dapps and wallets on what chain the target account is. In theory, this prefix could be a [EIP-155](./eip-155.md) chainID. However, these chain IDs are not meant to be displayed to users in dApps or wallets, and they were optimized for developer interoperability, rather than human readability. - -## Specification - -This proposal extends addresses with a human-readable blockchain short name. - -### Syntax - -A chain-specific address is prefixed with a chain shortName, separated with a colon sign (:). - -Chain-specific address = "`shortName`" "`:`" "`address`" - -- `shortName` = STRING - -- `address` = STRING - -### Semantics - -``` - -`shortName` is mandatory and MUST be a valid chain short name from https://github.com/ethereum-lists/chains - -`address` is mandatory and MUST be a [ERC-55](./eip-55.md) compatible hexadecimal address - -``` - -### Examples - -![Chain-specific addresses](../assets/eip-3770/examples.png "Examples of chain-specific addresses") - -## Rationale - -To solve the initial problem of user-facing addresses being ambiguous in a multichain context, we need to map EIP-155 chain IDs with a user-facing format of displaying chain identifiers. - -## Backwards Compatibility - -Ethereum addresses without the chain specifier will continue to require additional context to understand which chain the address refers to. - -## Security Considerations - -The Ethereum List curators must consider how similar looking chain short names can be used to confuse users. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3770.md diff --git a/EIPS/eip-3772.md b/EIPS/eip-3772.md index 566964c6a48291..0c89b967d2bfab 100644 --- a/EIPS/eip-3772.md +++ b/EIPS/eip-3772.md @@ -1,278 +1 @@ ---- -eip: 3772 -title: Compressed Integers -description: Using lossy compression on uint256 to improve gas costs, ideally by a factor up to 4x. -author: Soham Zemse (@zemse) -discussions-to: https://github.com/ethereum/EIPs/issues/3772 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-08-27 ---- - -## Abstract - -This document specifies compression of `uint256` to smaller data structures like `uint64`, `uint96`, `uint128`, for optimizing costs for storage. The smaller data structure (represented as `cintx`) is divided into two parts, in the first one we store `significant` bits and in the other number of left `shift`s needed on the significant bits to decompress. This document also includes two specifications for decompression due to the nature of compression being lossy, i.e. it causes underflow. - -## Motivation - -- Storage is costly, each storage slot costs almost $0.8 to initialize and $0.2 to update (20 gwei, 2000 ETHUSD). -- Usually, we store money amounts in `uint256` which takes up one entire slot. -- If it's DAI value, the range we work with most is 0.001 DAI to 1T DAI (or 1012). If it's ETH value, the range we work with most is 0.000001 ETH to 1B ETH. Similarly, any token of any scale has a reasonable range of 1015 amounts that we care/work with. -- However, uint256 type allows us to represent $10-18 to $1058, and most of it is a waste. In technical terms, we have the probability distribution for values larger than $1015 and smaller than $10-3 as negligible (i.e. P[val > 1015] ≈ 0 and P[val < 10-3] ≈ 0). -- Number of bits required to represent 1015 values = log2(1015) = 50 bits. So just 50 bits (instead of 256) are reasonably enough to represent a practical range of money, causing a very negligible difference. - -## Specification - -In this specification, the structure for representing a compressed value is represented using `cintx`, where x is the number of bits taken by the entire compressed value. On the implementation level, an `uintx` can be used for storing a `cintx` value. - -### Compression - -#### uint256 into cint64 (up to cint120) - -The rightmost, or least significant, 8 bits in `cintx` are reserved for storing the shift and the rest available bits are used to store the significant bits starting from the first `1` bit in `uintx`. - -```solidity -struct cint64 { uint56 significant; uint8 shift; } - -// ... - -struct cint120 { uint112 significant; uint8 shift; } -``` - -#### uint256 into cint128 (up to cint248) - -The rightmost, or least significant, 7 bits in `cintx` are reserved for storing the shift and the rest available bits are used to store the significant bits starting from the first one bit in `uintx`. - -> In the following code example, `uint7` is used just for representation purposes only, but it should be noted that uints in Solidity are in multiples of 8. - -```solidity -struct cint128 { uint121 significant; uint7 shift; } - -// ... - -struct cint248 { uint241 significant; uint7 shift; } -``` - -Examples: - -``` -Example: -uint256 value: 2**100, binary repr: 1000000...(hundred zeros) -cint64 { significant: 10000000...(55 zeros), shift: 00101101 (45 in decimal)} - -Example: -uint256 value: 2**100-1, binary repr: 111111...(hundred ones) -cint64 { significant: 11111111...(56 ones), shift: 00101100 (44 in decimal) } -``` - -### Decompression - -Two decompression methods are defined: a normal `decompress` and a `decompressRoundingUp`. - -```solidity -library CInt64 { - // packs the uint256 amount into a cint64 - function compress(uint256) internal returns (cint64) {} - - // unpacks cint64, by shifting the significant bits left by shift - function decompress(cint64) internal returns (uint256) {} - - // unpacks cint64, by shifting the significant bits left by shift - // and having 1s in the shift bits - function decompressRoundingUp(cint64) internal returns (uint256) {} -} -``` - -#### Normal Decompression - -The `significant` bits in the `cintx` are moved to a `uint256` space and shifted left by `shift`. - -> NOTE: In the following example, cint16 is used for visual demonstration purposes. But it should be noted that it is definitely not safe for storing money amounts because its significant bits capacity is 8, while at least 50 bits are required for storing money amounts. - -``` -Example: -cint16{significant:11010111, shift:00000011} -decompressed uint256: 11010111000 // shifted left by 3 - -Example: -cint64 { significant: 11111111...(56 ones), shift: 00101100 (44 in decimal) } -decompressed uint256: 1111...(56 ones)0000...(44 zeros) -``` - -#### Decompression along with rounding up - -The `significant` bits in the `cintx` are moved to a `uint256` space and shifted left by `shift` and the least significant `shift` bits are `1`s. - -``` -Example: -cint16{significant:11011110, shift:00000011} -decompressed rounded up value: 11011110111 // shifted left by 3 and 1s instead of 0s - -Example: -cint64 { significant: 11111111...(56 ones), shift: 00101100 (44 in decimal) } -decompressed uint256: 1111...(100 ones) -``` - -This specification is to be used by a new smart contract for managing its internal state so that any state mutating calls to it can be cheaper. These compressed values on a smart contract's state are something that should not be exposed to the external world (other smart contracts or clients). A smart contract should expose a decompressed value if needed. - -## Rationale - -- The `significant` bits are stored in the most significant part of `cintx` while `shift` bits in the least significant part, to help prevent obvious dev mistakes. For e.g. a number smaller than 256-1 its compressed `cint64` value would be itself if the arrangement were to be opposite than specified. If a developer forgets to uncompress a value before using it, this case would still pass if the compressed value is the same as decompressed value. -- It should be noted that using `cint64` doesn't render gas savings automatically. The solidity compiler needs to pack more data into the same storage slot. -- Also the packing and unpacking adds some small cost too. -- Though this design can also be seen as a binary floating point representation, however using floating point numbers on EVM is not in the scope of this ERC. The primary goal of floating point numbers is to be able to represent a wider range in an available number of bits, while the goal of compression in this ERC is to keep as much precision as possible. Hence, it specifies for the use of minimum exponent/shift bits (i.e 8 up to `uint120` and 7 up to `uint248`). - -```solidity -// uses 3 slots -struct UserData1 { - uint64 amountCompressed; - bytes32 hash; - address beneficiary; -} - -// uses 2 slots -struct UserData2 { - uint64 amountCompressed; - address beneficiary; - bytes32 hash; -} -``` - -## Backwards Compatibility - -There are no known backward-incompatible issues. - -## Reference Implementation - -On the implementation level `uint64` may be used directly, or with custom types introduced in 0.8.9. - -```soldity -function compress(uint256 full) public pure returns (uint64 cint) { - uint8 bits = mostSignificantBitPosition(full); - if (bits <= 55) { - cint = uint64(full) << 8; - } else { - bits -= 55; - cint = (uint64(full >> bits) << 8) + bits; - } -} - -function decompress(uint64 cint) public pure returns (uint256 full) { - uint8 bits = uint8(cint % (1 << 9)); - full = uint256(cint >> 8) << bits; -} - -function decompressRoundingUp(uint64 cint) public pure returns (uint256 full) { - uint8 bits = uint8(cint % (1 << 9)); - full = uint256(cint >> 8) << bits + ((1 << bits) - 1); -} -``` - -The above gist has `library CInt64` that contains demonstrative logic for compression, decompression, and arithmetic for `cint64`. The gist also has an example contract that uses the library for demonstration purposes. - -The CInt64 format is intended only for storage, while dev should convert it to uint256 form using suitable logic (decompress or decompressRoundingUp) to perform any arithmetic on it. - -## Security Considerations - -The following security considerations are discussed: - -1. Effects due to lossy compression - - Error estimation for `cint64` - - Handling the error -2. Losing precision due to incorrect use of `cintx` -3. Compressing something other than money `uint256`s. - -### 1. Effects due to lossy compression - -When a value is compressed, it causes underflow, i.e. some less significant bits are sacrificed. This results in a `cintx` value whose decompressed value is less than or equal to the actual `uint256` value. - -```solidity -uint a = 2**100 - 1; // 100 # of 1s in binary format -uint c = a.compress().decompress(); - -a > c; // true -a - (2**(100 - 56) - 1) == c; // true - -// Visual example: -// before: 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 -// after: 1111111111111111111111111111111111111111111111111111111100000000000000000000000000000000000000000000 -``` - -#### Error estimation for cint64 - -Let's consider we have a `value` of the order 2m (less than 2m and greater than or equal to 2m-1). - -For all values such that 2m - 1 - (2m-56 - 1) <= `value` <= 2m - 1, the compressed value `cvalue` is 2m - 1 - (2m-56 - 1). - -The maximum error is 2m-56 - 1, approximating it to decimal: 10n-17 (log2(56) is 17). Here `n` is number of decimal digits + 1. - -For e.g. compressing a value of the order $1,000,000,000,000 (or 1T or 1012) to `cint64`, the maximum error turns out to be 1012+1-17 = $10-4 = $0.0001. This means the precision after 4 decimal places is lost, or we can say that the uncompressed value is at maximum $0.0001 smaller. Similarly, if someone is storing $1,000,000 into `cint64`, the uncompressed value would be at maximum $0.0000000001 smaller. In comparison, the storage costs are almost $0.8 to initialize and $0.2 to update (20 gwei, 2000 ETHUSD). - -#### Handling the error - -Note that compression makes the value slightly smaller (underflow). But we also have another operation that also does that. In integer math, the division is a lossy operation (causing underflow). For instance, - -```solidity -10000001 / 2 == 5000000 // true -``` - -The result of the division operation is not always exact, but it's smaller than the actual value, in some cases as in the above example. Though, most engineers try to reduce this effect by doing all the divisions at the end. - -``` -1001 / 2 * 301 == 150500 // true -1001 * 301 / 2 == 150650 // true -``` - -The division operation has been in use in the wild, and plenty of lossy integer divisions have taken place, causing DeFi users to get very very slightly less withdrawal amounts, which they don't even notice. If been careful, then the risk is very negligible. Compression is similar, in the sense that it is also a division by 2shift. If been careful with this too, the effects are minimized. - -In general, one should follow the rule: - -1. When a smart contract has to transfer a compressed amount to a user, they should use a rounded down value (by using `amount.decompress()`). -2. When a smart contract has to transferFrom a compressed amount from a user to itself, i.e charging for some bill, they should use a rounded up value (by using `amount.decompressUp()`). - -The above ensures that smart contract does not loose money due to the compression, it is the user who receives less funds or pays more funds. The extent of rounding is something that is negligible enough for the user. Also just to mention, this rounding up and down pattern is observed in many projects including UniswapV3. - -### 2. Losing precision due to incorrect use of `cintx` - -This is an example where dev errors while using compression can be a problem. - -Usual user amounts mostly have an max entropy of 50, i.e. 1015 (or 250) values in use, that is the reason why we find uint56 enough for storing significant bits. However, let's see an example: - -```solidity -uint64 sharesC = // reading compressed value from storage; -uint64 price = // CALL; -uint64 amountC = sharesC.cmuldiv(price, PRICE_UNIT); -user.transfer(amountC.uncompress()); -``` - -The above code results in a serious precision loss. `sharesC` has an entropy of 50, as well as `priceC` also has an entropy of 50. When we multiply them, we get a value that contains entropies of both, and hence, an entropy of 100. After multiplication is done, `cmul` compresses the value, which drops the entropy of `amountC` to 56 (as we have uint56 there to store significant bits). - -To prevent entropy/precision from dropping, we get out from compression. - -```solidity -uint64 sharesC = shares.compress(); -uint64 priceC = price.compress(); -uint256 amount = sharesC.uncompress() * price / PRICE_UNIT; -user.transfer(amount); -``` - -Compression is only useful when writing to storage while doing arithmetic with them should be done very carefully. - -### 3. Compressing something other than money `uint256`s. - -Compressed Integers is intended to only compress money amount. Technically there are about 1077 values that a `uint256` can store but most of those values have a flat distribution i.e. the probability is 0 or extremely negligible. (What is a probability that a user would be depositing 1000T DAI or 1T ETH to a contract? In normal circumstances it doesn't happen, unless someone has full access to the mint function). Only the amounts that people work with have a non-zero distribution ($0.001 DAI to $1T or 1015 to 1030 in uint256). 50 bits are enough to represent this information, just to round it we use 56 bits for precision. - -Using the same method for compressing something else which have a completely different probability distribution will likely result in a problem. It's best to just not compress if you're not sure about the distribution of values your `uint256` is going to take. And also, for things you think you are sure about using compression for, it's better to give more thought if compression can result in edge cases (e.g. in previous multiplication example). - -### 4. Compressing Stable vs Volatile money amounts - -Since we have a dynamic `uint8 shift` value that can move around. So even if you wanted to represent 1 Million SHIBA INU tokens or 0.0002 WBTC (both $10 as of this writing), cint64 will pick its top 56 significant bits which will take care of the value representation. - -It can be a problem for volatile tokens if the coin is extremely volatile wrt user's native currency. Imagine a very unlikely case where a coin goes 256x up (price went up by 1016 lol). In such cases `uint56` might not be enough as even its least significant bit is very valuable. If such insanely volatile tokens are to be stored, you should store more significant bits, i.e. using `cint96` or `cint128`. - -`cint64` has 56 bits for storing significant, when only 50 were required. Hence there are 6 extra bits, which means that it is fine if the $ value of the cryptocurrency stored in cint64 increases by 26 or 64x. If the value goes down it's not a problem. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-3772.md diff --git a/EIPS/eip-4337.md b/EIPS/eip-4337.md index 8ea4afe97b2fcb..48175dec28bf92 100644 --- a/EIPS/eip-4337.md +++ b/EIPS/eip-4337.md @@ -1,1013 +1 @@ ---- -eip: 4337 -title: Account Abstraction Using Alt Mempool -description: An account abstraction proposal which completely avoids consensus-layer protocol changes, instead relying on higher-layer infrastructure. -author: Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn), Alex Forshtat (@forshtat), Kristof Gazso (@kristofgazso), Tjaden Hess (@tjade273) -discussions-to: https://ethereum-magicians.org/t/erc-4337-account-abstraction-via-entry-point-contract-specification/7160 -status: Draft -type: Standards Track -category: ERC -created: 2021-09-29 ---- - -## Abstract - -An account abstraction proposal which completely avoids the need for consensus-layer protocol changes. Instead of adding new protocol features and changing the bottom-layer transaction type, this proposal instead introduces a higher-layer pseudo-transaction object called a `UserOperation`. Users send `UserOperation` objects into a separate mempool. A special class of actor called bundlers package up a set of these objects into a transaction making a `handleOps` call to a special contract, and that transaction then gets included in a block. - -## Motivation - -See also `https://ethereum-magicians.org/t/implementing-account-abstraction-as-part-of-eth1-x/4020` and the links therein for historical work and motivation, and [EIP-2938](./eip-2938.md) for a consensus layer proposal for implementing the same goal. - -This proposal takes a different approach, avoiding any adjustments to the consensus layer. It seeks to achieve the following goals: - -* **Achieve the key goal of account abstraction**: allow users to use smart contract wallets containing arbitrary verification logic instead of EOAs as their primary account. Completely remove any need at all for users to also have EOAs (as status quo SC wallets and [EIP-3074](./eip-3074.md) both require) -* **Decentralization** - * Allow any bundler (think: block builder) to participate in the process of including account-abstracted user operations - * Work with all activity happening over a public mempool; users do not need to know the direct communication addresses (eg. IP, onion) of any specific actors - * Avoid trust assumptions on bundlers -* **Do not require any Ethereum consensus changes**: Ethereum consensus layer development is focusing on the merge and later on scalability-oriented features, and there may not be any opportunity for further protocol changes for a long time. Hence, to increase the chance of faster adoption, this proposal avoids Ethereum consensus changes. -* **Try to support other use cases** - * Privacy-preserving applications - * Atomic multi-operations (similar goal to [EIP-3074](./eip-3074.md)) - * Pay tx fees with [ERC-20](./eip-20.md) tokens, allow developers to pay fees for their users, and [EIP-3074](./eip-3074.md)-like **sponsored transaction** use cases more generally - * Support aggregated signature (e.g. BLS) - -## Specification - -### Definitions - -* **UserOperation** - a structure that describes a transaction to be sent on behalf of a user. To avoid confusion, it is not named "transaction". - * Like a transaction, it contains "sender", "to", "calldata", "maxFeePerGas", "maxPriorityFee", "signature", "nonce" - * unlike a transaction, it contains several other fields, described below - * also, the "signature" field usage is not defined by the protocol, but by each account implementation -* **Sender** - the account contract sending a user operation. -* **EntryPoint** - a singleton contract to execute bundles of UserOperations. Bundlers/Clients whitelist the supported entrypoint. -* **Bundler** - a node (block builder) that can handle UserOperations, - create a valid an EntryPoint.handleOps() transaction, - and add it to the block while it is still valid. - This can be achieved by a number of ways: - * Bundler can act as a block builder itself - * If the bundler is not a block builder, it MUST work with the block building infrastructure such as `mev-boost` or - other kind of PBS (proposer-builder separation) - * The `bundler` can also rely on an experimental `eth_sendRawTransactionConditional` RPC API if it is available. -* **Aggregator** - a helper contract trusted by accounts to validate an aggregated signature. Bundlers/Clients whitelist the supported aggregators. - -To avoid Ethereum consensus changes, we do not attempt to create new transaction types for account-abstracted transactions. Instead, users package up the action they want their account to take in an ABI-encoded struct called a `UserOperation`: - -| Field | Type | Description -| - | - | - | -| `sender` | `address` | The account making the operation | -| `nonce` | `uint256` | Anti-replay parameter (see "Semi-abstracted Nonce Support" ) | -| `initCode` | `bytes` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | -| `callData` | `bytes` | The data to pass to the `sender` during the main execution call | -| `callGasLimit` | `uint256` | The amount of gas to allocate the main execution call | -| `verificationGasLimit` | `uint256` | The amount of gas to allocate for the verification step | -| `preVerificationGas` | `uint256` | The amount of gas to pay for to compensate the bundler for pre-verification execution, calldata and any gas overhead that can't be tracked on-chain | -| `maxFeePerGas` | `uint256` | Maximum fee per gas (similar to [EIP-1559](./eip-1559.md) `max_fee_per_gas`) | -| `maxPriorityFeePerGas` | `uint256` | Maximum priority fee per gas (similar to EIP-1559 `max_priority_fee_per_gas`) | -| `paymasterAndData` | `bytes` | Address of paymaster sponsoring the transaction, followed by extra data to send to the paymaster (empty for self-sponsored transaction) | -| `signature` | `bytes` | Data passed into the account along with the nonce during the verification step | - -Users send `UserOperation` objects to a dedicated user operation mempool. A specialized class of actors called **bundlers** (either block builders running special-purpose code, or users that can relay transactions to block builders eg. through a bundle marketplace such as Flashbots that can guarantee next-block-or-never inclusion) listen in on the user operation mempool, and create **bundle transactions**. A bundle transaction packages up multiple `UserOperation` objects into a single `handleOps` call to a pre-published global **entry point contract**. - -To prevent replay attacks (both cross-chain and multiple `EntryPoint` implementations), the `signature` should depend on `chainid` and the `EntryPoint` address. - -The core interface of the entry point contract is as follows: - -```solidity -function handleOps(UserOperation[] calldata ops, address payable beneficiary); - -function handleAggregatedOps( - UserOpsPerAggregator[] calldata opsPerAggregator, - address payable beneficiary -); - - -struct UserOpsPerAggregator { - UserOperation[] userOps; - IAggregator aggregator; - bytes signature; -} -function simulateValidation(UserOperation calldata userOp); - -error ValidationResult(ReturnInfo returnInfo, - StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo); - -error ValidationResultWithAggregation(ReturnInfo returnInfo, - StakeInfo senderInfo, StakeInfo factoryInfo, StakeInfo paymasterInfo, - AggregatorStakeInfo aggregatorInfo); - -struct ReturnInfo { - uint256 preOpGas; - uint256 prefund; - bool sigFailed; - uint48 validAfter; - uint48 validUntil; - bytes paymasterContext; -} - -struct StakeInfo { - uint256 stake; - uint256 unstakeDelaySec; -} - -struct AggregatorStakeInfo { - address actualAggregator; - StakeInfo stakeInfo; -} -``` - -The core interface required for an account to have is: - -```solidity -interface IAccount { - function validateUserOp - (UserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) - external returns (uint256 validationData); -} -``` - -The `userOpHash` is a hash over the userOp (except signature), entryPoint and chainId. - -The account: - -* MUST validate the caller is a trusted EntryPoint -* If the account does not support signature aggregation, it MUST validate the signature is a valid signature of the `userOpHash`, and - SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error should revert. -* MUST pay the entryPoint (caller) at least the "missingAccountFunds" (which might be zero, in case current account's deposit is high enough) -* The account MAY pay more than this minimum, to cover future transactions (it can always issue `withdrawTo` to retrieve it) -* The return value MUST be packed of `authorizer`, `validUntil` and `validAfter` timestamps. - * authorizer - 0 for valid signature, 1 to mark signature failure. Otherwise, an address of an authorizer contract. This ERC defines "signature aggregator" as authorizer. - * `validUntil` is 6-byte timestamp value, or zero for "infinite". The UserOp is valid only up to this time. - * `validAfter` is 6-byte timestamp. The UserOp is valid only after this time. - -An account that works with aggregated signature, should return its signature aggregator address in the "sigAuthorizer" return value of validateUserOp. -It MAY ignore the signature field - -The core interface required by an aggregator is: - -```solidity -interface IAggregator { - - function validateUserOpSignature(UserOperation calldata userOp) - external view returns (bytes memory sigForUserOp); - - function aggregateSignatures(UserOperation[] calldata userOps) external view returns (bytes memory aggregatesSignature); - - function validateSignatures(UserOperation[] calldata userOps, bytes calldata signature) view external; -} -``` - -* If an account uses an aggregator (returns it from validateUserOp), then its address is returned by `simulateValidation()` reverting with `ValidationResultWithAggregator` instead of `ValidationResult` -* To accept the UserOp, the bundler must call **validateUserOpSignature()** to validate the userOp's signature. -* **aggregateSignatures()** must aggregate all UserOp signature into a single value. -* Note that the above methods are helper method for the bundler. The bundler MAY use a native library to perform the same validation and aggregation logic. -* **validateSignatures()** MUST validate the aggregated signature matches for all UserOperations in the array, and revert otherwise. - This method is called on-chain by `handleOps()` - -#### Semi-abstracted Nonce Support - -In Ethereum protocol, the sequential transaction `nonce` value is used as a replay protection method as well as to -determine the valid order of transaction being included in blocks. - -It also contributes to the transaction hash uniqueness, as a transaction by the same sender with the same -nonce may not be included in the chain twice. - -However, requiring a single sequential `nonce` value is limiting the senders' ability to define their custom logic -with regard to transaction ordering and replay protection. - -Instead of sequential `nonce` we implement a nonce mechanism that uses a single `uint256` nonce value in the `UserOperation`, -but treats it as two values: - -* 192-bit "key" -* 64-bit "sequence" - -These values are represented on-chain in the `EntryPoint` contract. -We define the following method in the `EntryPoint` interface to expose these values: - -```solidity -function getNonce(address sender, uint192 key) external view returns (uint256 nonce); -``` - -For each `key` the `sequence` is validated and incremented sequentially and monotonically by the `EntryPoint` for -each UserOperation, however a new key can be introduced with an arbitrary value at any point. - -This approach maintains the guarantee of `UserOperation` hash uniqueness on-chain on the protocol level while allowing -wallets to implement any custom logic they may need operating on a 192-bit "key" field, while fitting the 32 byte word. - -##### Reading and validating the nonce - -When preparing the UserOp clients may make a view call to this method to determine a valid value for the `nonce` field. - -Bundler's validation of a UserOp should start with `getNonce` to ensure the transaction has a valid `nonce` field. - -If the bundler is willing to accept multiple UserOperations by the same sender into their mempool, -this bundler is supposed to track the `key` and `sequence` pair of the UserOperations already added in the mempool. - -##### Usage examples - -1. Classic sequential nonce. - - In order to require the wallet to have classic, sequential nonce, the validation function should perform: - - ```solidity - require(userOp.nonce> 64; - if (sig == ADMIN_METHODSIG) { - require(key == ADMIN_KEY, "wrong nonce-key for admin operation"); - } else { - require(key == 0, "wrong nonce-key for normal operation"); - } - ``` - -#### Using signature aggregators - -An account signifies it uses signature aggregation returning its address from `validateUserOp`. -During `simulateValidation`, this aggregator is returned (in the `ValidationResultWithAggregator`) - -The bundler should first accept the aggregator (validate its stake info and that it is not throttled/banned) -Then it MUST verify the userOp using `aggregator.validateUserOpSignature()` - -Signature aggregator SHOULD stake just like a paymaster, unless it is exempt due to not accessing global storage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. Bundlers MAY throttle down and ban aggregators in case they take too much -resources (or revert) when the above methods are called in view mode, or if the signature aggregation fails. - -### Required entry point contract functionality - -There are 2 separate entry point methods: `handleOps` and `handleAggregatedOps` - -* `handleOps` handle userOps of accounts that don't require any signature aggregator. -* `handleAggregatedOps` can handle a batch that contains userOps of multiple aggregators (and also requests without any aggregator) -* `handleAggregatedOps` performs the same logic below as `handleOps`, but it must transfer the correct aggregator to each userOp, and also must call `validateSignatures` on each aggregator after doing all the per-account validation. -The entry point's `handleOps` function must perform the following steps (we first describe the simpler non-paymaster case). It must make two loops, the **verification loop** and the **execution loop**. In the verification loop, the `handleOps` call must perform the following steps for each `UserOperation`: - -* **Create the account if it does not yet exist**, using the initcode provided in the `UserOperation`. If the account does not exist, _and_ the initcode is empty, or does not deploy a contract at the "sender" address, the call must fail. -* **Call `validateUserOp` on the account**, passing in the `UserOperation`, the required fee and aggregator (if there is one). The account should verify the operation's signature, and pay the fee if the account considers the operation valid. If any `validateUserOp` call fails, `handleOps` must skip execution of at least that operation, and may revert entirely. -* Validate the account's deposit in the entryPoint is high enough to cover the max possible cost (cover the already-done verification and max execution gas) - -In the execution loop, the `handleOps` call must perform the following steps for each `UserOperation`: - -* **Call the account with the `UserOperation`'s calldata**. It's up to the account to choose how to parse the calldata; an expected workflow is for the account to have an `execute` function that parses the remaining calldata as a series of one or more calls that the account should make. - -![](../assets/eip-4337/image1.png) - -Before accepting a `UserOperation`, bundlers should use an RPC method to locally call the `simulateValidation` function of the entry point, to verify that the signature is correct and the operation actually pays fees; see the [Simulation section below](#simulation) for details. -A node/bundler SHOULD drop (not add to the mempool) a `UserOperation` that fails the validation - -### Extension: paymasters - -We extend the entry point logic to support **paymasters** that can sponsor transactions for other users. This feature can be used to allow application developers to subsidize fees for their users, allow users to pay fees with [ERC-20](./eip-20.md) tokens and many other use cases. When the paymaster is not equal to the zero address, the entry point implements a different flow: - -![](../assets/eip-4337/image2.png) - -During the verification loop, in addition to calling `validateUserOp`, the `handleOps` execution also must check that the paymaster has enough ETH deposited with the entry point to pay for the operation, and then call `validatePaymasterUserOp` on the paymaster to verify that the paymaster is willing to pay for the operation. Note that in this case, the `validateUserOp` is called with a `missingAccountFunds` of 0 to reflect that the account's deposit is not used for payment for this userOp. - -If the paymaster's validatePaymasterUserOp returns a "context", then `handleOps` must call `postOp` on the paymaster after making the main execution call. It must guarantee the execution of `postOp`, by making the main execution inside an inner call context, and if the inner call context reverts attempting to call `postOp` again in an outer call context. - -Maliciously crafted paymasters _can_ DoS the system. To prevent this, we use a reputation system. paymaster must either limit its storage usage, or have a stake. see the [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. - -The paymaster interface is as follows: - -```c++ -function validatePaymasterUserOp - (UserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost) - external returns (bytes memory context, uint256 validationData); - -function postOp - (PostOpMode mode, bytes calldata context, uint256 actualGasCost) - external; - -enum PostOpMode { - opSucceeded, // user op succeeded - opReverted, // user op reverted. still has to pay for gas. - postOpReverted // user op succeeded, but caused postOp to revert -} -``` - - -```c++ -// add a paymaster stake (must be called by the paymaster) -function addStake(uint32 _unstakeDelaySec) external payable - -// unlock the stake (must wait unstakeDelay before can withdraw) -function unlockStake() external - -// withdraw the unlocked stake -function withdrawStake(address payable withdrawAddress) external -``` - -The paymaster must also have a deposit, which the entry point will charge UserOperation costs from. -The deposit (for paying gas fees) is separate from the stake (which is locked). - -The entry point must implement the following interface to allow paymasters (and optionally accounts) manage their deposit: - -```c++ -// return the deposit of an account -function balanceOf(address account) public view returns (uint256) - -// add to the deposit of the given account -function depositTo(address account) public payable - -// withdraw from the deposit -function withdrawTo(address payable withdrawAddress, uint256 withdrawAmount) external -``` - -### Client behavior upon receiving a UserOperation - -When a client receives a `UserOperation`, it must first run some basic sanity checks, namely that: - -* Either the `sender` is an existing contract, or the `initCode` is not empty (but not both) -* If `initCode` is not empty, parse its first 20 bytes as a factory address. Record whether the factory is staked, in case the later simulation indicates that it needs to be. If the factory accesses global state, it must be staked - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. -* The `verificationGasLimit` is sufficiently low (`<= MAX_VERIFICATION_GAS`) and the `preVerificationGas` is sufficiently high (enough to pay for the calldata gas cost of serializing the `UserOperation` plus `PRE_VERIFICATION_OVERHEAD_GAS`) -* The `paymasterAndData` is either empty, or start with the **paymaster** address, which is a contract that (i) currently has nonempty code on chain, (ii) has a sufficient deposit to pay for the UserOperation, and (iii) is not currently banned. During simulation, the paymaster's stake is also checked, depending on its storage usage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. -* The callgas is at least the cost of a `CALL` with non-zero value. -* The `maxFeePerGas` and `maxPriorityFeePerGas` are above a configurable minimum value that the client is willing to accept. At the minimum, they are sufficiently high to be included with the current `block.basefee`. -* The sender doesn't have another `UserOperation` already present in the pool (or it replaces an existing entry with the same sender and nonce, with a higher `maxPriorityFeePerGas` and an equally increased `maxFeePerGas`). Only one `UserOperation` per sender may be included in a single batch. A sender is exempt from this rule and may have multiple `UserOperations` in the pool and in a batch if it is staked (see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) below), but this exception is of limited use to normal accounts. - -If the `UserOperation` object passes these sanity checks, the client must next run the first op simulation, and if the simulation succeeds, the client must add the op to the pool. A second simulation must also happen during bundling to make sure the UserOperation is still valid. - -### Simulation - -#### Simulation Rationale - -In order to add a UserOperation into the mempool (and later to add it into a bundle) we need to "simulate" its validation to make sure it is valid, and that it is capable of paying for its own execution. -In addition, we need to verify that the same will hold true when executed on-chain. -For this purpose, a UserOperation is not allowed to access any information that might change between simulation and execution, such as current block time, number, hash etc. -In addition, a UserOperation is only allowed to access data related to this sender address: Multiple UserOperations should not access the same storage, so that it is impossible to invalidate a large number of UserOperations with a single state change. -There are 3 special contracts that interact with the account: the factory (initCode) that deploys the contract, the paymaster that can pay for the gas, and signature aggregator (described later) -Each of these contracts is also restricted in its storage access, to make sure UserOperation validations are isolated. - -#### Specification: - -To simulate a `UserOperation` validation, the client makes a view call to `simulateValidation(userop)` - -This method always revert with `ValidationResult` as successful response. -If the call reverts with other error, the client rejects this `userOp`. - -The simulated call performs the full validation, by calling: - -1. If `initCode` is present, create the account. -2. `account.validateUserOp`. -3. if specified a paymaster: `paymaster.validatePaymasterUserOp`. - -Either `validateUserOp` or `validatePaymasterUserOp` may return a "validAfter" and "validUntil" timestamps, which is the time-range that this UserOperation is valid on-chain. -The simulateValidation call returns this range. -A node MAY drop a UserOperation if it expires too soon (e.g. wouldn't make it to the next block) -If the `ValidationResult` includes `sigFail`, the client SHOULD drop the `UserOperation`. - -The operations differ in their opcode banning policy. -In order to distinguish between them, there is a call to the NUMBER opcode (`block.number`), used as a delimiter between the 3 functions. -While simulating `userOp` validation, the client should make sure that: - -1. May not invokes any **forbidden opcodes** -2. Must not use GAS opcode (unless followed immediately by one of { `CALL`, `DELEGATECALL`, `CALLCODE`, `STATICCALL` }.) -3. Storage access is limited as follows: - 1. self storage (of factory/paymaster, respectively) is allowed, but only if self entity is staked - 2. account storage access is allowed (see Storage access by Slots, below), - 3. in any case, may not use storage used by another UserOp `sender` in the same bundle (that is, paymaster and factory are not allowed as senders) -4. Limitation on "CALL" opcodes (`CALL`, `DELEGATECALL`, `CALLCODE`, `STATICCALL`): - 1. must not use value (except from account to the entrypoint) - 2. must not revert with out-of-gas - 3. destination address must have code (EXTCODESIZE>0) or be a standard Ethereum precompile defined at addresses from `0x01` to `0x09` - 4. cannot call EntryPoint's methods, except `depositFor` (to avoid recursion) -5. `EXTCODEHASH` of every address accessed (by any opcode) does not change between first and second simulations of the op. -6. `EXTCODEHASH`, `EXTCODELENGTH`, `EXTCODECOPY` may not access address with no code. -7. If `op.initcode.length != 0` , allow only one `CREATE2` opcode call (in the first (deployment) block), otherwise forbid `CREATE2`. - -Transient Storage slots defined in [EIP-1153](./eip-1153.md) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes -must follow the exact same validation rules as persistent storage if Transient Storage is enabled. - -#### Storage associated with an address - -We define storage slots as "associated with an address" as all the slots that uniquely related on this address, and cannot be related with any other address. -In solidity, this includes all storage of the contract itself, and any storage of other contracts that use this contract address as a mapping key. - -An address `A` is associated with: - -1. Slots of contract `A` address itself. -2. Slot `A` on any other address. -3. Slots of type `keccak256(A || X) + n` on any other address. (to cover `mapping(address => value)`, which is usually used for balance in ERC-20 tokens). - `n` is an offset value up to 128, to allow accessing fields in the format `mapping(address => struct)` - -#### Alternative Mempools - -The simulation rules above are strict and prevent the ability of paymasters and signature aggregators to grief the system. -However, there might be use-cases where specific paymasters (and signature aggregators) can be validated -(through manual auditing) and verified that they cannot cause any problem, while still require relaxing of the opcode rules. -A bundler cannot simply "whitelist" request from a specific paymaster: if that paymaster is not accepted by all -bundlers, then its support will be sporadic at best. -Instead, we introduce the term "alternate mempool". -UserOperations that use whitelisted paymasters (or signature aggregators) are put into a separate mempool. -Only bundlers that support this whitelist will use UserOperations from this mempool. -These UserOperations can be bundled together with UserOperations from the main mempool - -### Bundling - -During bundling, the client should: - -* Exclude UserOps that access any sender address of another UserOp in the same batch. -* Exclude UserOps that access any address created by another UserOp validation in the same batch (via a factory). -* For each paymaster used in the batch, keep track of the balance while adding UserOps. Ensure that it has sufficient deposit to pay for all the UserOps that use it. -* Sort UserOps by aggregator, to create the lists of UserOps-per-aggregator. -* For each aggregator, run the aggregator-specific code to create aggregated signature, and update the UserOps - -After creating the batch, before including the transaction in a block, the client should: - -* Run `debug_traceCall` with maximum possible gas, to enforce the validation opcode and precompile banning and storage access rules, - as well as to verify the entire `handleOps` batch transaction, - and use the consumed gas for the actual transaction execution. -* If the call reverted, check the `FailedOp` event. - A `FailedOp` during `handleOps` simulation is an unexpected event since it was supposed to be caught - by the single-UserOperation simulation. -* If any verification context rule was violated the bundlers should treat it the same as - if this UserOperation reverted with a `FailedOp` event. -* Remove the offending UserOperation from the current bundle and from mempool. -* If the error is caused by a `factory` (error code is `AA1x`) or a `paymaster` (error code is `AA3x`), and the `sender` - of the UserOp **is not** a staked entity, then issue a "ban" (see ["Reputation, throttling and banning"](#reputation-scoring-and-throttlingbanning-for-global-entities)) - for the guilty factory or paymaster. -* If the error is caused by a `factory` (error code is `AA1x`) or a `paymaster` (error code is `AA3x`), and the `sender` - of the UserOp **is** a staked entity, do not ban the `factory` / `paymaster` from the mempool. - Instead, issue a "ban" for the staked `sender` entity. -* Repeat until `debug_traceCall` succeeds. - -As staked entries may use some kind of transient storage to communicate data between UserOperations in the same bundle, -it is critical that the exact same opcode and precompile banning rules as well as storage access rules are enforced -for the `handleOps` validation in its entirety as for individual UserOperations. -Otherwise, attackers may be able to use the banned opcodes to detect running on-chain and trigger a `FailedOp` revert. - -Banning an offending entity for a given bundler is achieved by increasing its `opsSeen` value by `1000000` -and removing all UserOperations for this entity already present in the mempool. -This change will allow the negative reputation value to deteriorate over time consistent with other banning reasons. - -If any of the three conditions is violated, the client should reject the `op`. If both calls succeed (or, if `op.paymaster == ZERO_ADDRESS` and the first call succeeds)without violating the three conditions, the client should accept the op. On a bundler node, the storage keys accessed by both calls must be saved as the `accessList` of the `UserOperation` - -When a bundler includes a bundle in a block it must ensure that earlier transactions in the block don't make any UserOperation fail. It should either use access lists to prevent conflicts, or place the bundle as the first transaction in the block. - -#### Forbidden opcodes - -The forbidden opcodes are to be forbidden when `depth > 2` (i.e. when it is the factory, account, paymaster, or other contracts called by them that are being executed). They are: `GASPRICE`, `GASLIMIT`, `DIFFICULTY`, `TIMESTAMP`, `BASEFEE`, `BLOCKHASH`, `NUMBER`, `SELFBALANCE`, `BALANCE`, `ORIGIN`, `GAS`, `CREATE`, `COINBASE`, `SELFDESTRUCT`. They should only be forbidden during verification, not execution. These opcodes are forbidden because their outputs may differ between simulation and execution, so simulation of calls using these opcodes does not reliably tell what would happen if these calls are later done on-chain. - -Exceptions to the forbidden opcodes: - -1. A single `CREATE2` is allowed if `op.initcode.length != 0` and must result in the deployment of a previously-undeployed `UserOperation.sender`. -2. `GAS` is allowed if followed immediately by one of { `CALL`, `DELEGATECALL`, `CALLCODE`, `STATICCALL` }. - (that is, making calls is allowed, using `gasleft()` or `gas` opcode directly is forbidden) - -### Reputation scoring and throttling/banning for global entities - -#### Reputation Rationale. - -UserOperation's storage access rules prevent them from interfere with each other. -But "global" entities - paymasters, factories and aggregators are accessed by multiple UserOperations, and thus might invalidate multiple previously-valid UserOperations. - -To prevent abuse, we throttle down (or completely ban for a period of time) an entity that causes invalidation of large number of UserOperations in the mempool. -To prevent such entities from "sybil-attack", we require them to stake with the system, and thus make such DoS attack very expensive. -Note that this stake is never slashed, and can be withdrawn any time (after unstake delay) - -Unstaked entities are allowed, under the rules below. - -When staked, an entity is also allowed to use its own associated storage, in addition to sender's associated storage. - -The stake value is not enforced on-chain, but specifically by each node while simulating a transaction. -The stake is expected to be above MIN_STAKE_VALUE, and unstake delay above MIN_UNSTAKE_DELAY -The value of MIN_UNSTAKE_DELAY is 84600 (one day) -The value of MIN_STAKE_VALUE is determined per chain, and specified in the "bundler specification test suite" - -#### Un-staked entities - -Under the following special conditions, unstaked entities still can be used: - -* An entity that doesn't use any storage at all, or only the senders's storage (not the entity's storage - that does require a stake) -* If the UserOp doesn't create a new account (that is initCode is empty), or the UserOp creates a new account using a - staked `factory` contract, then the entity may also use [storage associated with the sender](#storage-associated-with-an-address)) -* A paymaster that has a “postOp()” method (that is, validatePaymasterUserOp returns “context”) must be staked - -#### Specification. - -In the following specification, "entity" is either address that is explicitly referenced by the UserOperation: sender, factory, paymaster and aggregator. -Clients maintain two mappings with a value for staked entities: - -* `opsSeen: Map[Address, int]` -* `opsIncluded: Map[Address, int]` - -If an entity doesn't use storage at all, or only reference storage associated with the "sender" (see [Storage associated with an address](#storage-associated-with-an-address)), then it is considered "OK", without using the rules below. - -When the client learns of a new staked entity, it sets `opsSeen[entity] = 0` and `opsIncluded[entity] = 0` . - -The client sets `opsSeen[entity] +=1` each time it adds an op with that `entity` to the `UserOperationPool`, and the client sets `opsIncluded[entity] += 1` each time an op that was in the `UserOperationPool` is included on-chain. - -Every hour, the client sets `opsSeen[entity] -= opsSeen[entity] // 24` and `opsIncluded[entity] -= opsIncluded[entity] // 24` for all entities (so both values are 24-hour exponential moving averages). - -We define the **status** of an entity as follows: - -```python -OK, THROTTLED, BANNED = 0, 1, 2 - -def status(paymaster: Address, - opsSeen: Map[Address, int], - opsIncluded: Map[Address, int]): - if paymaster not in opsSeen: - return OK - min_expected_included = opsSeen[paymaster] // MIN_INCLUSION_RATE_DENOMINATOR - if min_expected_included <= opsIncluded[paymaster] + THROTTLING_SLACK: - return OK - elif min_expected_included <= opsIncluded[paymaster] + BAN_SLACK: - return THROTTLED - else: - return BANNED -``` - -Stated in simpler terms, we expect at least `1 / MIN_INCLUSION_RATE_DENOMINATOR` of all ops seen on the network to get included. If an entity falls too far behind this minimum, it gets **throttled** (meaning, the client does not accept ops from that paymaster if there is already an op with that entity, and an op only stays in the pool for 10 blocks), If the entity falls even further behind, it gets **banned**. Throttling and banning naturally decay over time because of the exponential-moving-average rule. - -**Non-bundling clients and bundlers should use different settings for the above params**: - -| Param | Client setting | Bundler setting | -| - | - | - | -| `MIN_INCLUSION_RATE_DENOMINATOR` | 100 | 10 | -| `THROTTLING_SLACK` | 10 | 10 | -| `BAN_SLACK` | 50 | 50 | - -To help make sense of these params, note that a malicious paymaster can at most cause the network (only the p2p network, not the blockchain) to process `BAN_SLACK * MIN_INCLUSION_RATE_DENOMINATOR / 24` non-paying ops per hour. - -## Rationale - -The main challenge with a purely smart contract wallet based account abstraction system is DoS safety: how can a block builder including an operation make sure that it will actually pay fees, without having to first execute the entire operation? Requiring the block builder to execute the entire operation opens a DoS attack vector, as an attacker could easily send many operations that pretend to pay a fee but then revert at the last moment after a long execution. Similarly, to prevent attackers from cheaply clogging the mempool, nodes in the P2P network need to check if an operation will pay a fee before they are willing to forward it. - -In this proposal, we expect accounts to have a `validateUserOp` method that takes as input a `UserOperation`, and verify the signature and pay the fee. This method is required to be almost-pure: it is only allowed to access the storage of the account itself, cannot use environment opcodes (eg. `TIMESTAMP`), and can only edit the storage of the account, and can also send out ETH (needed to pay the entry point). The method is gas-limited by the `verificationGasLimit` of the `UserOperation`; nodes can choose to reject operations whose `verificationGasLimit` is too high. These restrictions allow block builders and network nodes to simulate the verification step locally, and be confident that the result will match the result when the operation actually gets included into a block. - -The entry point-based approach allows for a clean separation between verification and execution, and keeps accounts' logic simple. The alternative would be to require accounts to follow a template where they first self-call to verify and then self-call to execute (so that the execution is sandboxed and cannot cause the fee payment to revert); template-based approaches were rejected due to being harder to implement, as existing code compilation and verification tooling is not designed around template verification. - -### Paymasters - -Paymasters facilitate transaction sponsorship, allowing third-party-designed mechanisms to pay for transactions. Many of these mechanisms _could_ be done by having the paymaster wrap a `UserOperation` with their own, but there are some important fundamental limitations to that approach: - -* No possibility for "passive" paymasters (eg. that accept fees in some ERC-20 token at an exchange rate pulled from an on-chain DEX) -* Paymasters run the risk of getting griefed, as users could send ops that appear to pay the paymaster but then change their behavior after a block - -The paymaster scheme allows a contract to passively pay on users' behalf under arbitrary conditions. It even allows ERC-20 token paymasters to secure a guarantee that they would only need to pay if the user pays them: the paymaster contract can check that there is sufficient approved ERC-20 balance in the `validatePaymasterUserOp` method, and then extract it with `transferFrom` in the `postOp` call; if the op itself transfers out or de-approves too much of the ERC-20s, the inner `postOp` will fail and revert the execution and the outer `postOp` can extract payment (note that because of storage access restrictions the ERC-20 would need to be a wrapper defined within the paymaster itself). - -### First-time account creation - -It is an important design goal of this proposal to replicate the key property of EOAs that users do not need to perform some custom action or rely on an existing user to create their wallet; they can simply generate an address locally and immediately start accepting funds. - -The wallet creation itself is done by a "factory" contract, with wallet-specific data. -The factory is expected to use CREATE2 (not CREATE) to create the wallet, so that the order of creation of wallets doesn't interfere with the generated addresses. -The `initCode` field (if non-zero length) is parsed as a 20-byte address, followed by "calldata" to pass to this address. -This method call is expected to create a wallet and return its address. -If the factory does use CREATE2 or some other deterministic method to create the wallet, it's expected to return the wallet address even if the wallet has already been created. This is to make it easier for clients to query the address without knowing if the wallet has already been deployed, by simulating a call to `entryPoint.getSenderAddress()`, which calls the factory under the hood. -When `initCode` is specified, if either the `sender` address points to an existing contract, or (after calling the initCode) the `sender` address still does not exist, -then the operation is aborted. -The `initCode` MUST NOT be called directly from the entryPoint, but from another address. -The contract created by this factory method should accept a call to `validateUserOp` to validate the UserOp's signature. -For security reasons, it is important that the generated contract address will depend on the initial signature. -This way, even if someone can create a wallet at that address, he can't set different credentials to control it. -The factory has to be staked if it accesses global storage - see [reputation, throttling and banning section](#reputation-scoring-and-throttlingbanning-for-global-entities) for details. - -NOTE: In order for the wallet to determine the "counterfactual" address of the wallet (prior its creation), -it should make a static call to the `entryPoint.getSenderAddress()` - -### Entry point upgrading - -Accounts are encouraged to be DELEGATECALL forwarding contracts for gas efficiency and to allow account upgradability. The account code is expected to hard-code the entry point into their code for gas efficiency. If a new entry point is introduced, whether to add new functionality, improve gas efficiency, or fix a critical security bug, users can self-call to replace their account's code address with a new code address containing code that points to a new entry point. During an upgrade process, it's expected that two mempools will run in parallel. - -### RPC methods (eth namespace) - -#### * eth_sendUserOperation - -eth_sendUserOperation submits a User Operation object to the User Operation pool of the client. The client MUST validate the UserOperation, and return a result accordingly. - -The result `SHOULD` be set to the **userOpHash** if and only if the request passed simulation and was accepted in the client's User Operation pool. If the validation, simulation, or User Operation pool inclusion fails, `result` `SHOULD NOT` be returned. Rather, the client `SHOULD` return the failure reason. - -##### Parameters: - -1. **UserOperation** a full user-operation struct. All fields MUST be set as hex values. empty `bytes` block (e.g. empty `initCode`) MUST be set to `"0x"` -2. **EntryPoint** the entrypoint address the request should be sent through. this MUST be one of the entry points returned by the `supportedEntryPoints` rpc call. - -##### Return value: - -* If the UserOperation is valid, the client MUST return the calculated **userOpHash** for it -* in case of failure, MUST return an `error` result object, with `code` and `message`. The error code and message SHOULD be set as follows: - * **code: -32602** - invalid UserOperation struct/fields - * **code: -32500** - transaction rejected by entryPoint's simulateValidation, during wallet creation or validation - * The `message` field MUST be set to the FailedOp's "`AAxx`" error message from the EntryPoint - * **code: -32501** - transaction rejected by paymaster's validatePaymasterUserOp - * The `message` field SHOULD be set to the revert message from the paymaster - * The `data` field MUST contain a `paymaster` value - * **code: -32502** - transaction rejected because of opcode validation - * **code: -32503** - UserOperation out of time-range: either wallet or paymaster returned a time-range, and it is already expired (or will expire soon) - * The `data` field SHOULD contain the `validUntil` and `validAfter` values - * The `data` field SHOULD contain a `paymaster` value, if this error was triggered by the paymaster - * **code: -32504** - transaction rejected because paymaster (or signature aggregator) is throttled/banned - * The `data` field SHOULD contain a `paymaster` or `aggregator` value, depending on the failed entity - * **code: -32505** - transaction rejected because paymaster (or signature aggregator) stake or unstake-delay is too low - * The `data` field SHOULD contain a `paymaster` or `aggregator` value, depending on the failed entity - * The `data` field SHOULD contain a `minimumStake` and `minimumUnstakeDelay` - * **code: -32506** - transaction rejected because wallet specified unsupported signature aggregator - * The `data` field SHOULD contain an `aggregator` value - * **code: -32507** - transaction rejected because of wallet signature check failed (or paymaster signature, if the paymaster uses its data as signature) - -##### Example: - -Request: - -```json= -{ - "jsonrpc": "2.0", - "id": 1, - "method": "eth_sendUserOperation", - "params": [ - { - sender, // address - nonce, // uint256 - initCode, // bytes - callData, // bytes - callGasLimit, // uint256 - verificationGasLimit, // uint256 - preVerificationGas, // uint256 - maxFeePerGas, // uint256 - maxPriorityFeePerGas, // uint256 - paymasterAndData, // bytes - signature // bytes - }, - entryPoint // address - ] -} - -``` - -Response: - -``` -{ - "jsonrpc": "2.0", - "id": 1, - "result": "0x1234...5678" -} -``` - -##### Example failure responses: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "error": { - "message": "AA21 didn't pay prefund", - "code": -32500 - } -} -``` - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "error": { - "message": "paymaster stake too low", - "data": { - "paymaster": "0x123456789012345678901234567890123456790", - "minimumStake": "0xde0b6b3a7640000", - "minimumUnstakeDelay": "0x15180" - }, - "code": -32504 - } -} -``` - - -#### * eth_estimateUserOperationGas - -Estimate the gas values for a UserOperation. -Given UserOperation optionally without gas limits and gas prices, return the needed gas limits. -The signature field is ignored by the wallet, so that the operation will not require user's approval. -Still, it might require putting a "semi-valid" signature (e.g. a signature in the right length) - -**Parameters**: same as `eth_sendUserOperation` - gas limits (and prices) parameters are optional, but are used if specified. - `maxFeePerGas` and `maxPriorityFeePerGas` default to zero, so no payment is required by neither account nor paymaster. - -**Return Values:** - -* **preVerificationGas** gas overhead of this UserOperation -* **verificationGasLimit** actual gas used by the validation of this UserOperation -* **callGasLimit** value used by inner account execution - -##### Error Codes: - -Same as `eth_sendUserOperation` -This operation may also return an error if the inner call to the account contract reverts. - -#### * eth_getUserOperationByHash - -Return a UserOperation based on a hash (userOpHash) returned by `eth_sendUserOperation` - -**Parameters** - -* **hash** a userOpHash value returned by `eth_sendUserOperation` - -**Return value**: - -`null` in case the UserOperation is not yet included in a block, or a full UserOperation, with the addition of `entryPoint`, `blockNumber`, `blockHash` and `transactionHash` - -#### * eth_getUserOperationReceipt - -Return a UserOperation receipt based on a hash (userOpHash) returned by `eth_sendUserOperation` - -**Parameters** - -* **hash** a userOpHash value returned by `eth_sendUserOperation` - -**Return value**: - -`null` in case the UserOperation is not yet included in a block, or: - -* **userOpHash** the request hash -* **entryPoint** -* **sender** -* **nonce** -* **paymaster** the paymaster used for this userOp (or empty) -* **actualGasCost** - actual amount paid (by account or paymaster) for this UserOperation -* **actualGasUsed** - total gas used by this UserOperation (including preVerification, creation, validation and execution) -* **success** boolean - did this execution completed without revert -* **reason** in case of revert, this is the revert reason -* **logs** the logs generated by this UserOperation (not including logs of other UserOperations in the same bundle) -* **receipt** the TransactionReceipt object. - Note that the returned TransactionReceipt is for the entire bundle, not only for this UserOperation. - -#### * eth_supportedEntryPoints - -Returns an array of the entryPoint addresses supported by the client. The first element of the array `SHOULD` be the entryPoint addressed preferred by the client. - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "eth_supportedEntryPoints", - "params": [] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": [ - "0xcd01C8aa8995A59eB7B2627E69b40e0524B5ecf8", - "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6" - ] -} -``` - -#### * eth_chainId - -Returns [EIP-155](./eip-155.md) Chain ID. - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "eth_chainId", - "params": [] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": "0x1" -} -``` - -### RPC methods (debug Namespace) - -This api must only be available on testing mode and is required by the compatibility test suite. In production, any `debug_*` rpc calls should be blocked. - -#### * debug_bundler_clearState - -Clears the bundler mempool and reputation data of paymasters/accounts/factories/aggregators. - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "debug_bundler_clearState", - "params": [] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": "ok" -} -``` - -#### * debug_bundler_dumpMempool - -Dumps the current UserOperations mempool - -**Parameters:** - -* **EntryPoint** the entrypoint used by eth_sendUserOperation - -**Returns:** - -`array` - Array of UserOperations currently in the mempool. - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "debug_bundler_dumpMempool", - "params": ["0x1306b01bC3e4AD202612D3843387e94737673F53"] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": [ - { - sender, // address - nonce, // uint256 - initCode, // bytes - callData, // bytes - callGasLimit, // uint256 - verificationGasLimit, // uint256 - preVerificationGas, // uint256 - maxFeePerGas, // uint256 - maxPriorityFeePerGas, // uint256 - paymasterAndData, // bytes - signature // bytes - } - ] -} -``` - -#### * debug_bundler_sendBundleNow - -Forces the bundler to build and execute a bundle from the mempool as `handleOps()` transaction. - -Returns: `transactionHash` - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "debug_bundler_sendBundleNow", - "params": [] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": "0xdead9e43632ac70c46b4003434058b18db0ad809617bd29f3448d46ca9085576" -} -``` - -#### * debug_bundler_setBundlingMode - -Sets bundling mode. - -After setting mode to "manual", an explicit call to debug_bundler_sendBundleNow is required to send a bundle. - -##### parameters: - -`mode` - 'manual' | 'auto' - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "debug_bundler_setBundlingMode", - "params": ["manual"] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": "ok" -} -``` - -#### * debug_bundler_setReputation - -Sets reputation of given addresses. parameters: - -**Parameters:** - -* An array of reputation entries to add/replace, with the fields: - - * `address` - The address to set the reputation for. - * `opsSeen` - number of times a user operations with that entity was seen and added to the mempool - * `opsIncluded` - number of times a user operations that uses this entity was included on-chain - * `status` - (string) The status of the address in the bundler 'ok' | 'throttled' | 'banned'. - -* **EntryPoint** the entrypoint used by eth_sendUserOperation - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "debug_bundler_setReputation", - "params": [ - [ - { - "address": "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6", - "opsSeen": 20, - "opsIncluded": 13 - } - ], - "0x1306b01bC3e4AD202612D3843387e94737673F53" - ] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": "ok" -} -``` - - -#### * debug_bundler_dumpReputation - -Returns the reputation data of all observed addresses. -Returns an array of reputation objects, each with the fields described above in `debug_bundler_setReputation` with the - - -**Parameters:** - -* **EntryPoint** the entrypoint used by eth_sendUserOperation - -**Return value:** - -An array of reputation entries with the fields: - -* `address` - The address to set the reputation for. -* `opsSeen` - number of times a user operations with that entity was seen and added to the mempool -* `opsIncluded` - number of times a user operations that uses this entity was included on-chain -* `status` - (string) The status of the address in the bundler 'ok' | 'throttled' | 'banned'. - -```json= -# Request -{ - "jsonrpc": "2.0", - "id": 1, - "method": "debug_bundler_dumpReputation", - "params": ["0x1306b01bC3e4AD202612D3843387e94737673F53"] -} - -# Response -{ - "jsonrpc": "2.0", - "id": 1, - "result": [ - { "address": "0x7A0A0d159218E6a2f407B99173A2b12A6DDfC2a6", - "opsSeen": 20, - "opsIncluded": 19, - "status": "ok" - } - ] -} -``` - -## Backwards Compatibility - -This EIP does not change the consensus layer, so there are no backwards compatibility issues for Ethereum as a whole. Unfortunately it is not easily compatible with pre-[ERC-4337](./eip-4337.md) accounts, because those accounts do not have a `validateUserOp` function. If the account has a function for authorizing a trusted op submitter, then this could be fixed by creating an [ERC-4337](./eip-4337.md) compatible account that re-implements the verification logic as a wrapper and setting it to be the original account's trusted op submitter. - -## Reference Implementation - -See `https://github.com/eth-infinitism/account-abstraction/tree/main/contracts` - -## Security Considerations - -The entry point contract will need to be very heavily audited and formally verified, because it will serve as a central trust point for _all_ [ERC-4337](./eip-4337.md). In total, this architecture reduces auditing and formal verification load for the ecosystem, because the amount of work that individual _accounts_ have to do becomes much smaller (they need only verify the `validateUserOp` function and its "check signature, increment nonce and pay fees" logic) and check that other functions are `msg.sender == ENTRY_POINT` gated (perhaps also allowing `msg.sender == self`), but it is nevertheless the case that this is done precisely by concentrating security risk in the entry point contract that needs to be verified to be very robust. - -Verification would need to cover two primary claims (not including claims needed to protect paymasters, and claims needed to establish p2p-level DoS resistance): - -* **Safety against arbitrary hijacking**: The entry point only calls an account generically if `validateUserOp` to that specific account has passed (and with `op.calldata` equal to the generic call's calldata) -* **Safety against fee draining**: If the entry point calls `validateUserOp` and passes, it also must make the generic call with calldata equal to `op.calldata` - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4337.md diff --git a/EIPS/eip-4341.md b/EIPS/eip-4341.md index 6bc8c21655a56c..1cd2db5ac0ffc0 100644 --- a/EIPS/eip-4341.md +++ b/EIPS/eip-4341.md @@ -1,124 +1 @@ ---- -eip: 4341 -title: Ordered NFT Batch Standard -description: The ordering information of multiple NFTs is retained and managed -author: Simon Tian (@simontianx) -discussions-to: https://github.com/ethereum/EIPs/issues/3782 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-10-01 ---- - -## Abstract -This standard introduces a smart contract interface that can represent a batch -of non-fungible tokens of which the ordering information shall be retained and -managed. Such information is particularly useful if `tokenId`s are encoded with -the sets of `unicodes` for logographic characters and emojis. As a result, NFTs -can be utilized as carriers of meanings. - -## Motivation -Non-fungible tokens are widely accepted as carriers of crypto-assets, hence in both -[ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md), the ordering information of -multiple NFTs is discarded. However, as proposed in [EIP-3754](./eip-3754.md), -non-fungible tokens are thought of as basic units on a blockchain and can carry -abstract meanings with unicoded `tokenId`s. Transferring such tokens is transmitting -an ordered sequence of unicodes, thus effectively transmitting phrases or meanings -on a blockchain. - -A **[logograph](https://en.wikipedia.org/wiki/Logogram)** is a written character -that represents a word or morpheme, examples include _hanzi_ in Mandarin, _kanji_ -in Japanese, _hanja_ in Korean, and etc. A [unicode](https://en.wikipedia.org/wiki/Unicode) -is an information technology standard for the consistent encoding, representation, and -handling of texts. - -It is natural to combine the two to create unicoded NFTs to represent logographic -characters. Since a rich amount of meanings can be transmitted in just a few -characters in such languages, it is technically practical and valuable to create -a standard for it. Emojis are similar with logographs and can be included as well. -For non-logographic languages such as English, although the same standard can be -applied, it is tedious to represent each letter with an NFT, hence the gain is -hardly justifiable. - -A motivating example is instead of sending the two Chinese characters of the -Great Wall `长城`, two NFTs with IDs `#38271` and `#22478` respectively can be -transferred in a batch. The two IDs are corresponding to the decimal unicode of -the two characters. The receiving end decodes the IDs and retrieves the original -characters. A key point is the ordering information matters in this scenario -since the tuples `(38271, 22478)` and `(22478, 38271)` can be decoded as -`长城` and `城长`, respectively, and both are legitimate words in the Chinese -language. This illustrates the key difference between this standard and [ERC-1155](./eip-1155.md). - -Besides, in the eastern Asian culture, characters are sometimes considered or -practically used as gifts in holidays such as Spring Feastival, etc. -`(24685, 21916, 21457, 36001)` `恭喜发财` can be used literally as a gift to -express the best wishes for financial prosperity. It is therefore cuturally -natural to transfer tokens to express meanings with this standard. - -Also in logographic language systems, ancient teachings are usually written in -concise ways such that a handful of characters can unfold a rich amount of -meanings. Modern people now get a reliable technical means to pass down their -words, poems and proverbs to the future generations by sending tokens. - -Other practical and interesting applications include Chinese chess, wedding -vows, family generation quotes and sayings, funeral commendation words, prayers, -anecdotes and etc. - -## Specification -``` -pragma solidity ^0.8.0; - -/** - @title EIP-4341 Multi Ordered NFT Standard - @dev See https://eips.ethereum.org/EIPS/eip-4341 - */ -interface ERC4341 /* is ERC165 */ { - event Transfer(address indexed from, address indexed to, uint256 id, uint256 amount); - - event TransferBatch(address indexed from, address indexed to, uint256[] ids, uint256[] amounts); - - event ApprovalForAll(address indexed owner, address indexed operator, bool approved); - - function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; - - function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external; - - function safePhraseTransferFrom(address from, address to, uint256[] calldata phrase, bytes calldata data) external; - - function balanceOf(address owner, uint256 id) external view returns (uint256); - - function balanceOfPhrase(address owner) external view returns (uint256); - - function balanceOfBatch(address[] calldata owners, uint256[] calldata ids) external view returns (uint256[] memory); - - function retrievePhrase(address owner, uint256 phraseId) external view returns (uint256[] memory); - - function setApprovalForAll(address operator, bool approved) external; - - function isApprovedForAll(address owner, address operator) external view returns (bool); -} -``` - -## Rationale -In [ERC-1155](./eip-1155.md) and [ERC-721](./eip-721.md), NFTs are used to represent -crypto-assets, and in this standard together with [EIP-3754](./eip-3754.md), NFTs -are equipped with utilities. In this standard, the ordering information of a batch -of NFTs is retained and managed through a construct `phrase`. - -### Phrase -A `phrase` is usually made of a handful of basic characters or an orderred sequence -of unicodes and is able to keep the ordering information in a batch of tokens. -Technically, it is stored in an array of unsigned integers, and is not supposed -to be disseminated. A phrase does not increase or decrease the amount of any NFT -in anyway. A phrase cannot be transferred, however, it can be retrieved and -decoded to restore the original sequence of unicodes. The phrase information -is kept in storage and hence additional storage than [ERC-1155](./eip-1155.md) is required. - -## Backwards Compatibility -[EIP-3754](./eip-3754.md) is the pre-requisite to this standard. - -## Reference Implementation -https://github.com/simontianx/ERC4341 - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4341.md diff --git a/EIPS/eip-4353.md b/EIPS/eip-4353.md index b0b4a3e0db12c0..cec012c8961d0f 100644 --- a/EIPS/eip-4353.md +++ b/EIPS/eip-4353.md @@ -1,230 +1 @@ ---- -eip: 4353 -title: Interface for Staked Tokens in NFTs -description: This interface enables access to publicly viewable staking data of an NFT. -author: Rex Creed (@aug2uag), Dane Scarborough -discussions-to: https://ethereum-magicians.org/t/eip-4353-viewing-staked-tokens-in-nft/7234 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-10-08 -requires: 165 ---- - -## Abstract -[EIP-721](./eip-721.md) tokens can be deposited or staked in NFTs for a variety of reasons including escrow, rewards, benefits, and others. There is currently no means of retrieving the number of tokens staked and/or bound to an NFT. This proposal outlines a standard that may be implemented by all wallets and marketplaces easily to correctly retrieve the staked token amount of an NFT. - -## Motivation -Without staked token data, the actual amount of staked tokens cannot be conveyed from token owners to other users, and cannot be displayed in wallets, marketplaces, or block explorers. The ability to identify and verify an exogenous value derived from the staking process may be critical to the aims of an NFT holder. - -## Specification -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @dev Interface of the ERC4353 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-4353. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others. - * - * Note: The ERC-165 identifier for this interface is 0x3a3d855f. - * - */ -interface IERC721Staked { - - /** - * @dev Returns uint256 amount of on-chain tokens staked to the NFT. - * - * @dev Wallets and marketplaces would need to call this for displaying - * the amount of tokens staked and/or bound to the NFT. - */ - function stakedAmount(uint256 tokenId) external view returns (uint256); - -} -``` - -### Suggested flow: - -#### Constructor/deployment -* Creator - the owner of an NFT with its own rules for depositing tokens at and/or after the minting of a token. -* Token Amount - the current amount of on-chain [EIP-20](./eip-20.md) or derived tokens bound to an NFT from one or more deposits. -* Withdraw Mechanism - rules based approach for withdrawing staked tokens and making sure to update the balance of the staked tokens. - -### Staking at mint and locking tokens in NFT -The suggested and intended implementation of this standard is to stake tokens at the time of minting an NFT, and not implementing any outbound transfer of tokens outside of `burn`. Therefore, only to stake at minting and withdraw only at burning. - -#### NFT displayed in wallet or marketplace -A wallet or marketplace checks if an NFT has publicly staked tokens available for display - if so, call `stakedAmount(tokenId)` to get the current amount of tokens staked and/or bound to the NFT. - -The logical code looks something like this and inspired by William Entriken: - -```solidity -// contracts/Token.sol -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -/** - * @title Token - * @dev Very simple ERC721 example with stake interface example. - * Note this implementation enforces recommended procedure: - * 1) stake at mint - * 2) withdraw at burn - */ -contract ERC721Staked is ERC721URIStorage, Ownable { - /// @dev track original minter of tokenId - mapping (uint256 => address payable) private payees; - /// @dev map tokens to stored staked token value - mapping (uint256 => uint256) private tokenValue; - - /// @dev metadata - constructor() ERC721 ( - "Staked NFT", - "SNFT" - ){} - - /// @dev mints a new NFT - /// @param _to address that will own the minted NFT - /// @param _tokenId id the NFT - /// @param _uri metadata - function mint( - address payable _to, - uint256 _tokenId, - string calldata _uri - ) - external - payable - onlyOwner - { - _mint(_to, _tokenId); - _setTokenURI(_tokenId, _uri); - payees[_tokenId] = _to; - tokenValue[_tokenId] = msg.value; - } - - /// @dev staked interface - /// @param _tokenId id of the NFT - /// @return _value staked value - function stakedAmount( - uint256 _tokenId - ) external view returns (uint256 _value) { - _value = tokenValue[_tokenId]; - return _value; - } - - /// @dev removes NFT & transfers crypto to minter - /// @param _tokenId the NFT we want to remove - function burn( - uint256 _tokenId - ) - external - onlyOwner - { - super._burn(_tokenId); - payees[_tokenId].transfer(tokenValue[_tokenId]); - tokenValue[_tokenId] = 0; - } - -} -``` - -## Rationale -This standard is completely agnostic to how tokens are deposited or handled by the NFT. It is, therefore, the choice and responsibility of the author to encode and communicate the encoding of their tokenomics to purchasees of their token and/or to make their contracts viewable by purchasees. - -Although the intention of this standard is for tokens staked at mint and withdrawable only upon burn, the interface may be modified for dynamic withdrawing and depositing of tokens especially under DeFi application settings. In its current form, the contract logic may be the determining factor whether a deviation from the standard exists. - -## Backward Compatibility -TBD - -## Test Cases -```js -const { expect } = require("chai"); -const { ethers, waffle } = require("hardhat"); -const provider = waffle.provider; - -describe("StakedNFT", function () { - let _id = 1234567890; - let value = '1.5'; - let Token; - let Interface; - let owner; - let addr1; - let addr2; - - beforeEach(async function () { - Token = await ethers.getContractFactory("ERC721Staked"); - [owner, addr1, ...addr2] = await ethers.getSigners(); - Interface = await Token.deploy(); - }); - - describe("Staked NFT", function () { - it("Should set the right owner", async function () { - let mint = await Interface.mint( - addr1.address, _id, 'http://foobar') - expect(await Interface.ownerOf(_id)).to.equal(addr1.address); - }); - - it("Should not have staked balance without value", async function () { - let mint = await Interface.mint( - addr1.address, _id, 'http://foobar') - expect(await Interface.stakedAmount(_id)).to.equal( - ethers.utils.parseEther('0')); - }); - - it("Should set and return the staked amount", async function () { - let mint = await Interface.mint( - addr1.address, _id, 'http://foobar', - {value: ethers.utils.parseEther(value)}) - expect(await Interface.stakedAmount(_id)).to.equal( - ethers.utils.parseEther(value)); - }); - - it("Should decrease owner eth balance on mint (deposit)", async function () { - let balance1 = await provider.getBalance(owner.address); - let mint = await Interface.mint( - addr1.address, _id, 'http://foobar', - {value: ethers.utils.parseEther(value)}) - let balance2 = await provider.getBalance(owner.address); - let diff = parseFloat(ethers.utils.formatEther( - balance1.sub(balance2))).toFixed(1); - expect(diff === value); - }); - - it("Should add to payee's eth balance on burn (withdraw)", async function () { - let balance1 = await provider.getBalance(addr1.address); - let mint = await Interface.mint( - addr1.address, _id, 'http://foobar', - {value: ethers.utils.parseEther(value)}) - await Interface.burn(_id); - let balance2 = await provider.getBalance(addr1.address); - let diff = parseFloat(ethers.utils.formatEther( - balance2.sub(balance1))).toFixed(1); - expect(diff === value); - }); - - it("Should update balance after transfer", async function () { - let mint = await Interface.mint( - addr1.address, _id, 'http://foobar', - {value: ethers.utils.parseEther(value)}) - await Interface.burn(_id); - expect(await Interface.stakedAmount(_id)).to.equal( - ethers.utils.parseEther('0')); - }); - }); -}); -``` - -## Security Considerations -The purpose of this standard is to simply and publicly identify whether an NFT claims to have staked tokens. - -Staked claims will be unreliable without a locking mechanism enforced, for example, if staked tokens can only be transferred at burn. Otherwise, tokens may be deposited and/or withdrawn at any time via arbitrary methods. Also, contracts that may allow arbitrary transfers without updating the correct balance will result in potential issues. A strict rules-based approach should be taken with these edge cases in mind. - -A dedicated service may exist to verify the claims of a token by analyzing transactions on the explorer. In this manner, verification may be automated to ensure a token's claims are valid. The logical extension of this method may be to extend the interface and support flagging erroneous claims, all the while maintaining a simple goal of validating and verifying a staked amount exists to benefit the operator experience. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4353.md diff --git a/EIPS/eip-4361.md b/EIPS/eip-4361.md index 8ee187384a9657..026709c174fe8c 100644 --- a/EIPS/eip-4361.md +++ b/EIPS/eip-4361.md @@ -1,402 +1 @@ ---- -eip: 4361 -title: Sign-In with Ethereum -description: Off-chain authentication for Ethereum accounts to establish sessions. -author: Wayne Chang (@wyc), Gregory Rocco (@obstropolos), Brantly Millegan (@brantlymillegan), Nick Johnson (@Arachnid), Oliver Terbu (@awoie) -discussions-to: https://ethereum-magicians.org/t/eip-4361-sign-in-with-ethereum/7263 -status: Review -type: Standards Track -category: ERC -created: 2021-10-11 -requires: 55, 137, 155, 191, 1271, 1328 ---- - -## Abstract - -Sign-In with Ethereum describes how Ethereum accounts authenticate with off-chain services by signing a standard message format parameterized by scope, session details, and security mechanisms (e.g., a nonce). The goals of this specification are to provide a self-custodied alternative to centralized identity providers, improve interoperability across off-chain services for Ethereum-based authentication, and provide wallet vendors a consistent machine-readable message format to achieve improved user experiences and consent management. - -## Motivation - -When signing in to popular non-blockchain services today, users will typically use identity providers (IdPs) that are centralized entities with ultimate control over users' identifiers, for example, large internet companies and email providers. Incentives are often misaligned between these parties. Sign-In with Ethereum offers a new self-custodial option for users who wish to assume more control and responsibility over their own digital identity. - -Already, many services support workflows to authenticate Ethereum accounts using message signing, such as to establish a cookie-based web session which can manage privileged metadata about the authenticating address. This is an opportunity to standardize the sign-in workflow and improve interoperability across existing services, while also providing wallet vendors a reliable method to identify signing requests as Sign-In with Ethereum requests for improved UX. - -## 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 - -Sign-In with Ethereum (SIWE) works as follows: - -1. The relying party generates a SIWE Message and prefixes the SIWE Message with `\x19Ethereum Signed Message:\n` as defined in [ERC-191](./eip-191.md). -2. The wallet presents the user with a structured plaintext message or equivalent interface for signing the SIWE Message with the [ERC-191](./eip-191.md) signed data format. -3. The signature is then presented to the relying party, which checks the signature's validity and SIWE Message content. -4. The relying party might further fetch data associated with the Ethereum address, such as from the Ethereum blockchain (e.g., ENS, account balances, [ERC-20](./eip-20.md)/[ERC-721](./eip-721.md)/[ERC-1155](./eip-1155.md) asset ownership), or other data sources that might or might not be permissioned. - -### Message Format - -#### ABNF Message Format - -A SIWE Message MUST conform with the following Augmented Backus–Naur Form (ABNF, RFC 5234) expression (note that `%s` denotes case sensitivity for a string term, as per RFC 7405). - -```abnf -sign-in-with-ethereum = - [ scheme "://" ] domain %s" wants you to sign in with your Ethereum account:" LF - address LF - LF - [ statement LF ] - LF - %s"URI: " uri LF - %s"Version: " version LF - %s"Chain ID: " chain-id LF - %s"Nonce: " nonce LF - %s"Issued At: " issued-at - [ LF %s"Expiration Time: " expiration-time ] - [ LF %s"Not Before: " not-before ] - [ LF %s"Request ID: " request-id ] - [ LF %s"Resources:" - resources ] - -scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - ; See RFC 3986 for the fully contextualized - ; definition of "scheme". - -domain = authority - ; From RFC 3986: - ; authority = [ userinfo "@" ] host [ ":" port ] - ; See RFC 3986 for the fully contextualized - ; definition of "authority". - -address = "0x" 40*40HEXDIG - ; Must also conform to captilization - ; checksum encoding specified in EIP-55 - ; where applicable (EOAs). - -statement = *( reserved / unreserved / " " ) - ; See RFC 3986 for the definition - ; of "reserved" and "unreserved". - ; The purpose is to exclude LF (line break). - -uri = URI - ; See RFC 3986 for the definition of "URI". - -version = "1" - -chain-id = 1*DIGIT - ; See EIP-155 for valid CHAIN_IDs. - -nonce = 8*( ALPHA / DIGIT ) - ; See RFC 5234 for the definition - ; of "ALPHA" and "DIGIT". - -issued-at = date-time -expiration-time = date-time -not-before = date-time - ; See RFC 3339 (ISO 8601) for the - ; definition of "date-time". - -request-id = *pchar - ; See RFC 3986 for the definition of "pchar". - -resources = *( LF resource ) - -resource = "- " URI -``` - -#### Message Fields - -This specification defines the following SIWE Message fields that can be parsed from a SIWE Message by following the rules in [ABNF Message Format](#abnf-message-format): - -- `scheme` OPTIONAL. The URI scheme of the origin of the request. Its value MUST be a RFC 3986 URI scheme. -- `domain` REQUIRED. The domain that is requesting the signing. Its value MUST be a RFC 3986 authority. The authority includes an OPTIONAL port. If the port is not specified, the default port for the provided `scheme` is assumed (e.g., 443 for HTTPS). If `scheme` is not specified, HTTPS is assumed by default. -- `address` REQUIRED. The Ethereum address performing the signing. Its value SHOULD be conformant to mixed-case checksum address encoding specified in [ERC-55](./eip-55.md) where applicable. -- `statement` OPTIONAL. A human-readable ASCII assertion that the user will sign which MUST NOT include `'\n'` (the byte `0x0a`). -- `uri` REQUIRED. An RFC 3986 URI referring to the resource that is the subject of the signing (as in the _subject of a claim_). -- `version` REQUIRED. The current version of the SIWE Message, which MUST be `1` for this specification. -- `chain-id` REQUIRED. The [EIP-155](./eip-155.md) Chain ID to which the session is bound, and the network where Contract Accounts MUST be resolved. -- `nonce` REQUIRED. A random string typically chosen by the relying party and used to prevent replay attacks, at least 8 alphanumeric characters. -- `issued-at` REQUIRED. The time when the message was generated, typically the current time. Its value MUST be an ISO 8601 datetime string. -- `expiration-time` OPTIONAL. The time when the signed authentication message is no longer valid. Its value MUST be an ISO 8601 datetime string. -- `not-before` OPTIONAL. The time when the signed authentication message will become valid. Its value MUST be an ISO 8601 datetime string. -- `request-id` OPTIONAL. An system-specific identifier that MAY be used to uniquely refer to the sign-in request. -- `resources` OPTIONAL. A list of information or references to information the user wishes to have resolved as part of authentication by the relying party. Every resource MUST be a RFC 3986 URI separated by `"\n- "` where `\n` is the byte `0x0a`. - -#### Informal Message Template - -A Bash-like informal template of the full SIWE Message is presented below for readability and ease of understanding, and it does not reflect the allowed optionality of the fields. Field descriptions are provided in the following section. A full ABNF description is provided in [ABNF Message Format](#abnf-message-format). - -``` -${scheme}:// ${domain} wants you to sign in with your Ethereum account: -${address} - -${statement} - -URI: ${uri} -Version: ${version} -Chain ID: ${chain-id} -Nonce: ${nonce} -Issued At: ${issued-at} -Expiration Time: ${expiration-time} -Not Before: ${not-before} -Request ID: ${request-id} -Resources: -- ${resources[0]} -- ${resources[1]} -... -- ${resources[n]} -``` - -#### Examples - -The following is an example SIWE Message with an implicit scheme: - -``` -example.com wants you to sign in with your Ethereum account: -0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 - -I accept the ExampleOrg Terms of Service: https://example.com/tos - -URI: https://example.com/login -Version: 1 -Chain ID: 1 -Nonce: 32891756 -Issued At: 2021-09-30T16:25:24Z -Resources: -- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ -- https://example.com/my-web2-claim.json -``` - -The following is an example SIWE Message with an implicit scheme and explicit port: - -``` -example.com:3388 wants you to sign in with your Ethereum account: -0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 - -I accept the ExampleOrg Terms of Service: https://example.com/tos - -URI: https://example.com/login -Version: 1 -Chain ID: 1 -Nonce: 32891756 -Issued At: 2021-09-30T16:25:24Z -Resources: -- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ -- https://example.com/my-web2-claim.json -``` - -The following is an example SIWE Message with an explicit scheme: - -``` -https://example.com wants you to sign in with your Ethereum account: -0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 - -I accept the ExampleOrg Terms of Service: https://example.com/tos - -URI: https://example.com/login -Version: 1 -Chain ID: 1 -Nonce: 32891756 -Issued At: 2021-09-30T16:25:24Z -Resources: -- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ -- https://example.com/my-web2-claim.json -``` - -### Signing and Verifying Messages with Ethereum Accounts - -- For Externally Owned Accounts (EOAs), the verification method specified in [ERC-191](./eip-191.md) MUST be used. - -- For Contract Accounts, - - The verification method specified in [ERC-1271](./eip-1271.md) SHOULD be used, and if it is not, the implementer MUST clearly define the verification method to attain security and interoperability for both wallets and relying parties. - - When performing [ERC-1271](./eip-1271.md) signature verification, the contract performing the verification MUST be resolved from the specified `chain-id`. - - Implementers SHOULD take into consideration that [ERC-1271](./eip-1271.md) implementations are not required to be pure functions, and can return different results for the same inputs depending on blockchain state. This can affect the security model and session validation rules. For example, a service with [ERC-1271](./eip-1271.md) signing enabled could rely on webhooks to receive notifications when state affecting the results is changed. When it receives a notification, it invalidates any matching sessions. - -### Resolving Ethereum Name Service (ENS) Data - -- The relying party or wallet MAY additionally perform resolution of ENS data, as this can improve the user experience by displaying human-friendly information that is related to the `address`. Resolvable ENS data include: - - The [primary ENS name](./eip-181.md). - - The ENS avatar. - - Any other resolvable resources specified in the ENS documentation. -- If resolution of ENS data is performed, implementers SHOULD take precautions to preserve user privacy and consent, as their `address` could be forwarded to third party services as part of the resolution process. - -### Relying Party Implementer Steps - -#### Specifying the Request Origin - -- The `domain` and, if present, the `scheme`, in the SIWE Message MUST correspond to the origin from where the signing request was made. For instance, if the signing request is made within a cross-origin iframe embedded in a parent browser window, the `domain` (and, if present, the `scheme`) have to match the origin of the iframe, rather than the origin of the parent. This is crucial to prevent the iframe from falsely asserting the origin of one of its ancestor windows for security reasons. This behavior is enforced by conforming wallets. - -#### Verifying a signed Message - -- The SIWE Message MUST be checked for conformance to the ABNF Message Format in the previous sections, checked against expected values after parsing (e.g., `expiration-time`, `nonce`, `request-uri` etc.), and its signature MUST be checked as defined in [Signing and Verifying Messages with Ethereum Accounts](#signing-and-verifying-messages-with-ethereum-accounts). - -#### Creating Sessions - -- Sessions MUST be bound to the `address` and not to further resolved resources that can change. - -#### Interpreting and resolving Resources - -- Implementers SHOULD ensure that that URIs in the listed `resources` are human-friendly when expressed in plaintext form. -- The intepretation of the listed `resources` in the SIWE Message is out of scope of this specification. - -### Wallet Implementer Steps - -#### Verifying the Message Format - -- The full SIWE message MUST be checked for conformance to the ABNF defined in [ABNF Message Format](#abnf-message-format). -- Wallet implementers SHOULD warn users if the substring `"wants you to sign in - with your Ethereum account"` appears anywhere in an [ERC-191](./eip-191.md) message signing - request unless the message fully conforms to the format defined [ABNF Message Format](#abnf-message-format). - -#### Verifying the Request Origin - -- Wallet implementers MUST prevent phishing attacks by verifying the origin of the request against the `scheme` and `domain` fields in the SIWE Message. For example, when processing the SIWE message beginning with `"example.com wants you to sign in..."`, the wallet checks that the request actually originated from `https://example.com`. -- The origin SHOULD be read from a trusted data source such as the browser window or over WalletConnect ([ERC-1328](./eip-1328.md)) sessions for comparison against the signing message contents. -- Wallet implementers MAY warn instead of rejecting the verification if the origin is pointing to localhost. - -The following is a RECOMMENDED algorithm for Wallets to conform with the requirements on request origin verification defined by this specification. - -The algorithm takes the following input variables: - -- fields from the SIWE message. -- `origin` of the signing request - in the case of a browser wallet implementation - the origin of the page which requested the signin via the provider. -- `allowedSchemes` - a list of schemes allowed by the Wallet. -- `defaultScheme` - a scheme to assume when none was provided. Wallet implementers in the browser SHOULD use `https`. -- developer mode indication - a setting deciding if certain risks should be a warning instead of rejection. Can be manually configured or derived from `origin` being localhost. - -The algorithm is described as follows: - -- If `scheme` was not provided, then assign `defaultScheme` as `scheme`. -- If `scheme` is not contained in `allowedSchemes`, then the `scheme` is not expected and the Wallet MUST reject the request. Wallet implementers in the browser SHOULD limit the list of `allowedSchemes` to just `'https'` unless a developer mode is activated. -- If `scheme` does not match the scheme of `origin`, the Wallet SHOULD reject the request. Wallet implementers MAY show a warning instead of rejecting the request if a developer mode is activated. In that case the Wallet continues processing the request. -- If the `host` part of the `domain` and `origin` do not match, the Wallet MUST reject the request unless the Wallet is in developer mode. In developer mode the Wallet MAY show a warning instead and continues procesing the request. -- If `domain` and `origin` have mismatching subdomains, the Wallet SHOULD reject the request unless the Wallet is in developer mode. In developer mode the Wallet MAY show a warning instead and continues procesing the request. -- Let `port` be the port component of `domain`, and if no port is contained in `domain`, assign `port` the default port specified for the `scheme`. -- If `port` is not empty, then the Wallet SHOULD show a warning if the `port` does not match the port of `origin`. -- If `port` is empty, then the Wallet MAY show a warning if `origin` contains a specific port. (Note 'https' has a default port of 443 so this only applies if `allowedSchemes` contain unusual schemes) -- Return request origin verification completed. - -#### Creating Sign-In with Ethereum Interfaces - -- Wallet implementers MUST display to the user the following fields from the SIWE Message request by default and prior to signing, if they are present: `scheme`, `domain`, `address`, `statement`, and `resources`. Other present fields MUST also be made available to the user prior to signing either by default or through an extended interface. -- Wallet implementers displaying a plaintext SIWE Message to the user SHOULD require the user to scroll to the bottom of the text area prior to signing. -- Wallet implementers MAY construct a custom SIWE user interface by parsing the ABNF terms into data elements for use in the interface. The display rules above still apply to custom interfaces. - -#### Supporting internationalization (i18n) - -- After successfully parsing the message into ABNF terms, translation MAY happen at the UX level per human language. - -## Rationale - -### Requirements - -Write a specification for how Sign-In with Ethereum should work. The specification should be simple and generally follow existing practices. Avoid feature bloat, particularly the inclusion of lesser-used projects who see getting into the specification as a means of gaining adoption. The core specification should be decentralized, open, non-proprietary, and have long-term viability. It should have no dependence on a centralized server except for the servers already being run by the application that the user is signing in to. The basic specification should include: Ethereum accounts used for authentication, ENS names for usernames (via reverse resolution), and data from the ENS name’s text records for additional profile information (e.g. avatar, social media handles, etc). - -Additional functional requirements: - -1. The user must be presented a human-understandable interface prior to signing, mostly free of machine-targeted artifacts such as JSON blobs, hex codes (aside from the Ethereum address), and baseXX-encoded strings. -2. The application server must be able to implement fully usable support for its end without forcing a change in the wallets. -3. There must be a simple and straightforward upgrade path for both applications and wallets already using Ethereum account-based signing for authentication. -4. There must be facilities and guidelines for adequate mitigation of Man-in-the-Middle (MITM) attacks, replay attacks, and malicious signing requests. - -### Design Goals - -1. Human-Friendly -2. Simple to Implement -3. Secure -4. Machine Readable -5. Extensible - -### Technical Decisions - -- Why [ERC-191](./eip-191.md) (Signed Data Standard) over [EIP-712](./eip-712.md) (Ethereum typed structured data hashing and signing) - - [ERC-191](./eip-191.md) is already broadly supported across wallets UX, while [EIP-712](./eip-712.md) support for friendly user display is pending. **(1, 2, 3, 4)** - - [ERC-191](./eip-191.md) is simple to implement using a pre-set prefix prior to signing, while [EIP-712](./eip-712.md) is more complex to implement requiring the further implementations of a bespoke Solidity-inspired type system, RLP-based encoding format, and custom keccak-based hashing scheme. **(2)** - - [ERC-191](./eip-191.md) produces more human-readable messages, while [EIP-712](./eip-712.md) creates signing outputs for machine consumption, with most wallets not displaying the payload to be signed in a manner friendly to humans. **(1)**![](../assets/eip-4361/signing.png) - - - [EIP-712](./eip-712.md) has the advantage of on-chain representation and on-chain verifiability, such as for their use in metatransactions, but this feature is not relevant for the specification's scope. **(2)** -- Why not use JWTs? Wallets don't support JWTs. The keccak hash function is not assigned by IANA for use as a JOSE algorithm. **(2, 3)** -- Why not use YAML or YAML with exceptions? YAML is loose compared to ABNF, which can readily express character set limiting, required ordering, and strict whitespacing. **(2, 3)** - -### Out of Scope - -The following concerns are out of scope for this version of the specification to define: - -- Additional authentication not based on Ethereum addresses. -- Authorization to server resources. -- Interpretation of the URIs in the `resources` field as claims or other resources. -- The specific mechanisms to ensure domain-binding. -- The specific mechanisms to generate nonces and evaluation of their appropriateness. -- Protocols for use without TLS connections. - -### Considerations for Forwards Compatibility - -The following items are considered for future support in either through an iteration of this specification or new work items using this specification as a dependency. - -- Possible support for Decentralized Identifiers and Verifiable Credentials. -- Possible cross-chain support. -- Possible SIOPv2 support. -- Possible future support for [EIP-712](./eip-712.md). -- Version interpretation rules, e.g., sign with minor revision greater than understood, but not greater major version. - -## Backwards Compatibility - -- Most wallet implementations already support [ERC-191](./eip-191.md), so this is used as a base pattern with additional features. -- Requirements were gathered from existing implementations of similar sign-in workflows, including statements to allow the user to accept a Terms of Service, nonces for replay protection, and inclusion of the Ethereum address itself in the message. - -## Reference Implementation - -A reference implementation is available [here](../assets/eip-4361/example.js). - -## Security Considerations - -### Identifier Reuse - -- Towards perfect privacy, it would be ideal to use a new uncorrelated identifier (e.g., Ethereum address) per digital interaction, selectively disclosing the information required and no more. -- This concern is less relevant to certain user demographics who are likely to be early adopters of this specification, such as those who manage an Ethereum address and/or ENS names intentionally associated with their public presence. These users often prefer identifier reuse to maintain a single correlated identity across many services. -- This consideration will become increasingly important with mainstream adoption. There are several ways to move towards this model, such as using HD wallets, signed delegations, and zero-knowledge proofs. However, these approaches are out of scope for this specification and better suited for follow-on specifications. - -### Key Management - -- Sign-In with Ethereum gives users control through their keys. This is additional responsibility that mainstream users may not be accustomed to accepting, and key management is a hard problem especially for individuals. For example, there is no "forgot password" button as centralized identity providers commonly implement. -- Early adopters of this specification are likely to be already adept at key management, so this consideration becomes more relevant with mainstream adoption. -- Certain wallets can use smart contracts and multisigs to provide an enhanced user experiences with respect to key usage and key recovery, and these can be supported via [ERC-1271](./eip-1271.md) signing. - -### Wallet and Relying Party combined Security - -- Both the wallet and relying party have to implement this specification for improved security to the end user. Specifically, the wallet has to confirm that the SIWE Message is for the correct request origin or provide the user means to do so manually (such as instructions to visually confirming the correct domain in a TLS-protected website prior to connecting via QR code or deeplink), otherwise the user is subject to phishing attacks. - -### Minimizing Wallet and Server Interaction - -- In some implementions of wallet sign-in workflows, the server first sends parameters of the SIWE Message to the wallet. Others generate the SIWE message for signing entirely in the client side (e.g., dapps). The latter approach without initial server interaction SHOULD be preferred when there is a user privacy advantage by minimizing wallet-server interaction. Often, the backend server first produces a `nonce` to prevent replay attacks, which it verifies after signing. Privacy-preserving alternatives are suggested in the next section on preventing replay attacks. -- Before the wallet presents the SIWE message signing request to the user, it MAY consult the server for the proper contents of the message to be signed, such as an acceptable `nonce` or requested set of `resources`. When communicating to the server, the wallet SHOULD take precautions to protect user privacy by mitigating user information revealed as much as possible. -- Prior to signing, the wallet MAY consult the user for preferences, such as the selection of one `address` out of many, or a preferred ENS name out of many. - -### Preventing Replay Attacks - -- A `nonce` SHOULD be selected per session initiation with enough entropy to prevent replay attacks, a man-in-the-middle attack in which an attacker is able to capture the user's signature and resend it to establish a new session for themselves. -- Implementers MAY consider using privacy-preserving yet widely-available `nonce` values, such as one derived from a recent Ethereum block hash or a recent Unix timestamp. - -### Preventing Phishing Attacks - -- To prevent phishing attacks Wallets have to implement request origin verification as described in [Verifying the Request Origin](#verifying-the-request-origin). - -### Channel Security - -- For web-based applications, all communications SHOULD use HTTPS to prevent man-in-the-middle attacks on the message signing. -- When using protocols other than HTTPS, all communications SHOULD be protected with proper techniques to maintain confidentiality, data integrity, and sender/receiver authenticity. - -### Session Invalidation - -There are several cases where an implementer SHOULD check for state changes as they relate to sessions. - -- If an [ERC-1271](./eip-1271.md) implementation or dependent data changes the signature computation, the server SHOULD invalidate sessions appropriately. -- If any resources specified in `resources` change, the server SHOULD invalidate sessions appropriately. However, the interpretation of `resources` is out of scope of this specification. - -### Maximum Lengths for ABNF Terms - -- While this specification does not contain normative requirements around maximum string lengths, implementers SHOULD choose maximum lengths for terms that strike a balance across the prevention of denial of service attacks, support for arbitrary use cases, and user readability. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4361.md diff --git a/EIPS/eip-4393.md b/EIPS/eip-4393.md index 9955beb26a6bef..0600900cb5755d 100644 --- a/EIPS/eip-4393.md +++ b/EIPS/eip-4393.md @@ -1,363 +1 @@ ---- -eip: 4393 -title: Micropayments for NFTs and Multi Tokens -description: An interface for tip tokens that allows tipping to holders of NFTs and multi tokens -author: Jules Lai (@julesl23) -discussions-to: https://ethereum-magicians.org/t/eip-proposal-micropayments-standard-for-nfts-and-multi-tokens/7366 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-10-24 -requires: 165, 721, 1155 ---- - -## Abstract - -This standard outlines a smart contract interface for tipping to non-fungible and multi tokens. Holders of the tokens are able to withdraw the tips as [EIP-20](./eip-20.md) rewards. - -For the purpose of this EIP, a micropayment is termed as a financial transaction that involves usually a small sum of money called "tips" that are sent to specific [EIP-721](./eip-721.md) NFTs and [EIP-1155](./eip-1155.md) multi tokens, as rewards to their holders. A holder (also referred to as controller) is used as a more generic term for owner, as NFTs may represent non-digital assets such as services. - -## Motivation - -A cheap way to send tips to any type of NFT or multi token. This can be achieved by gas optimising the tip token contract and sending the tips in batches using the `tipBatch` function from the interface. - -To make it easy to implement into dapps a tipping service to reward the NFT and multi token holders. Allows for fairer distribution of revenue back to NFT holders from the user community. - -To make the interface as minimal as possible in order to allow adoption into many different use cases. - -Some use cases include: - -- In game purchases and other virtual goods - -- Tipping messages, posts, music and video content - -- Donations/crowdfunding - -- Distribution of royalties - -- Pay per click advertising - -- Incentivising use of services - -- Reward cards and coupons - -These can all leverage the security, immediacy and transparency of blockchain. - -## Specification - -This standard proposal outlines a generalised way to allow tipping via implementation of an `ITipToken` interface. The interface is intentionally kept to a minimum in order to allow for maximum use cases. - -Smart contracts implementing this EIP standard MUST implement all of the functions in this EIP interface. MUST also emit the events specified in the interface so that a complete state of the tip token contract can be derived from the events emitted alone. - -Smart contracts implementing this EIP standard MUST implement the [EIP-165](./eip-165.md) supportsInterface function and MUST return the constant value true if 0xE47A7022 is passed through the interfaceID argument. Note that revert in this document MAY mean a require, throw (not recommended as depreciated) or revert solidity statement with or without error messages. - -Note that, nft (or NFT in caps) in the code and as mentioned in this document, MAY also refer to an EIP-1155 fungible token. - -```solidity -interface ITipToken { - /** - @dev This emits when the tip token implementation approves the address - of an NFT for tipping. - The holders of the 'nft' are approved to receive rewards. - When an NFT Transfer event emits, this also indicates that the approved - addresses for that NFT (if any) is reset to none. - Note: the ERC-165 identifier for this interface is 0x985A3267. - */ - event ApprovalForNFT( - address[] holders, - address indexed nft, - uint256 indexed id, - bool approved - ); - - /** - @dev This emits when a user has deposited an ERC-20 compatible token to - the tip token's contract address or to an external address. - This also indicates that the deposit has been exchanged for an - amount of tip tokens - */ - event Deposit( - address indexed user, - address indexed rewardToken, - uint256 amount, - uint256 tipTokenAmount - ); - - /** - @dev This emits when a holder withdraws an amount of ERC-20 compatible - reward. This reward comes from the tip token's contract address or from - an external address, depending on the tip token implementation - */ - event WithdrawReward( - address indexed holder, - address indexed rewardToken, - uint256 amount - ); - - /** - @dev This emits when the tip token constructor or initialize method is - executed. - Importantly the ERC-20 compatible token 'rewardToken_' to use as reward - to NFT holders is set at this time and remains the same throughout the - lifetime of the tip token contract. - The 'rewardToken_' and 'tipToken_' MAY be the same. - */ - event InitializeTipToken( - address indexed tipToken_, - address indexed rewardToken_, - address owner_ - ); - - /** - @dev This emits every time a user tips an NFT holder. - Also includes the reward token address and the reward token amount that - will be held pending until the holder withdraws the reward tokens. - */ - event Tip( - address indexed user, - address[] holder, - address indexed nft, - uint256 id, - uint256 amount, - address rewardToken, - uint256[] rewardTokenAmount - ); - - /** - @notice Enable or disable approval for tipping for a single NFT held - by a holder or a multi token shared by holders - @dev MUST revert if calling nft's supportsInterface does not return - true for either IERC721 or IERC1155. - MUST revert if any of the 'holders' is the zero address. - MUST revert if 'nft' has not approved the tip token contract address as operator. - MUST emit the 'ApprovalForNFT' event to reflect approval or not approval. - @param holders The holders of the NFT (NFT controllers) - @param nft The NFT contract address - @param id The NFT token id - @param approved True if the 'holder' is approved, false to revoke approval - */ - function setApprovalForNFT( - address[] calldata holders, - address nft, - uint256 id, - bool approved - ) external; - - /** - @notice Checks if 'holder' and 'nft' with token 'id' have been approved - by setApprovalForNFT - @dev This does not check that the holder of the NFT has changed. That is - left to the implementer to detect events for change of ownership and to - take appropriate action - @param holder The holder of the NFT (NFT controller) - @param nft The NFT contract address - @param id The NFT token id - @return True if 'holder' and 'nft' with token 'id' have previously been - approved by the tip token contract - */ - function isApprovalForNFT( - address holder, - address nft, - uint256 id - ) external returns (bool); - - /** - @notice Sends tip from msg.sender to holder of a single NFT or - to shared holders of a multi token - @dev If 'nft' has not been approved for tipping, MUST revert - MUST revert if 'nft' is zero address. - MUST burn the tip 'amount' to the 'holder' and send the reward to - an account pending for the holder(s). - If 'nft' is a multi token that has multiple holders then each holder - MUST receive tip amount in proportion of their balance of multi tokens - MUST emit the 'Tip' event to reflect the amounts that msg.sender tipped - to holder(s) of 'nft'. - @param nft The NFT contract address - @param id The NFT token id - @param amount Amount of tip tokens to send to the holder of the NFT - */ - function tip( - address nft, - uint256 id, - uint256 amount - ) external; - - /** - @notice Sends a batch of tips to holders of 'nfts' for gas efficiency - @dev If NFT has not been approved for tipping, revert - MUST revert if the input arguments lengths are not all the same - MUST revert if any of the user addresses are zero - MUST revert the whole batch if there are any errors - MUST emit the 'Tip' events so that the state of the amounts sent to - each holder and for which nft and from whom, can be reconstructed. - @param users User accounts to tip from - @param nfts The NFT contract addresses whose holders to tip to - @param ids The NFT token ids that uniquely identifies the 'nfts' - @param amounts Amount of tip tokens to send to the holders of the NFTs - */ - function tipBatch( - address[] calldata users, - address[] calldata nfts, - uint256[] calldata ids, - uint256[] calldata amounts - ) external; - - /** - @notice Deposit an ERC-20 compatible token in exchange for tip tokens - @dev The price of tip tokens can be different for each deposit as - the amount of reward token sent ultimately is a ratio of the - amount of tip tokens to tip over the user's tip tokens balance available - multiplied by the user's deposit balance. - The deposited tokens can be held in the tip tokens contract account or - in an external escrow. This will depend on the tip token implementation. - Each tip token contract MUST handle only one type of ERC-20 compatible - reward for deposits. - This token address SHOULD be passed in to the tip token constructor or - initialize method. SHOULD revert if ERC-20 reward for deposits is - zero address. - MUST emit the 'Deposit' event that shows the user, deposited token details - and amount of tip tokens minted in exchange - @param user The user account - @param amount Amount of ERC-20 token to deposit in exchange for tip tokens. - This deposit is to be used later as the reward token - */ - function deposit(address user, uint256 amount) external payable; - - /** - @notice An NFT holder can withdraw their tips as an ERC-20 compatible - reward at a time of their choosing - @dev MUST revert if not enough balance pending available to withdraw. - MUST send 'amount' to msg.sender account (the holder) - MUST reduce the balance of reward tokens pending by the 'amount' withdrawn. - MUST emit the 'WithdrawReward' event to show the holder who withdrew, the reward - token address and 'amount' - @param amount Amount of ERC-20 token to withdraw as a reward - */ - function withdrawReward(uint256 amount) external payable; - - /** - @notice MUST have identical behaviour to ERC-20 balanceOf and is the amount - of tip tokens held by 'user' - @param user The user account - @return The balance of tip tokens held by user - */ - function balanceOf(address user) external view returns (uint256); - - /** - @notice The balance of deposit available to become rewards when - user sends the tips - @param user The user account - @return The remaining balance of the ERC-20 compatible token deposited - */ - function balanceDepositOf(address user) external view returns (uint256); - - /** - @notice The amount of reward token owed to 'holder' - @dev The pending tokens can come from the tip token contract account - or from an external escrow, depending on tip token implementation - @param holder The holder of NFT(s) (NFT controller) - @return The amount of reward tokens owed to the holder from tipping - */ - function rewardPendingOf(address holder) external view returns (uint256); -} -``` - -### Tipping and rewards to holders - -A user first deposits a compatible EIP-20 to the tip token contract that is then held (less any agreed fee) in escrow, in exchange for tip tokens. These tip tokens can then be sent by the user to NFTs and multi tokens (that have been approved by the tip token contract for tipping) to be redeemed for the original EIP-20 deposits on withdrawal by the holders as rewards. - -### Tip Token transfer and value calculations - -Tip token values are exchanged with EIP-20 deposits and vice-versa. It is left to the tip token implementer to decide on the price of a tip token and hence how much tip to mint in exchange for the EIP-20 deposited. One possibility is to have fixed conversion rates per geographical region so that users from poorer countries are able to send the same number of tips as those from richer nations for the same level of appreciation for content/assets etc. Hence, not skewed by average wealth when it comes to analytics to discover what NFTs are actually popular, allowing creators to have a level playing field. - -Whenever a user sends a tip, an equivalent value of deposited EIP-20 MUST be transferred to a pending account for the NFT or multi token holder, and the tip tokens sent MUST be burnt. This equivalent value is calculated using a simple formula: - -_total user balance of EIP-20 deposit _ tip amount / total user balance of tip tokens\* - -Thus adding \*free\* tips to a user's balance of tips for example, simply dilutes the overall value of each tip for that user, as collectively they still refer to the same amount of EIP-20 deposited. - -Note if the tip token contract inherits from an EIP-20, tips can be transferred from one user to another directly. The deposit amount would be already in the tip token contract (or an external escrow account) so only tip token contract's internal mapping of user account to deposit balances needs to be updated. It is RECOMMENDED that the tip amount be burnt from user A and then minted back to user B in the amount that keeps user B's average EIP-20 deposited value per tip the same, so that the value of the tip does not fluctuate in the process of tipping. - -If not inheriting from EIP-20, then minting the tip tokens MUST emit `event Transfer(address indexed from, address indexed to, uint256 value)` where sender is the zero address for a mint and to is the zero address for a burn. The Transfer event MUST be the same signature as the Transfer function in the `IERC20` interface. - -### Royalty distribution - -EIP-1155 allows for shared holders of a token id. Imagine a scenario where an article represented by an NFT was written by multiple contributors. Here, each contributor is a holder and the fractional sharing percentage between them can be represented by the balance that each holds in the EIP-1155 token id. So for two holders A and B of EIP-1155 token 1, if holder A's balance is 25 and holder B's is 75 then any tip sent to token 1 would distribute 25% of the reward pending to holder A and the remaining 75% pending to holder B. - -Here is an example implementation of ITipToken contract data structures: - -```solidity - /// Mapping from NFT/multi token to token id to holder(s) - mapping(address => mapping(uint256 => address[])) private _tokenIdToHolders; - - /// Mapping from user to user's deposit balance - mapping(address => uint256) private _depositBalances; - - /// Mapping from holder to holder's reward pending amount - mapping(address => uint256) private _rewardsPending; -``` - -This copes with EIP-721 contracts that must have unique token ids and single holders (to be compliant with the standard), and EIP-1155 contracts that can have multiple token ids and multiple holders per instance. The `tip` function implementation would then access `_tokenIdToHolders` via indices NFT/multi token address and token id to distribute to holder's or holders' `_rewardsPending`. - -For scenarios where royalties are to be distributed to holders directly, then implementation of the `tip` method of `ITipToken` contract MAY send the royalty amount straight from the user's account to the holder of a single NFT or to the shared holders of a multi token, less an optional agreed fee. In this case, the tip token type is the reward token. - -### Caveats - -To keep the `ITipToken` interface simple and general purpose, each tip token contract MUST use one EIP-20 compatible deposit type at a time. If tipping is required to support many EIP-20 deposits then each tip token contract MUST be deployed separately per EIP-20 compatible type required. Thus, if tipping is required from both ETH and BTC wrapper EIP-20 deposits then the tip token contract is deployed twice. The tip token contract's constructor is REQUIRED to pass in the address of the EIP-20 token supported for the deposits for the particular tip token contract. Or in the case for upgradeable tip token contracts, an initialize method is REQUIRED to pass in the EIP-20 token address. - -This EIP does not provide details for where the EIP-20 reward deposits are held. It MUST be available at the time a holder withdraws the rewards that they are owed. A RECOMMENDED implementation would be to keep the deposits locked in the tip token contract address. By keeping a mapping structure that records the balances pending to holders then the -deposits can remain where they are when a user tips, and only transferred out to a holder's address when a holder withdraws it as their reward. - -This standard does not specify the type of EIP-20 compatible deposits allowed. Indeed, could be tip tokens themselves. But it is RECOMMENDED that balances of the deposits be checked after transfer to find out the exact amount deposited to keep internal accounting consistent. In case, for example, the EIP-20 contract takes fees and hence reduces the actual amount deposited. - -This standard does not specify any functionality for refunds for deposits nor for tip tokens sent, it is left to the implementor to add this to their smart contract(s). The reasoning for this is to keep the interface light and not to enforce upon implementors the need for refunds but to leave that as a choice. - -### Minimising Gas Costs - -By caching tips off-chain and then batching them up to call the `tipBatch` method of the ITipToken interface then essentially the cost of initialising transactions is paid once rather than once per tip. Plus, further gas savings can be made off-chain if multiple tips sent by the same user to the same NFT token are accumulated together and sent as one entry in the batch. - -Further savings can be made by grouping users together sending to the same NFT, so that checking the validity of the NFT and whether it is an EIP-721 or EIP-1155, is performed once for each group. - -Clever ways to minimise on-chain state updating of the deposit balances for each user and the reward balances of each holder, can help further to minimise the gas costs when sending in a batch if the batch is ordered beforehand. For example, can avoid the checks if the next NFT in the batch is the same. This left to the tip token contract implementer. Whatever optimisation is applied, it MUST still allow information of which account tipped which account and for what NFT to be reconstructed from the Tip and the TipBatch events emitted. - -## Rationale - -### Simplicity - -ITipToken interface uses a minimal number of functions, in order to keep its use as general purpose as possible, whilst there being enough to guide implementation that fulfils its purpose for micropayments to NFT holders. - -### Use of NFTs - -Each NFT is a unique non-fungible token digital asset stored on the blockchain that are uniquely identified by its address and token id. It's truth burnt using cryptographic hashing on a secure blockchain means that it serves as an anchor for linking with a unique digital asset, service or other contractual agreement. Such use cases may include (but only really limited by imagination and acceptance): - -- Digital art, collectibles, music, video, licenses and certificates, event tickets, ENS names, gaming items, objects in metaverses, proof of authenticity of physical items, service agreements etc. - -This mechanism allows consumers of the NFT a secure way to easily tip and reward the NFT holder. - -### New Business Models - -To take the music use case for example. Traditionally since the industry transitioned from audio distributed on physical medium such as CDs, to an online digital distribution model via streaming, the music industry has been controlled by oligopolies that served to help in the transition. They operate a fixed subscription model and from that they set the amount of royalty distribution to content creators; such as the singers, musicians etc. Using tip tokens represent an additional way for fans of music to reward the content creators. Each song or track is represented by an NFT and fans are able to tip the song (hence the NFT) that they like, and in turn the content creators of the NFT are able to receive the EIP-20 rewards that the tips were bought for. A fan led music industry with decentralisation and tokenisation is expected to bring new revenue, and bring fans and content creators closer together. - -Across the board in other industries a similar ethos can be applied where third party controllers move to a more facilitating role rather than a monetary controlling role that exists today. - -### Guaranteed audit trail - -As the Ethereum ecosystem continues to grow, many dapps are relying on traditional databases and explorer API services to retrieve and categorize data. This EIP standard guarantees that event logs emitted by the smart contract MUST provide enough data to create an accurate record of all current tip token and EIP-20 reward balances. A database or explorer can provide indexed and categorized searches of every tip token and reward sent to NFT holders from the events emitted by any tip token contract that implements this standard. Thus, the state of the tip token contract can be reconstructed from the events emitted alone. - -## Backwards Compatibility - -A tip token contract can be fully compatible with EIP-20 specification and inherit some functions such as transfer if the tokens are allowed to be sent directly to other users. Note that balanceOf has been adopted and MUST be the number of tips held by a user's address. If inheriting from, for example, OpenZeppelin's implementation of EIP-20 token then their contract is responsible for maintaining the balance of tip token. Therefore, tip token balanceOf function SHOULD simply directly call the parent (super) contract's balanceOf function. - -What hasn't been carried over to tip token standard, is the ability for a spender of other users' tips. For the moment, this standard does not foresee a need for this. - -This EIP does not stress a need for tip token secondary markets or other use cases where identifying the tip token type with names rather than addresses might be useful, so these functions were left out of the ITipToken interface and is the remit for implementers. - -## Security Considerations - -Though it is RECOMMENDED that users' deposits are kept locked in the tip token contract or external escrow account, and SHOULD NOT be used for anything but the rewards for holders, this cannot be enforced. This standard stipulates that the rewards MUST be available for when holders withdraw their rewards from the pool of deposits. - -Before any users can tip an NFT, the holder of the NFT has to give their approval for tipping from the tip token contract. This standard stipulates that holders of the NFTs receive the rewards. It SHOULD be clear in the tip token contract code that it does so, without obfuscation to where the rewards go. Any fee charges SHOULD be made obvious to users before acceptance of their deposit. There is a risk that rogue implementers may attempt to \*hijack\* potential tip income streams for their own purposes. But additionally the number and frequency of transactions of the tipping process should make this type of fraud quicker to be found out. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4393.md diff --git a/EIPS/eip-4400.md b/EIPS/eip-4400.md index 500ff8ff26002d..e87eac3e1b81c3 100644 --- a/EIPS/eip-4400.md +++ b/EIPS/eip-4400.md @@ -1,113 +1 @@ ---- -eip: 4400 -title: EIP-721 Consumable Extension -description: Interface extension for EIP-721 consumer role -author: Daniel Ivanov (@Daniel-K-Ivanov), George Spasov (@Perseverance) -discussions-to: https://ethereum-magicians.org/t/EIP-4400-EIP721consumer-extension/7371 -status: Final -type: Standards Track -category: ERC -created: 2021-10-30 -requires: 165, 721 ---- - -## Abstract - -This specification defines standard functions outlining a `consumer` role for instance(s) of [EIP-721](./eip-721.md). An implementation allows reading the current `consumer` for a given NFT (`tokenId`) along with a standardized event for when an `consumer` has changed. The proposal depends on and extends the existing [EIP-721](./eip-721.md). - -## Motivation - -Many [EIP-721](./eip-721.md) contracts introduce their own custom role that grants permissions for utilising/consuming a given NFT instance. The need for that role stems from the fact that other than owning the NFT instance, there are other actions that can be performed on an NFT. For example, various metaverses use `operator` / `contributor` roles for Land (EIP-721), so that owners of the land can authorise other addresses to deploy scenes to them (f.e. commissioning a service company to develop a scene). - -It is common for NFTs to have utility other than ownership. That being said, it requires a separate standardized consumer role, allowing compatibility with user interfaces and contracts, managing those contracts. - -Having a `consumer` role will enable protocols to integrate and build on top of dApps that issue EIP-721 tokens. One example is the creation of generic/universal NFT renting marketplaces. - -Example of kinds of contracts and applications that can benefit from this standard are: -- metaverses that have land and other types of digital assets in those metaverses (scene deployment on land, renting land / characters / clothes / passes to events etc.) -- NFT-based yield-farming. Adopting the standard enables the "staker" (owner of the NFT) to have access to the utility benefits even after transferring his NFT to the staking contract - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Every contract compliant to the `EIP721Consumable` extension MUST implement the `IEIP721Consumable` interface. The **consumer extension** is OPTIONAL for EIP-721 contracts. - -```solidity -/// @title EIP-721 Consumer Role extension -/// Note: the EIP-165 identifier for this interface is 0x953c8dfa -interface IEIP721Consumable /* is EIP721 */ { - - /// @notice Emitted when `owner` changes the `consumer` of an NFT - /// The zero address for consumer indicates that there is no consumer address - /// When a Transfer event emits, this also indicates that the consumer address - /// for that NFT (if any) is set to none - event ConsumerChanged(address indexed owner, address indexed consumer, uint256 indexed tokenId); - - /// @notice Get the consumer address of an NFT - /// @dev The zero address indicates that there is no consumer - /// Throws if `_tokenId` is not a valid NFT - /// @param _tokenId The NFT to get the consumer address for - /// @return The consumer address for this NFT, or the zero address if there is none - function consumerOf(uint256 _tokenId) view external returns (address); - - /// @notice Change or reaffirm the consumer address for an NFT - /// @dev The zero address indicates there is no consumer address - /// Throws unless `msg.sender` is the current NFT owner, an authorised - /// operator of the current owner or approved address - /// Throws if `_tokenId` is not valid NFT - /// @param _consumer The new consumer of the NFT - function changeConsumer(address _consumer, uint256 _tokenId) external; -} -``` - -Every contract implementing the `EIP721Consumable` extension is free to define the permissions of a `consumer` (e.g. what are consumers allowed to do within their system) with only one exception - consumers MUST NOT be considered owners, authorised operators or approved addresses as per the EIP-721 specification. Thus, they MUST NOT be able to execute transfers & approvals. - -The `consumerOf(uint256 _tokenId)` function MAY be implemented as `pure` or `view`. - -The `changeConsumer(address _consumer, uint256 _tokenId)` function MAY be implemented as `public` or `external`. - -The `ConsumerChanged` event MUST be emitted when a consumer is changed. - -On every `transfer`, the consumer MUST be changed to a default address. It is RECOMMENDED for implementors to use `address(0)` as that default address. - -The `supportsInterface` method MUST return `true` when called with `0x953c8dfa`. - -## Rationale - -Key factors influencing the standard: - -- Keeping the number of functions in the interfaces to a minimum to prevent contract bloat -- Simplicity -- Gas Efficiency -- Not reusing or overloading other already existing roles (e.g. owners, operators, approved addresses) - -### Name - -The chosen name resonates with the purpose of its existence. Consumers can be considered entities that utilise the token instances, without necessarily having ownership rights to it. - -The other name for the role that was considered was `operator`, however it is already defined and used within the `EIP-721` standard. - -### Restriction on the Permissions - -There are numerous use-cases where a distinct role for NFTs is required that MUST NOT have owner permissions. A contract that implements the consumer role and grants ownership permissions to the consumer renders this standard pointless. - -## Backwards Compatibility - -This standard is compatible with current EIP-721 standards. There are no other standards that define a similar role for NFTs and the name (`consumer`) is not used by other EIP-721 related standards. - -## Test Cases - -Test cases are available in the reference implementation [here](../assets/eip-4400/test/erc721-consumable.ts). - -## Reference Implementation - -The reference implementation can be found [here](../assets/eip-4400/contracts/ERC721Consumable.sol). - -## Security Considerations - -Implementors of the `EIP721Consumable` standard must consider thoroughly the permissions they give to `consumers`. Even if they implement the standard correctly and do not allow transfer/burning of NFTs, they might still provide permissions to the `consumers` that they might not want to provide otherwise and should be restricted to `owners` only. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4400.md diff --git a/EIPS/eip-4430.md b/EIPS/eip-4430.md index 48dc234bc00564..28d81a19852a87 100644 --- a/EIPS/eip-4430.md +++ b/EIPS/eip-4430.md @@ -1,145 +1 @@ ---- -eip: 4430 -title: Described Transactions -description: A technique for contracts to provide a human-readable description of a transaction's side-effects. -author: Richard Moore (@ricmoo), Nick Johnson (@arachnid) -discussions-to: https://ethereum-magicians.org/t/discussion-eip-4430-described-transactions/8762 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-11-07 ---- - -## Abstract - -Use a contract method to provide *virtual functions* which can generate -a human-readable description at the same time as the machine-readable -bytecode, allowing the user to agree to the human-readable component -in a UI while the machine can execute the bytecode once accepted. - - -## Motivation - -When using an Ethereum Wallet (e.g. MetaMask, Clef, Hardware Wallets) -users must accept a transaction before it can be submitted (or the user -may decline). - -Due to the complexity of Ethereum transactions, wallets are very limited -in their ability to provide insight into the effects of a transaction -that the user is approving; outside special-cased support for common -transactions such as ERC20 transfers, this often amounts to asking the -user to sign an opaque blob of binary data. - -This EIP presents a method for dapp developers to enable a more comfortable -user experience by providing wallets with a means to generate a better -description about what the contract claims will happen. - -It does not address malicious contracts which wish to lie, it only addresses -honest contracts that want to make their user's life better. We believe -that this is a reasonable security model, as transaction descriptions can be -audited at the same time as contract code, allowing auditors and code -reviewers to check that transaction descriptions are accurate as part of -their review. - - -## Specification - -The **description** (a string) and the matching **execcode** (bytecode) -are generated simultaneously by evaluating the method on a contract: - -```solidity -function eipXXXDescribe(bytes inputs, bytes32 reserved) view returns (string description, bytes execcode) -``` - -The human-readable **description** can be shown in any client which supports -user interaction for approval, while the **execcode** is the data that -should be included in a transaction to the contract to perform that operation. - -The method must be executable in a static context, (i.e. any side effects, -such as logX, sstore, etc.), including through indirect calls may be ignored. - -During evaluation, the `ADDRESS` (i.e. `to`), `CALLER` (i.e. `from`), `VALUE`, -and `GASPRICE` must be the same as the values for the transaction being -described, so that the code generating the description can rely on them. - -When executing the bytecode, best efforts should be made to ensure `BLOCKHASH`, -`NUMBER`, `TIMESTAMP` and `DIFFICULTY` match the `"latest"` block. The -`COINBASE` should be the zero address. - -The method may revert, in which case the signing must be aborted. - - -## Rationale - -### Meta Description - -There have been many attempts to solve this problem, many of which attempt -to examine the encoded transaction data or message data directly. - -In many cases, the information that would be necessary for a meaningful -description is not present in the final encoded transaction data or message -data. - -Instead this EIP uses an indirect description of the data. - -For example, the `commit(bytes32)` method of ENS places a commitment -**hash** on-chain. The hash contains the **blinded** name and address; -since the name is blinded, the encoded data (i.e. the hash) no longer -contains the original values and is insufficient to access the necessary -values to be included in a description. - -By instead describing the commitment indirectly (with the original information -intact: NAME, ADDRESS and SECRET) a meaningful description can be computed -(e.g. "commit to NAME for ADDRESS (with SECRET)") and the matching data can -be computed (i.e. `commit(hash(name, owner, secret))`). - -This technique of blinded data will become much more popular with L2 -solutions, which use blinding not necessarily for privacy, but for -compression. - -### Entangling the Contract Address - -To prevent signed data being used across contracts, the contract address -is entanlged into both the transaction implicitly via the `to` field. - - -### Alternatives - -- NatSpec and company are a class of more complex languages that attempt to describe the encoded data directly. Because of the language complexity they often end up being quite large requiring entire runtime environments with ample processing power and memory, as well as additional sandboxing to reduce security concerns. One goal of this is to reduce the complexity to something that could execute on hardware wallets and other simple wallets. These also describe the data directly, which in many cases (such as blinded data), cannot adequately describe the data at all - -- Custom Languages; due to the complexity of Ethereum transactions, any language used would require a lot of expressiveness and re-inventing the wheel. The EVM already exists (it may not be ideal), but it is there and can handle everything necessary. - -- Format Strings (e.g. Trustless Signing UI Protocol; format strings can only operate on the class of regular languages, which in many cases is insufficient to describe an Ethereum transaction. This was an issue quite often during early attempts at solving this problem. - -- The signTypedData [EIP-712](./eip-712.md) has many parallels to what this EIP aims to solve - - -## Backwards Compatibility - -This does not affect backwards compatibility. - - -## Reference Implementation - -I will add deployed examples by address and chain ID. - - -## Security Considerations - -### Escaping Text - -Wallets must be careful when displaying text provided by contracts and proper -efforts must be taken to sanitize it, for example, be sure to consider: - -- HTML could be embedded to attempt to trick web-based wallets into executing code using the script tag (possibly uploading any private keys to a server) -- In general, extreme care must be used when rendering HTML; consider the ENS names `not-ricmoo.eth` or ` ricmoo.eth`, which if rendered without care would appear as `ricmoo.eth`, which it is not -- Other marks which require escaping could be included, such as quotes (`"`), formatting (`\n` (new line), `\f` (form feed), `\t` (tab), any of many non-standard whitespaces), back-slassh (`\`) -- UTF-8 has had bugs in the past which could allow arbitrary code execution and crashing renderers; consider using the UTF-8 replacement character (or *something*) for code-points outside common planes or common sub-sets within planes -- Homoglyphs attacks -- Right-to-left mark may affect rendering -- Many other things, deplnding on your environment - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4430.md diff --git a/EIPS/eip-4494.md b/EIPS/eip-4494.md index 450f4ca83a6496..dcadfd4efa6bdb 100644 --- a/EIPS/eip-4494.md +++ b/EIPS/eip-4494.md @@ -1,207 +1 @@ ---- -eip: 4494 -title: Permit for ERC-721 NFTs -description: ERC-712-singed approvals for ERC-721 NFTs -author: Simon Fremaux (@dievardump), William Schwab (@wschwab) -discussions-to: https://ethereum-magicians.org/t/eip-extending-erc2612-style-permits-to-erc721-nfts/7519/2 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-11-25 -requires: 165, 712, 721 ---- - -## Abstract -The "Permit" approval flow outlined in [ERC-2612](./eip-2612.md) has proven a very valuable advancement in UX by creating gasless approvals for ERC20 tokens. This EIP extends the pattern to ERC-721 NFTs. This EIP borrows heavily from ERC-2612. - -This requires a separate EIP due to the difference in structure between ERC-20 and ERC-721 tokens. While ERC-20 permits use value (the amount of the ERC-20 token being approved) and a nonce based on the owner's address, ERC-721 permits focus on the `tokenId` of the NFT and increment nonce based on the transfers of the NFT. - -## Motivation -The permit structure outlined in [ERC-2612](./eip-2612.md) allows for a signed message (structured as outlined in [ERC-712](./eip-712.md)) to be used in order to create an approval. Whereas the normal approval-based pull flow generally involves two transactions, one to approve a contract and a second for the contract to pull the asset, which is poor UX and often confuses new users, a permit-style flow only requires signing a message and a transaction. Additional information can be found in [ERC-2612](./eip-2612.md). - -[ERC-2612](./eip-2612.md) only outlines a permit architecture for ERC-20 tokens. This ERC proposes an architecture for ERC-721 NFTs, which also contain an approve architecture that would benefit from a signed message-based approval flow. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Three new functions MUST be added to [ERC-721](./eip-721.md): -```solidity -pragma solidity 0.8.10; - -import "./IERC165.sol"; - -/// -/// @dev Interface for token permits for ERC-721 -/// -interface IERC4494 is IERC165 { - /// ERC165 bytes to add to interface array - set in parent contract - /// - /// _INTERFACE_ID_ERC4494 = 0x5604e225 - - /// @notice Function to approve by way of owner signature - /// @param spender the address to approve - /// @param tokenId the index of the NFT to approve the spender on - /// @param deadline a timestamp expiry for the permit - /// @param sig a traditional or EIP-2098 signature - function permit(address spender, uint256 tokenId, uint256 deadline, bytes memory sig) external; - /// @notice Returns the nonce of an NFT - useful for creating permits - /// @param tokenId the index of the NFT to get the nonce of - /// @return the uint256 representation of the nonce - function nonces(uint256 tokenId) external view returns(uint256); - /// @notice Returns the domain separator used in the encoding of the signature for permits, as defined by EIP-712 - /// @return the bytes32 domain separator - function DOMAIN_SEPARATOR() external view returns(bytes32); -} -``` -The semantics of which are as follows: - -For all addresses `spender`, `uint256`s `tokenId`, `deadline`, and `nonce`, and `bytes` `sig`, a call to `permit(spender, tokenId, deadline, sig)` MUST set `spender` as approved on `tokenId` as long as the owner of `tokenId` remains in possession of it, and MUST emit a corresponding `Approval` event, if and only if the following conditions are met: - -* the current blocktime is less than or equal to `deadline` -* the owner of the `tokenId` is not the zero address -* `nonces[tokenId]` is equal to `nonce` -* `sig` is a valid `secp256k1` or [EIP-2098](./eip-2098.md) signature from owner of the `tokenId`: -``` -keccak256(abi.encodePacked( - hex"1901", - DOMAIN_SEPARATOR, - keccak256(abi.encode( - keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"), - spender, - tokenId, - nonce, - deadline)) -)); -``` -where `DOMAIN_SEPARATOR` MUST be defined according to [EIP-712](./eip-712.md). The `DOMAIN_SEPARATOR` should be unique to the contract and chain to prevent replay attacks from other domains, and satisfy the requirements of EIP-712, but is otherwise unconstrained. A common choice for `DOMAIN_SEPARATOR` is: -``` -DOMAIN_SEPARATOR = keccak256( - abi.encode( - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), - keccak256(bytes(name)), - keccak256(bytes(version)), - chainid, - address(this) -)); -``` -In other words, the message is the following ERC-712 typed structure: -```json -{ - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Permit": [ - { - "name": "spender", - "type": "address" - }, - { - "name": "tokenId", - "type": "uint256" - }, - { - "name": "nonce", - "type": "uint256" - }, - { - "name": "deadline", - "type": "uint256" - } - ], - "primaryType": "Permit", - "domain": { - "name": erc721name, - "version": version, - "chainId": chainid, - "verifyingContract": tokenAddress - }, - "message": { - "spender": spender, - "value": value, - "nonce": nonce, - "deadline": deadline - } -}} -``` -In addition: -* the `nonce` of a particular `tokenId` (`nonces[tokenId]`) MUST be incremented upon any transfer of the `tokenId` -* the `permit` function MUST check that the signer is not the zero address - -Note that nowhere in this definition do we refer to `msg.sender`. The caller of the `permit` function can be any address. - -This EIP requires [EIP-165](./eip-165.md). EIP165 is already required in [ERC-721](./eip-721.md), but is further necessary here in order to register the interface of this EIP. Doing so will allow easy verification if an NFT contract has implemented this EIP or not, enabling them to interact accordingly. The interface of this EIP (as defined in EIP-165) is `0x5604e225`. Contracts implementing this EIP MUST have the `supportsInterface` function return `true` when called with `0x5604e225`. - -## Rationale -The `permit` function is sufficient for enabling a `safeTransferFrom` transaction to be made without the need for an additional transaction. - -The format avoids any calls to unknown code. - -The `nonces` mapping is given for replay protection. - -A common use case of permit has a relayer submit a Permit on behalf of the owner. In this scenario, the relaying party is essentially given a free option to submit or withhold the Permit. If this is a cause of concern, the owner can limit the time a Permit is valid for by setting deadline to a value in the near future. The deadline argument can be set to uint(-1) to create Permits that effectively never expire. - -ERC-712 typed messages are included because of its use in [ERC-2612](./eip-2612.md), which in turn cites widespread adoption in many wallet providers. - -While ERC-2612 focuses on the value being approved, this EIP focuses on the `tokenId` of the NFT being approved via `permit`. This enables a flexibility that cannot be achieved with ERC-20 (or even [ERC-1155](./eip-1155.md)) tokens, enabling a single owner to give multiple permits on the same NFT. This is possible since each ERC-721 token is discrete (oftentimes referred to as non-fungible), which allows assertion that this token is still in the possession of the `owner` simply and conclusively. - -Whereas ERC-2612 splits signatures into their `v,r,s` components, this EIP opts to instead take a `bytes` array of variable length in order to support [EIP-2098](./eip-2098) signatures (64 bytes), which cannot be easily separated or reconstructed from `r,s,v` components (65 bytes). - -## Backwards Compatibility -There are already some ERC-721 contracts implementing a `permit`-style architecture, most notably Uniswap v3. - -Their implementation differs from the specification here in that: - * the `permit` architecture is based on `owner` - * the `nonce` is incremented at the time the `permit` is created - * the `permit` function must be called by the NFT owner, who is set as the `owner` - * the signature is split into `r,s,v` instead of `bytes` - - Rationale for differing on design decisions is detailed above. - -## Test Cases - -Basic test cases for the reference implementation can be found [here](https://github.com/dievardump/erc721-with-permits/tree/main/test). - -In general, test suites should assert at least the following about any implementation of this EIP: -* the nonce is incremented after each transfer -* `permit` approves the `spender` on the correct `tokenId` -* the permit cannot be used after the NFT is transferred -* an expired permit cannot be used - -## Reference Implementation - -A reference implementation has been set up [here](https://github.com/dievardump/erc721-with-permits). - -## Security Considerations - -Extra care should be taken when creating transfer functions in which `permit` and a transfer function can be used in one function to make sure that invalid permits cannot be used in any way. This is especially relevant for automated NFT platforms, in which a careless implementation can result in the compromise of a number of user assets. - -The remaining considerations have been copied from [ERC-2612](./eip-2612.md) with minor adaptation, and are equally relevant here: - -Though the signer of a `Permit` may have a certain party in mind to submit their transaction, another party can always front run this transaction and call `permit` before the intended party. The end result is the same for the `Permit` signer, however. - -Since the ecrecover precompile fails silently and just returns the zero address as `signer` when given malformed messages, it is important to ensure `ownerOf(tokenId) != address(0)` to avoid `permit` from creating an approval to any `tokenId` which does not have an approval set. - -Signed `Permit` messages are censorable. The relaying party can always choose to not submit the `Permit` after having received it, withholding the option to submit it. The `deadline` parameter is one mitigation to this. If the signing party holds ETH they can also just submit the `Permit` themselves, which can render previously signed `Permit`s invalid. - -The standard [ERC-20 race condition for approvals](https://swcregistry.io/docs/SWC-114) applies to `permit` as well. - -If the `DOMAIN_SEPARATOR` contains the `chainId` and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4494.md diff --git a/EIPS/eip-4519.md b/EIPS/eip-4519.md index b1743323f289e4..de772075c2a7c3 100644 --- a/EIPS/eip-4519.md +++ b/EIPS/eip-4519.md @@ -1,225 +1 @@ ---- -eip: 4519 -title: Non-Fungible Tokens Tied to Physical Assets -description: Interface for non-fungible tokens representing physical assets that can generate or recover their own accounts and obey users. -author: Javier Arcenegui (@Hardblock-IMSE-CNM), Rosario Arjona (@RosarioArjona), Roberto Román , Iluminada Baturone (@lumi2018) -discussions-to: https://ethereum-magicians.org/t/new-proposal-of-smart-non-fungible-token/7677 -status: Final -type: Standards Track -category: ERC -created: 2021-12-03 -requires: 165, 721 ---- - -## Abstract - -This EIP standardizes an interface for non-fungible tokens representing physical assets, such as Internet of Things (IoT) devices. These NFTs are tied to physical assets and can verify the authenticity of the tie. They can include an Ethereum address of the physical asset, permitting physical assets to sign messages and transactions. Physical assets can operate with an operating mode defined by its corresponding NFT. - -## Motivation - -This standard was developed because [EIP-721](./eip-721.md) only tracks ownership (not usage rights) and does not track the Ethereum addresses of the asset. The popularity of smart assets, such as IoT devices, is increasing. To permit secure and traceable management, these NFTs can be used to establish secure communication channels between the physical asset, its owner, and its user. - -## Specification - -The attributes `addressAsset` and `addressUser` are, respectively, the Ethereum addresses of the physical asset and the user. They are optional attributes but at least one of them should be used in an NFT. In the case of using only the attribute `addressUser`, two states define if the token is assigned or not to a user. `Figure 1` shows these states in a flow chart. When a token is created, transferred or unassigned, the token state is set to `notAssigned`. If the token is assigned to a valid user, the state is set to `userAssigned`. - -![Figure 1 : Flow chart of the token states with `addressUser` defined (and `addressAsset` undefined)](../assets/eip-4519/images/Figure1.jpg) - -In the case of defining the attribute `addressAsset` but not the attribute `addressUser`, two states define if the token is waiting for authentication with the owner or if the authentication has finished successfully. `Figure 2` shows these states in a flow chart. When a token is created or transferred to a new owner, then the token changes its state to `waitingForOwner`. In this state, the token is waiting for the mutual authentication between the asset and the owner. Once authentication is finished successfully, the token changes its state to `engagedWithOwner`. - -![Figure 2 : Flow chart of the token states with `addressAsset` defined (and `addressUser` undefined)](../assets/eip-4519/images/Figure2.jpg) - -Finally, if both the attributes `addressAsset` and `addressUser` are defined, the states of the NFT define if the asset has been engaged or not with the owner or the user (`waitingForOwner`, `engagedWithOwner`, `waitingForUser` and `engagedWithUser`). The flow chart in `Figure 3` shows all the possible state changes. The states related to the owner are the same as in `Figure 2`. The difference is that, at the state `engagedWithOwner`, the token can be assigned to a user. If a user is assigned (the token being at states `engagedWithOwner`, `waitingForUser` or `engagedWithUser`), then the token changes its state to `waitingForUser`. Once the asset and the user authenticate each other, the state of the token is set to `engagedWithUser`, and the user is able to use the asset. - - ![Figure 3 : Flow chart of the token states with `addressUser` and `addressUser` defined](../assets/eip-4519/images/Figure3.jpg) - -In order to complete the ownership transfer of a token, the new owner must carry out a mutual authentication process with the asset, which is off-chain with the asset and on-chain with the token, by using their Ethereum addresses. Similarly, a new user must carry out a mutual authentication process with the asset to complete a use transfer. NFTs define how the authentication processes start and finish. These authentication processes allow deriving fresh session cryptographic keys for secure communication between assets and owners, and between assets and users. Therefore, the trustworthiness of the assets can be traced even if new owners and users manage them. - -When the NFT is created or when the ownership is transferred, the token state is `waitingForOwner`. The asset sets its operating mode to `waitingForOwner`. The owner generates a pair of keys using the elliptic curve secp256k1 and the primitive element P used on this curve: a secret key SKO_A and a Public Key PKO_A, so that PKO_A = SKO_A * P. To generate the shared key between the owner and the asset, KO, the public key of the asset, PKA, is employed as follows: - -KO = PKA * SKO_A - -Using the function `startOwnerEngagement`, PKO_A is saved as the attribute `dataEngagement` and the hash of KO as the attribute `hashK_OA`. The owner sends request engagement to the asset, and the asset calculates: - -KA = SKA * PKO_A - -If everything is correctly done, KO and KA are the same since: - -KO = PKA * SKO_A = (SKA * P) * SKO_A = SKA * (SKO_A * P) = SKA * PKO_A - -Using the function `ownerEngagement`, the asset sends the hash of KA, and if it is the same as the data in `hashK_OA`, then the state of the token changes to `engagedWithOwner` and the event `OwnerEngaged` are sent. Once the asset receives the event, it changes its operation mode to `engagedWithOwner`. This process is shown in `Figure 4`. From this moment, the asset can be managed by the owner and they can communicate in a secure way using the shared key. - - ![Figure 4: Steps in a successful owner and asset mutual authentication process](../assets/eip-4519/images/Figure4.jpg) - -If the asset consults Ethereum and the state of its NFT is `waitingForUser`, the asset (assuming it is an electronic physical asset) sets its operating mode to `waitingForUser`. Then, a mutual authentication process is carried out with the user, as already done with the owner. The user sends the transaction associated with the function `startUserEngagement`. As in `startOwnerEngagement`, this function saves the public key generated by the user, PKU_A, as the attribute `dataEngagement` and the hash of KU = PKA * SKU_A as the attribute `hashK_UA` in the NFT. - -The user sends request engagement and the asset calculates: - -KA = SKA * PKU_A - -If everything is correctly done, KU and KA are the same since: - -KU = PKA * SKU_A = (SKA * P) * SKU_A = SKA * (SKU_A * P) = SKA * PKU_A - -Using the function `userEngagement`, the asset sends the hash of KA obtained and if it is the same as the data in `hashK_UA`, then the state of the token changes to `engagedWithUser` and the event `UserEngaged` is sent. Once the asset receives the event, it changes its operation mode to `engagedWithUser`. This process is shown in `Figure 5`. From this moment, the asset can be managed by the user and they can communicate in a secure way using the shared key. - - ![Figure 5: Steps in a successful user and asset mutual authentication process](../assets/eip-4519/images/Figure5.jpg) - -Since the establishment of a shared secret key is very important for a secure communication, NFTs include the attributes -`hashK_OA`, `hashK_UA` and `dataEngagement`. The first two attributes define, respectively, the hash of the secret key shared between the asset and its owner and between the asset and its user. Assets, owners and users should check they are using the correct shared secret keys. The attribute `dataEngagement` defines the public data needed for the agreement. - -```solidity -pragma solidity ^0.8.0; - /// @title EIP-4519 NFT: Extension of EIP-721 Non-Fungible Token Standard. -/// Note: the EIP-165 identifier for this interface is 0x8a68abe3 - interface EIP-4519 NFT is EIP721/*,EIP165*/{ - /// @dev This emits when the NFT is assigned as utility of a new user. - /// This event emits when the user of the token changes. - /// (`_addressUser` == 0) when no user is assigned. - event UserAssigned(uint256 indexed tokenId, address indexed _addressUser); - - /// @dev This emits when user and asset finish mutual authentication process successfully. - /// This event emits when both the user and the asset prove they share a secure communication channel. - event UserEngaged(uint256 indexed tokenId); - - /// @dev This emits when owner and asset finish mutual authentication process successfully. - /// This event emits when both the owner and the asset prove they share a secure communication channel. - event OwnerEngaged(uint256 indexed tokenId); - - /// @dev This emits when it is checked that the timeout has expired. - /// This event emits when the timestamp of the EIP-4519 NFT is not updated in timeout. - event TimeoutAlarm(uint256 indexed tokenId); - /// @notice This function defines how the NFT is assigned as utility of a new user (if "addressUser" is defined). - /// @dev Only the owner of the EIP-4519 NFT can assign a user. If "addressAsset" is defined, then the state of the token must be - /// "engagedWithOwner","waitingForUser" or "engagedWithUser" and this function changes the state of the token defined by "_tokenId" to - /// "waitingForUser". If "addressAsset" is not defined, the state is set to "userAssigned". In both cases, this function sets the parameter - /// "addressUser" to "_addressUser". - /// @param _tokenId is the tokenId of the EIP-4519 NFT tied to the asset. - /// @param _addressUser is the address of the new user. - function setUser(uint256 _tokenId, address _addressUser) external payable; - /// @notice This function defines the initialization of the mutual authentication process between the owner and the asset. - /// @dev Only the owner of the token can start this authentication process if "addressAsset" is defined and the state of the token is "waitingForOwner". - /// The function does not change the state of the token and saves "_dataEngagement" - /// and "_hashK_OA" in the parameters of the token. - /// @param _tokenId is the tokenId of the EIP-4519 NFT tied to the asset. - /// @param _dataEngagement is the public data proposed by the owner for the agreement of the shared key. - /// @param _hashK_OA is the hash of the secret proposed by the owner to share with the asset. - function startOwnerEngagement(uint256 _tokenId, uint256 _dataEngagement, uint256 _hashK_OA) external payable; - - /// @notice This function completes the mutual authentication process between the owner and the asset. - /// @dev Only the asset tied to the token can finish this authentication process provided that the state of the token is - /// "waitingForOwner" and dataEngagement is different from 0. This function compares hashK_OA saved in - /// the token with hashK_A. If they are equal then the state of the token changes to "engagedWithOwner", dataEngagement is set to 0, - /// and the event "OwnerEngaged" is emitted. - /// @param _hashK_A is the hash of the secret generated by the asset to share with the owner. - function ownerEngagement(uint256 _hashK_A) external payable; - - /// @notice This function defines the initialization of the mutual authentication process between the user and the asset. - /// @dev Only the user of the token can start this authentication process if "addressAsset" and "addressUser" are defined and - /// the state of the token is "waitingForUser". The function does not change the state of the token and saves "_dataEngagement" - /// and "_hashK_UA" in the parameters of the token. - /// @param _tokenId is the tokenId of the EIP-4519 NFT tied to the asset. - /// @param _dataEngagement is the public data proposed by the user for the agreement of the shared key. - /// @param _hashK_UA is the hash of the secret proposed by the user to share with the asset. - function startUserEngagement(uint256 _tokenId, uint256 _dataEngagement, uint256 _hashK_UA) external payable; - - /// @notice This function completes the mutual authentication process between the user and the asset. - /// @dev Only the asset tied to the token can finish this authentication process provided that the state of the token is - /// "waitingForUser" and dataEngagement is different from 0. This function compares hashK_UA saved in - /// the token with hashK_A. If they are equal then the state of the token changes to "engagedWithUser", dataEngagement is set to 0, - /// and the event "UserEngaged" is emitted. - /// @param _hashK_A is the hash of the secret generated by the asset to share with the user. - function userEngagement(uint256 _hashK_A) external payable; - - /// @notice This function checks if the timeout has expired. - /// @dev Everybody can call this function to check if the timeout has expired. The event "TimeoutAlarm" is emitted - /// if the timeout has expired. - /// @param _tokenId is the tokenId of the EIP-4519 NFT tied to the asset. - /// @return true if timeout has expired and false in other case. - function checkTimeout(uint256 _tokenId) external returns (bool); - - /// @notice This function sets the value of timeout. - /// @dev Only the owner of the token can set this value provided that the state of the token is "engagedWithOwner", - /// "waitingForUser" or "engagedWithUser". - /// @param _tokenId is the tokenId of the EIP-4519 NFT tied to the asset. - /// @param _timeout is the value to assign to timeout. - function setTimeout(uint256 _tokenId, uint256 _timeout) external; - - /// @notice This function updates the timestamp, thus avoiding the timeout alarm. - /// @dev Only the asset tied to the token can update its own timestamp. - function updateTimestamp() external; - - /// @notice This function lets obtain the tokenId from an address. - /// @dev Everybody can call this function. The code executed only reads from Ethereum. - /// @param _addressAsset is the address to obtain the tokenId from it. - /// @return tokenId of the token tied to the asset that generates _addressAsset. - function tokenFromBCA(address _addressAsset) external view returns (uint256); - - /// @notice This function lets know the owner of the token from the address of the asset tied to the token. - /// @dev Everybody can call this function. The code executed only reads from Ethereum. - /// @param _addressAsset is the address to obtain the owner from it. - /// @return owner of the token bound to the asset that generates _addressAsset. - function ownerOfFromBCA(address _addressAsset) external view returns (address); - - /// @notice This function lets know the user of the token from its tokenId. - /// @dev Everybody can call this function. The code executed only reads from Ethereum. - /// @param _tokenId is the tokenId of the EIP-4519 NFT tied to the asset. - /// @return user of the token from its _tokenId. - function userOf(uint256 _tokenId) external view returns (address); - - /// @notice This function lets know the user of the token from the address of the asset tied to the token. - /// @dev Everybody can call this function. The code executed only reads from Ethereum. - /// @param _addressAsset is the address to obtain the user from it. - /// @return user of the token tied to the asset that generates _addressAsset. - function userOfFromBCA(address _addressAsset) external view returns (address); - - /// @notice This function lets know how many tokens are assigned to a user. - /// @dev Everybody can call this function. The code executed only reads from Ethereum. - /// @param _addressUser is the address of the user. - /// @return number of tokens assigned to a user. - function userBalanceOf(address _addressUser) external view returns (uint256); - - /// @notice This function lets know how many tokens of a particular owner are assigned to a user. - /// @dev Everybody can call this function. The code executed only reads from Ethereum. - /// @param _addressUser is the address of the user. - /// @param _addressOwner is the address of the owner. - /// @return number of tokens assigned to a user from an owner. - function userBalanceOfAnOwner(address _addressUser, address _addressOwner) external view returns (uint256); -} -``` - -## Rationale - -### Authentication - -This EIP uses smart contracts to verify the mutual authentication process since smart contracts are trustless. - -### Tie Time - -This EIP proposes including the attribute timestamp (to register in Ethereum the last time that the physical asset checked the tie with its token) and the attribute timeout (to register the maximum delay time established for the physical asset to prove again the tie). These attributes avoid that a malicious owner or user could use the asset endlessly. - -When the asset calls `updateTimestamp`, the smart contract must call `block.timestamp`, which provides current block timestamp as seconds since Unix epoch. For this reason, `timeout` must be provided in seconds. - -### EIP-721-based - -[EIP-721](./eip-721.md) is the most commonly-used standard for generic NFTs. This EIP extends EIP-721 for backwards compatibility. - -## Backwards Compatibility - -This standard is an extension of EIP-721. It is fully compatible with both of the commonly used optional extensions (`IERC721Metadata` and `IERC721Enumerable`) mentioned in the EIP-721 standard. - -## Test Cases - -The test cases presented in the paper shown below are available [here](../assets/eip-4519/PoC_SmartNFT/README.md). - -## Reference Implementation - -A first version was presented in a paper of the Special Issue **Security, Trust and Privacy in New Computing Environments** of **Sensors** journal of **MDPI** editorial. The paper, entitled [Secure Combination of IoT and Blockchain by Physically Binding IoT Devices to Smart Non-Fungible Tokens Using PUFs](../assets/eip-4519/sensors-21-03119.pdf), was written by the same authors of this EIP. - -## Security Considerations - -In this EIP, a generic system has been proposed for the creation of non-fungible tokens tied to physical assets. A generic point of view based on the improvements of the current EIP-721 NFT is provided, such as the implementation of the user management mechanism, which does not affect the token's ownership. The physical asset should have the ability to generate an Ethereum address from itself in a totally random way so that only the asset is able to know the secret from which the Ethereum address is generated. In this way, identity theft is avoided and the asset can be proven to be completely genuine. In order to ensure this, it is recommended that only the manufacturer of the asset has the ability to create its associated token. In the case of an IoT device, the device firmware will be unable to share and modify the secret. Instead of storing the secrets, it is recommended that assets reconstruct their secrets from non-sensitive information such as the helper data associated with Physical Unclonable Functions (PUFs). Although a secure key exchange protocol based on elliptic curves has been proposed, the token is open to coexist with other types of key exchange. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4519.md diff --git a/EIPS/eip-4521.md b/EIPS/eip-4521.md index d12bf999bd8cf6..a60afa327f35b5 100644 --- a/EIPS/eip-4521.md +++ b/EIPS/eip-4521.md @@ -1,62 +1 @@ ---- -eip: 4521 -title: 721/20-compatible transfer -description: Recommends a simple extension to make NFTs compatible with apps and contracts that handle fungibles. -author: Ross Campbell (@z0r0z) -discussions-to: https://ethereum-magicians.org/t/eip-4521-721-20-compatible-transfer/7903 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-12-13 -requires: 721 ---- - -## Abstract -ERC-721, the popular standard for non-fungible tokens (NFTs), includes send functions, such as `transferFrom()` and `safeTransferFrom()`, but does not include a backwards-compatible `transfer()` found in fungible ERC-20 tokens. This standard provides references to add such a `transfer()`. - -## Motivation -This standard proposes a simple extension to allow NFTs to work with contracts designed to manage ERC-20s and many consumer wallets which expect to be able to execute a token `transfer()`. For example, if an NFT is inadvertently sent to a contract that typically handles ERC-20, that NFT will be locked. It should also simplify the task for contract programmers if they can rely on `transfer()` to both handle ERC-20 and NFTs. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -The interface for ERC-4521 `transfer()` MUST conform to ERC-20 and resulting transfers MUST fire the `Transfer` event as described in ERC-721. - -```sol -function transfer(address to, uint256 tokenId) external returns (bool success); -``` - -## Rationale -Replicating ERC-20 `transfer()` with just a minor change to accurately reflect that a unique `tokenId` rather than fungible sum is being sent is desirable for code simplicity and to make integration easier. - -## Backwards Compatibility -This EIP does not introduce any known backward compatibility issues. - -## Reference Implementation -A reference implementation of an ERC-4521 `transfer()`: - -```sol -function transfer(address to, uint256 tokenId) public virtual returns (bool success) { - require(msg.sender == ownerOf[tokenId], "NOT_OWNER"); - - unchecked { - balanceOf[msg.sender]--; - - balanceOf[to]++; - } - - delete getApproved[tokenId]; - - ownerOf[tokenId] = to; - - emit Transfer(msg.sender, to, tokenId); - - success = true; -} -``` - -## Security Considerations -Implementers must be sure to include the relevant return `bool` value for an ERC-4521 in order to conform with existing contracts that use ERC-20 interfaces, otherwise, NFTs may be locked unless a `safeTransfer` is used in such contracts. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4521.md diff --git a/EIPS/eip-4524.md b/EIPS/eip-4524.md index 4475577eddebd2..e60254afaf2853 100644 --- a/EIPS/eip-4524.md +++ b/EIPS/eip-4524.md @@ -1,82 +1 @@ ---- -eip: 4524 -title: Safer ERC-20 -description: Extending ERC-20 with ERC165 and adding safeTransfer (like ERC-721 and ERC-1155) -author: William Schwab (@wschwab) -discussions-to: https://ethereum-magicians.org/t/why-isnt-there-an-erc-for-safetransfer-for-erc20/7604 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-12-05 -requires: 20, 165 ---- - -## Abstract - -This standard extends [ERC-20](./eip-20.md) tokens with [EIP-165](./eip-165.md), and adds familiar functions from [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) ensuring receiving contracts have implemented proper functionality. - -## Motivation - -[EIP-165](./eip-165.md) adds (among other things) the ability to tell if a target recipient explicitly signals compatibility with an ERC. This is already used in the EIPs for NFTs, [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md). In addition, EIP-165 is a valuable building block for extensions on popular standards to signal implementation, a trend we've seen in a number of NFT extensions. This EIP aims to bring these innovations back to ERC-20. - -The importance of [EIP-165](./eip-165.md) is perhaps felt most for app developers looking to integrate with a generic standard such as ERC-20 or ERC-721, while integrating newer innovations built atop these standards. An easy example would be token permits, which allow for a one-transaction approval and transfer. This has already been implemented in many popular ERC-20 tokens using the [ERC-2612](./eip-2612.md) standard or similar. A platform integrating ERC-20 tokens has no easy way of telling if a particular token has implemented token permits or not. (As of this writing, ERC-2612 does not require EIP-165.) With EIP-165, the app (or contracts) could query `supportsInterface` to see if the `interfaceId` of a particular EIP is registered (in this case, EIP-2612), allowing for easier and more modular functions interacting with ERC-20 contracts. It is already common in NFT extensions to include an EIP-165 interface with a standard, we would argue this is at least in part due to the underlying [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) standards integrating EIP-165. Our hope is that this extension to ERC-20 would also help future extensions by making them easier to integrate. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -In order to be compliant with this EIP, and ERC-20-compliant contract MUST also implement the following functions: -```solidity -pragma solidity 0.8.10; - -import './IERC20.sol'; -import './IERC165.sol'; - -// the EIP-165 interfaceId for this interface is 0x534f5876 - -interface SaferERC-20 is IERC20, IERC165 { - function safeTransfer(address to, uint256 amount) external returns(bool); - function safeTransfer(address to, uint256 amount, bytes memory data) external returns(bool); - function safeTransferFrom(address from, address to, uint256 amount) external returns(bool); - function safeTransferFrom(address from, address to, uint256 amount, bytes memory data) external returns(bool); -} -``` -`safeTransfer` and `safeTransferFrom` MUST transfer as expected to EOA addresses, and to contracts implementing `ERC20Receiver` and returning the function selector (`0x4fc35859`) when called, and MUST revert when transferring to a contract which either does not have `ERC20Receiver` implemented, or does not return the function selector when called. - -In addition, a contract accepting safe transfers MUST implement the following if it wishes to accept safe transfers, and MUST return the function selector (`0x4fc35859`): -```solidity -pragma solidity 0.8.10; - -import './IERC165.sol'; - -interface ERC20Receiver is IERC165 { - function onERC20Received( - address _operator, - address _from, - uint256 _amount, - bytes _data - ) external returns(bytes4); -} -``` - -## Rationale - -This EIP is meant to be minimal and straightforward. Adding EIP-165 to ERC-20 is useful for a number of applications, and outside of a minimal amount of code increasing contract size, carries no downside. The `safeTransfer` and `safeTransferFrom` functions are well recognized from ERC-721 and ERC-1155, and therefore keeping identical naming conventions is reasonable, and the benefits of being able to check for implementation before transferring are as useful for ERC-20 tokens as they are for ERC-721 and ERC-1155. - -Another easy backport from EIP721 and EIP1155 might be the inclusion of a metadata URI for tokens, allowing them to easily reference logo and other details. This has not been included, both in order to keep this EIP as minimal as possible, and because it is already sufficiently covered by [EIP-1046](./eip-1046.md). - -## Backwards Compatibility - -There are no issues with backwards compatibility in this EIP, as the full suite of ERC-20 functions is unchanged. - -## Test Cases -Test cases have been provided in the implementation repo [here](https://github.com/wschwab/SaferERC-20/blob/main/src/SaferERC-20.t.sol). - -## Reference Implementation -A sample repo demonstrating an implementation of this EIP has been created [here](https://github.com/wschwab/SaferERC-20). It is (as of this writing) in a Dapptools environment, for details on installing and running Dapptools see the Dapptools repo. - -## Security Considerations - -`onERC20Received` is a callback function. Callback functions have been exploited in the past as a reentrancy vector, and care should be taken to make sure implementations are not vulnerable. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4524.md diff --git a/EIPS/eip-4527.md b/EIPS/eip-4527.md index bd99e2dfce3d88..910c5fcfca17ba 100644 --- a/EIPS/eip-4527.md +++ b/EIPS/eip-4527.md @@ -1,239 +1 @@ ---- -eip: 4527 -title: QR Code transmission protocol for wallets -description: QR Code data transmission protocol between wallets and offline signers. -author: Aaron Chen (@aaronisme), Sora Lee (@soralit), ligi (@ligi), Dan Miller (@danjm), AndreasGassmann (@andreasgassmann), xardass (@xardass), Lixin Liu (@BitcoinLixin) -discussions-to: https://ethereum-magicians.org/t/add-qr-code-scanning-between-software-wallet-cold-signer-hardware-wallet/6568 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-12-07 ---- - -## Abstract - -The purpose of this EIP is to provide a process and data transmission protocol via QR Code between offline signers and watch-only wallets. - -## Motivation - -There is an increasing number of users whom like to use complete offline signers to manage their private keys, signers like hardware wallets and mobile phones in offline mode. In order to sign transactions or data, these offline signers have to rely on a watch-only wallet since it would prepare the data to be signed. Currently, there are 4 possible data transmission methods between offline signers and watch-only wallets: QR Code, USB, Bluetooth, and file transfer. The QR Code data transmission method have the following advantages when compared to the other three methods mentioned above: - -- Transparency and Security: Compared to USB or Bluetooth, users can easily decode the data via QR Code (with the help of some tools). It can also help users clearly identify what they are going to sign, which improves transparency and thus better security. -- Improved Compatibility: Compared to USB and Bluetooth, QR Code data transmissions has a wider range of compatibility. Normally, it wouldn’t be broken by software changes like browser upgrades, system upgrade, and etc. -- Improved User experience: QR Code data transmissions can provide a better user experience compared to USB, Bluetooth, and file transfer especially when the user is using a mobile device. -- A smaller attack surface: USB and Bluetooth have a bigger attack surface than QR-Codes. - -Due to these advantages, QR Code data transmissions is a better choice. Unfortunately, there is no modern standard for how offline signers should work with watch-only wallets nor how data should be encoded. -This EIP presents a standard process and data transmission protocol for offline signers to work with watch-only wallets. - -## Specification - -**Offline signer**: An offline signer is a device or application which holds the user’s private keys and does not have network access. - -**Watch-only wallet**: A watch-only wallet is a wallet that has network access and can interact with the Ethereum blockchain. - -### Process - -In order to work with offline signers, the watch-only wallet should follow the following process. - -1. The offline signer provides the public key information to the watch-only wallet to generate addresses, sync balances and etc via QR Codes. -2. The watch-only wallet generates the unsigned data and sends it to an offline signer for signing via QR Code, data that can include transactions, typed data, and etc. -3. The offline signer signs the data and provides a signature back to the watch-only wallet via QR Code. -4. The watch-only wallet receives the signature, constructs the signed data (transaction) and performs the following activities like broadcasting the transaction etc. - -### Data Transmission Protocol - -Since a single QR Code can only contain a limited amount of data, animated QR Codes should be utilized for data transmission. The `BlockchainCommons` have published a series of data transmission protocol called Uniform Resources (UR). It provides a basic method to encode data into animated QR Codes. This EIP will use UR and extend its current definition. - -`Concise Binary Object Representation(CBOR)` will be used for binary data encoding. `Concise Data Definition Language(CDDL)` will be used for expressing the CBOR. - -### Setting up the watch-only wallet with the offline signer - -In order to allow a watch-only wallet to collect information from the Ethereum blockchain, the offline signer would need to provide the public keys to the watch-only wallet in which the wallet will use them to query the necessary information from the Ethereum blockchain. - -In such a case, offline signers should provide the extended public keys and derivation path. The UR Type called `crypto-hdkey` will be used to encode this data and the derivation path will be encoded as `crypto-keypath`. - - -#### CDDL for Key Path - -The `crypto-keypath` will be used to specify the key path.The following specification is written in Concise Data Definition Language(CDDL) for `crypto-key-path` - -``` -; Metadata for the derivation path of a key. -; -; `source-fingerprint`, if present, is the fingerprint of the -; ancestor key from which the associated key was derived. -; -; If `components` is empty, then `source-fingerprint` MUST be a fingerprint of -; a master key. -; -; `depth`, if present, represents the number of derivation steps in -; the path of the associated key, even if not present in the `components` element -; of this structure. - crypto-keypath = { - components: [path-component], ; If empty, source-fingerprint MUST be present - ? source-fingerprint: uint32 .ne 0 ; fingerprint of ancestor key, or master key if components is empty - ? depth: uint8 ; 0 if this is a public key derived directly from a master key - } - path-component = ( - child-index / child-index-range / child-index-wildcard-range, - is-hardened - ) - uint32 = uint .size 4 - uint31 = uint32 .lt 2147483648 ;0x80000000 - child-index = uint31 - child-index-range = [child-index, child-index] ; [low, high] where low < high - child-index-wildcard = [] - is-hardened = bool - components = 1 - source-fingerprint = 2 - depth = 3 -``` - -#### CDDL for Extended Public Keys - -Since the purpose is to transfer public key data, the definition of `crypto-hdkey` will be kept only for public key usage purposes. - -The following specification is written in Concise Data Definition Language `CDDL` and includes the crypto-keypath spec above. - -``` -; An hd-key must be a derived key. -hd-key = { - derived-key -} -; A derived key must be public, has an optional chain code, and -; may carry additional metadata about its use and derivation. -; To maintain isomorphism with [BIP32] and allow keys to be derived from -; this key `chain-code`, `origin`, and `parent-fingerprint` must be present. -; If `origin` contains only a single derivation step and also contains `source-fingerprint`, -; then `parent-fingerprint` MUST be identical to `source-fingerprint` or may be omitted. -derived-key = ( - key-data: key-data-bytes, - ? chain-code: chain-code-bytes ; omit if no further keys may be derived from this key - ? origin: #6.304(crypto-keypath), ; How the key was derived - ? name: text, ; A short name for this key. - ? source: text, ; The device info or any other description for this key -) -key-data = 3 -chain-code = 4 -origin = 6 -name = 9 -source = 10 - -uint8 = uint .size 1 -key-data-bytes = bytes .size 33 -chain-code-bytes = bytes .size 32 -``` - -If the chain-code is provided, then it can be used to derive child keys but if it isn’t provided, it is simply a solo key and the origin can be provided to indicate the derivation key path. - -If the signer would like to provide muliple public keys instead of the extended public key for any reason, the signer can use `crypto-account` for that. - -### Sending the unsigned data from the watch-only wallet to the offline signer - -To send the unsigned data from a watch-only wallet to an offline signer, the new UR type `eth-sign-request` will be introduced to encode the signing request. - -#### CDDL for Eth Sign Request. - -The following specification is written in Concise Data Definition Language `CDDL`. -UUIDs in this specification notated UUID are CBOR binary strings tagged with #6.37, per the IANA `CBOR Tags Registry`. - -``` -; Metadata for the signing request for Ethereum. -; -sign-data-type = { - type: int .default 1 transaction data; the unsigned data type -} - -eth-transaction-data = 1; legacy transaction rlp encoding of unsigned transaction data -eth-typed-data = 2; EIP-712 typed signing data -eth-raw-bytes=3; for signing message usage, like EIP-191 personal_sign data -eth-typed-transaction=4; EIP-2718 typed transaction of unsigned transaction data - -; Metadata for the signing request for Ethereum. -; request-id: the identifier for this signing request. -; sign-data: the unsigned data -; data-type: see sign-data-type definition -; chain-id: chain id definition see https://github.com/ethereum-lists/chains for detail -; derivation-path: the key path of the private key to sign the data -; address: Ethereum address of the signing type for verification purposes which is optional - -eth-sign-request = ( - sign-data: sign-data-bytes, ; sign-data is the data to be signed by offline signer, currently it can be unsigned transaction or typed data - data-type: #3.401(sign-data-type), - chain-id: int .default 1, - derivation-path: #5.304(crypto-keypath), ;the key path for signing this request - ?request-id: uuid, ; the uuid for this signing request - ?address: eth-address-bytes, ;verification purpose for the address of the signing key - ?origin: text ;the origin of this sign request, like wallet name -) -request-id = 1 -sign-data = 2 -data-type = 3 -chain-id = 4 ;it will be the chain id of ethereum related blockchain -derivation-path = 5 -address = 6 -origin = 7 -eth-address-bytes = bytes .size 20 -sign-data-bytes = bytes ; for unsigned transactions it will be the rlp encoding for unsigned transaction data and ERC 712 typed data it will be the bytes of json string. -``` - -### The signature provided by offline signers to watch-only wallets - -After the data is signed, the offline signer should send the signature back to the watch-only wallet. The new UR type called `eth-signature` is introduced here to encode this data. - -#### CDDL for Eth Signature. - -The following specification is written in Concise Data Definition Language `CDDL`. - -``` -eth-signature = ( - request-id: uuid, - signature: eth-signature-bytes, - ? origin: text, ; The device info for providing this signature -) - -request-id = 1 -signature = 2 -origin = 3 - -eth-signature-bytes = bytes .size 65; the signature of the signing request (r,s,v) -``` - -## Rationale - -This EIP uses some existing UR types like `crypto-keypath` and `crypto-hdkey` and also introduces some new UR types like `eth-sign-request` and `eth-signature`. Here are the reasons we choose UR for the QR Code data transmission protocol: - -### UR provides a solid foundation for QR Code data transmission - -- Uses the alphanumeric QR code mode for efficiency. -- Includes a CRC32 checksum of the entire message in each part to tie the different parts of the QR code together and ensure the transmitted message has been reconstructed. -- uses `Fountain Code` for the arbitrary amount of data which can be both a minimal, finite sequence of parts and an indefinite sequence of parts. The Fountain Code can ultimately help the receiver to make the data extraction easier. - -### UR provides existing helpful types and scalability for new usages - -Currently, UR has provided some existing types like `crypto-keypath` and `crypto-hdkey` so it is quite easy to add a new type and definitions for new usages. - -### UR has an active air-gapped wallet community. - -Currently, the UR has an active `airgapped wallet community` which continues to improve the UR forward. - -## Backwards Compatibility - -Currently, there is no existing protocol to define data transmissions via QR Codes so there are no backward compatibility issues that needs to be addressed now. - -## Test Cases - -The test cases can be found on the `ur-registry-eth` package released by the Keystone team. - -## Reference Implementation - -The reference implementation can be found on the `ur-registry-eth` package released by the Keystone team. - -## Security Considerations - -The offline signer should decode all the data from `eth-sign-request` and show them to the user for confirmation prior to signing. It is recommended to provide an address field in the `eth-sign-request`. If provided, the offline signer should verify the address being the same one as the address associated with the signing key. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4527.md diff --git a/EIPS/eip-4546.md b/EIPS/eip-4546.md index c27fcabce67b99..8372709b2437b7 100644 --- a/EIPS/eip-4546.md +++ b/EIPS/eip-4546.md @@ -1,173 +1 @@ ---- -eip: 4546 -title: Wrapped Deposits -description: A singleton contract for managing asset deposits. -author: Justice Hudson (@jchancehud) -discussions-to: https://ethereum-magicians.org/t/wrapped-deposit-contract-eip/7740 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-12-11 ---- - -## Abstract -The wrapped deposit contract handles deposits of assets (Ether, [ERC-20](./eip-20.md), [ERC-721](./eip-721.md)) on behalf of a user. A user must only approve a spend limit once and then an asset may be deposited to any number of different applications that support deposits from the contract. - -## Motivation -The current user flow for depositing assets in dapps is unnecessarily expensive and insecure. To deposit an ERC-20 asset a user must either: - - - send an approve transaction for the exact amount being sent, before making a deposit, and then repeat this process for every subsequent deposit. - - send an approve transaction for an infinite spend amount before making deposits. - -The first option is inconvenient, and expensive. The second option is insecure. Further, explaining approvals to new or non-technical users is confusing. This has to be done in _every_ dapp that supports ERC20 deposits. - -## Specification -The wrapped deposit contract SHOULD be deployed at an identifiable address (e.g. `0x1111119a9e30bceadf9f939390293ffacef93fe9`). The contract MUST be non-upgradable with no ability for state variables to be changed. - -The wrapped deposit contract MUST have the following public functions: - -```js -depositERC20(address to, address token, uint amount) external; -depositERC721(address to, address token, uint tokenId) external; -safeDepositERC721(address to, address token, uint tokenId, bytes memory data) external; -safeDepositERC1155(address to, address token, uint tokenId, uint value, bytes calldata data) external; -batchDepositERC1155(address to, address token, uint[] calldata tokenIds, uint[] calldata values, bytes calldata data) external; -depositEther(address to) external payable; -``` - -Each of these functions MUST revert if `to` is an address with a zero code size. Each function MUST attempt to call a method on the `to` address confirming that it is willing and able to accept the deposit. If this function call does not return a true value execution MUST revert. If the asset transfer is not successful execution MUST revert. - -The following interfaces SHOULD exist for contracts wishing to accept deposits: - -```ts -interface ERC20Receiver { - function acceptERC20Deposit(address depositor, address token, uint amount) external returns (bool); -} - -interface ERC721Receiver { - function acceptERC721Deposit(address depositor, address token, uint tokenId) external returns (bool); -} - -interface ERC1155Receiver { - function acceptERC1155Deposit(address depositor, address token, uint tokenId, uint value, bytes calldata data) external returns (bool); - function acceptERC1155BatchDeposit(address depositor, address token, uint[] calldata tokenIds, uint[] calldata values, bytes calldata data) external returns (bool); -} - -interface EtherReceiver { - function acceptEtherDeposit(address depositor, uint amount) external returns (bool); -} -``` - -A receiving contract MAY implement any of these functions as desired. If a given function is not implemented deposits MUST not be sent for that asset type. - -## Rationale -Having a single contract that processes all token transfers allows users to submit a single approval per token to deposit to any number of contracts. The user does not have to trust receiving contracts with token spend approvals and receiving contracts have their complexity reduced by not having to implement token transfers themselves. - -User experience is improved because a simple global dapp can be implemented with the messaging: "enable token for use in other apps". - -## Backwards Compatibility - -This EIP is not backward compatible. Any contract planning to use this deposit system must implement specific functions to accept deposits. Existing contracts that are upgradeable can add support for this EIP retroactively by implementing one or more accept deposit functions. - -Upgraded contracts can allow deposits using both the old system (approving the contract itself) and the proposed deposit system to preserve existing approvals. New users should be prompted to use the proposed deposit system. - -## Reference Implementation -```ts -pragma solidity ^0.7.0; - -interface ERC20Receiver { - function acceptERC20Deposit(address depositor, address token, uint amount) external returns (bool); -} - -interface ERC721Receiver { - function acceptERC721Deposit(address depositor, address token, uint tokenId) external returns (bool); -} - -interface ERC1155Receiver { - function acceptERC1155Deposit(address depositor, address token, uint tokenId, uint value, bytes calldata data) external returns (bool); - function acceptERC1155BatchDeposit(address depositor, address token, uint[] calldata tokenIds, uint[] calldata values, bytes calldata data) external returns (bool); -} - -interface EtherReceiver { - function acceptEtherDeposit(address depositor, uint amount) external returns (bool); -} - -interface IERC20 { - function transferFrom(address sender, address recipient, uint amount) external returns (bool); -} - -interface IERC721 { - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external payable; -} - -interface IERC1155 { - function safeTransferFrom(address _from, address _to, uint _id, uint _value, bytes calldata _data) external; - function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external; -} - -contract WrappedDeposit { - function depositERC20(address to, address token, uint amount) public { - _assertContract(to); - require(ERC20Receiver(to).acceptERC20Deposit(msg.sender, token, amount)); - bytes memory data = abi.encodeWithSelector( - IERC20(token).transferFrom.selector, - msg.sender, - to, - amount - ); - (bool success, bytes memory returndata) = token.call(data); - require(success); - // backward compat for tokens incorrectly implementing the transfer function - if (returndata.length > 0) { - require(abi.decode(returndata, (bool)), "ERC20 operation did not succeed"); - } - } - - function depositERC721(address to, address token, uint tokenId) public { - _assertContract(to); - require(ERC721Receiver(to).acceptERC721Deposit(msg.sender, token, tokenId)); - IERC721(token).transferFrom(msg.sender, to, tokenId); - } - - function safeDepositERC721(address to, address token, uint tokenId, bytes memory data) public { - _assertContract(to); - require(ERC721Receiver(to).acceptERC721Deposit(msg.sender, token, tokenId)); - IERC721(token).safeTransferFrom(msg.sender, to, tokenId, data); - } - - function safeDepositERC1155(address to, address token, uint tokenId, uint value, bytes calldata data) public { - _assertContract(to); - require(ERC1155Receiver(to).acceptERC1155Deposit(msg.sender, to, tokenId, value, data)); - IERC1155(token).safeTransferFrom(msg.sender, to, tokenId, value, data); - } - - function batchDepositERC1155(address to, address token, uint[] calldata tokenIds, uint[] calldata values, bytes calldata data) public { - _assertContract(to); - require(ERC1155Receiver(to).acceptERC1155BatchDeposit(msg.sender, to, tokenIds, values, data)); - IERC1155(token).safeBatchTransferFrom(msg.sender, to, tokenIds, values, data); - } - - function depositEther(address to) public payable { - _assertContract(to); - require(EtherReceiver(to).acceptEtherDeposit(msg.sender, msg.value)); - (bool success, ) = to.call{value: msg.value}(''); - require(success, "nonpayable"); - } - - function _assertContract(address c) private view { - uint size; - assembly { - size := extcodesize(c) - } - require(size > 0, "noncontract"); - } -} -``` -## Security Considerations -The wrapped deposit implementation should be as small as possible to reduce the risk of bugs. The contract should be small enough that an engineer can read and understand it in a few minutes. - -Receiving contracts MUST verify that `msg.sender` is equal to the wrapped deposit contract. Failing to do so allows anyone to simulate deposits. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4546.md diff --git a/EIPS/eip-4626.md b/EIPS/eip-4626.md index c28df5cba1ac01..7c3d74cb20ef39 100644 --- a/EIPS/eip-4626.md +++ b/EIPS/eip-4626.md @@ -1,612 +1 @@ ---- -eip: 4626 -title: Tokenized Vaults -description: Tokenized Vaults with a single underlying EIP-20 token. -author: Joey Santoro (@joeysantoro), t11s (@transmissions11), Jet Jadeja (@JetJadeja), Alberto Cuesta Cañada (@alcueca), Señor Doggo (@fubuloubu) -discussions-to: https://ethereum-magicians.org/t/eip-4626-yield-bearing-vault-standard/7900 -status: Final -type: Standards Track -category: ERC -created: 2021-12-22 -requires: 20, 2612 ---- - -## Abstract - -The following standard allows for the implementation of a standard API for tokenized Vaults -representing shares of a single underlying [EIP-20](./eip-20.md) token. -This standard is an extension on the EIP-20 token that provides basic functionality for depositing -and withdrawing tokens and reading balances. - -## Motivation - -Tokenized Vaults have a lack of standardization leading to diverse implementation details. -Some various examples include lending markets, aggregators, and intrinsically interest bearing tokens. -This makes integration difficult at the aggregator or plugin layer for protocols which need to conform to many standards, and forces each protocol to implement their own adapters which are error prone and waste development resources. - -A standard for tokenized Vaults will lower the integration effort for yield-bearing vaults, while creating more consistent and robust implementation patterns. - -## Specification - -All [EIP-4626](./eip-4626.md) tokenized Vaults MUST implement EIP-20 to represent shares. -If a Vault is to be non-transferrable, it MAY revert on calls to `transfer` or `transferFrom`. -The EIP-20 operations `balanceOf`, `transfer`, `totalSupply`, etc. operate on the Vault "shares" -which represent a claim to ownership on a fraction of the Vault's underlying holdings. - -All EIP-4626 tokenized Vaults MUST implement EIP-20's optional metadata extensions. -The `name` and `symbol` functions SHOULD reflect the underlying token's `name` and `symbol` in some way. - -EIP-4626 tokenized Vaults MAY implement [EIP-2612](./eip-2612.md) to improve the UX of approving shares on various integrations. - -### Definitions: - -- asset: The underlying token managed by the Vault. - Has units defined by the corresponding EIP-20 contract. -- share: The token of the Vault. Has a ratio of underlying assets - exchanged on mint/deposit/withdraw/redeem (as defined by the Vault). -- fee: An amount of assets or shares charged to the user by the Vault. Fees can exists for - deposits, yield, AUM, withdrawals, or anything else prescribed by the Vault. -- slippage: Any difference between advertised share price and economic realities of - deposit to or withdrawal from the Vault, which is not accounted by fees. - -### Methods - -#### asset - -The address of the underlying token used for the Vault for accounting, depositing, and withdrawing. - -MUST be an EIP-20 token contract. - -MUST _NOT_ revert. - -```yaml -- name: asset - type: function - stateMutability: view - - inputs: [] - - outputs: - - name: assetTokenAddress - type: address -``` - -#### totalAssets - -Total amount of the underlying asset that is "managed" by Vault. - -SHOULD include any compounding that occurs from yield. - -MUST be inclusive of any fees that are charged against assets in the Vault. - -MUST _NOT_ revert. - -```yaml -- name: totalAssets - type: function - stateMutability: view - - inputs: [] - - outputs: - - name: totalManagedAssets - type: uint256 -``` - -#### convertToShares - -The amount of shares that the Vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met. - -MUST NOT be inclusive of any fees that are charged against assets in the Vault. - -MUST NOT show any variations depending on the caller. - -MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - -MUST NOT revert unless due to integer overflow caused by an unreasonably large input. - -MUST round down towards 0. - -This calculation MAY NOT reflect the "per-user" price-per-share, and instead should reflect the "average-user's" price-per-share, meaning what the average user should expect to see when exchanging to and from. - -```yaml -- name: convertToShares - type: function - stateMutability: view - - inputs: - - name: assets - type: uint256 - - outputs: - - name: shares - type: uint256 -``` - -#### convertToAssets - -The amount of assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met. - -MUST NOT be inclusive of any fees that are charged against assets in the Vault. - -MUST NOT show any variations depending on the caller. - -MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - -MUST NOT revert unless due to integer overflow caused by an unreasonably large input. - -MUST round down towards 0. - -This calculation MAY NOT reflect the "per-user" price-per-share, and instead should reflect the "average-user's" price-per-share, meaning what the average user should expect to see when exchanging to and from. - -```yaml -- name: convertToAssets - type: function - stateMutability: view - - inputs: - - name: shares - type: uint256 - - outputs: - - name: assets - type: uint256 -``` - -#### maxDeposit - -Maximum amount of the underlying asset that can be deposited into the Vault for the `receiver`, through a `deposit` call. - -MUST return the maximum amount of assets `deposit` would allow to be deposited for `receiver` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). This assumes that the user has infinite assets, i.e. MUST NOT rely on `balanceOf` of `asset`. - -MUST factor in both global and user-specific limits, like if deposits are entirely disabled (even temporarily) it MUST return 0. - -MUST return `2 ** 256 - 1` if there is no limit on the maximum amount of assets that may be deposited. - -MUST NOT revert. - -```yaml -- name: maxDeposit - type: function - stateMutability: view - - inputs: - - name: receiver - type: address - - outputs: - - name: maxAssets - type: uint256 -``` - -#### previewDeposit - -Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions. - -MUST return as close to and no more than the exact amount of Vault shares that would be minted in a `deposit` call in the same transaction. I.e. `deposit` should return the same or more `shares` as `previewDeposit` if called in the same transaction. - -MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the deposit would be accepted, regardless if the user has enough tokens approved, etc. - -MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - -MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause `deposit` to revert. - -Note that any unfavorable discrepancy between `convertToShares` and `previewDeposit` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing. - -```yaml -- name: previewDeposit - type: function - stateMutability: view - - inputs: - - name: assets - type: uint256 - - outputs: - - name: shares - type: uint256 -``` - -#### deposit - -Mints `shares` Vault shares to `receiver` by depositing exactly `assets` of underlying tokens. - -MUST emit the `Deposit` event. - -MUST support EIP-20 `approve` / `transferFrom` on `asset` as a deposit flow. -MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the `deposit` execution, and are accounted for during `deposit`. - -MUST revert if all of `assets` cannot be deposited (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). - -Note that most implementations will require pre-approval of the Vault with the Vault's underlying `asset` token. - -```yaml -- name: deposit - type: function - stateMutability: nonpayable - - inputs: - - name: assets - type: uint256 - - name: receiver - type: address - - outputs: - - name: shares - type: uint256 -``` - -#### maxMint - -Maximum amount of shares that can be minted from the Vault for the `receiver`, through a `mint` call. - -MUST return the maximum amount of shares `mint` would allow to be deposited to `receiver` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). This assumes that the user has infinite assets, i.e. MUST NOT rely on `balanceOf` of `asset`. - -MUST factor in both global and user-specific limits, like if mints are entirely disabled (even temporarily) it MUST return 0. - -MUST return `2 ** 256 - 1` if there is no limit on the maximum amount of shares that may be minted. - -MUST NOT revert. - -```yaml -- name: maxMint - type: function - stateMutability: view - - inputs: - - name: receiver - type: address - - outputs: - - name: maxShares - type: uint256 -``` - -#### previewMint - -Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions. - -MUST return as close to and no fewer than the exact amount of assets that would be deposited in a `mint` call in the same transaction. I.e. `mint` should return the same or fewer `assets` as `previewMint` if called in the same transaction. - -MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint would be accepted, regardless if the user has enough tokens approved, etc. - -MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - -MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause `mint` to revert. - -Note that any unfavorable discrepancy between `convertToAssets` and `previewMint` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by minting. - -```yaml -- name: previewMint - type: function - stateMutability: view - - inputs: - - name: shares - type: uint256 - - outputs: - - name: assets - type: uint256 -``` - -#### mint - -Mints exactly `shares` Vault shares to `receiver` by depositing `assets` of underlying tokens. - -MUST emit the `Deposit` event. - -MUST support EIP-20 `approve` / `transferFrom` on `asset` as a mint flow. -MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the `mint` execution, and are accounted for during `mint`. - -MUST revert if all of `shares` cannot be minted (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). - -Note that most implementations will require pre-approval of the Vault with the Vault's underlying `asset` token. - -```yaml -- name: mint - type: function - stateMutability: nonpayable - - inputs: - - name: shares - type: uint256 - - name: receiver - type: address - - outputs: - - name: assets - type: uint256 -``` - -#### maxWithdraw - -Maximum amount of the underlying asset that can be withdrawn from the `owner` balance in the Vault, through a `withdraw` call. - -MUST return the maximum amount of assets that could be transferred from `owner` through `withdraw` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). - -MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0. - -MUST NOT revert. - -```yaml -- name: maxWithdraw - type: function - stateMutability: view - - inputs: - - name: owner - type: address - - outputs: - - name: maxAssets - type: uint256 -``` - -#### previewWithdraw - -Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions. - -MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a `withdraw` call in the same transaction. I.e. `withdraw` should return the same or fewer `shares` as `previewWithdraw` if called in the same transaction. - -MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though the withdrawal would be accepted, regardless if the user has enough shares, etc. - -MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - -MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause `withdraw` to revert. - -Note that any unfavorable discrepancy between `convertToShares` and `previewWithdraw` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing. - -```yaml -- name: previewWithdraw - type: function - stateMutability: view - - inputs: - - name: assets - type: uint256 - - outputs: - - name: shares - type: uint256 -``` - -#### withdraw - -Burns `shares` from `owner` and sends exactly `assets` of underlying tokens to `receiver`. - -MUST emit the `Withdraw` event. - -MUST support a withdraw flow where the shares are burned from `owner` directly where `owner` is `msg.sender`. - -MUST support a withdraw flow where the shares are burned from `owner` directly where `msg.sender` has EIP-20 approval over the shares of `owner`. - -MAY support an additional flow in which the shares are transferred to the Vault contract before the `withdraw` execution, and are accounted for during `withdraw`. - -SHOULD check `msg.sender` can spend owner funds, assets needs to be converted to shares and shares should be checked for allowance. - -MUST revert if all of `assets` cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). - -Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. - -```yaml -- name: withdraw - type: function - stateMutability: nonpayable - - inputs: - - name: assets - type: uint256 - - name: receiver - type: address - - name: owner - type: address - - outputs: - - name: shares - type: uint256 -``` - -#### maxRedeem - -Maximum amount of Vault shares that can be redeemed from the `owner` balance in the Vault, through a `redeem` call. - -MUST return the maximum amount of shares that could be transferred from `owner` through `redeem` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). - -MUST factor in both global and user-specific limits, like if redemption is entirely disabled (even temporarily) it MUST return 0. - -MUST NOT revert. - -```yaml -- name: maxRedeem - type: function - stateMutability: view - - inputs: - - name: owner - type: address - - outputs: - - name: maxShares - type: uint256 -``` - -#### previewRedeem - -Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions. - -MUST return as close to and no more than the exact amount of assets that would be withdrawn in a `redeem` call in the same transaction. I.e. `redeem` should return the same or more `assets` as `previewRedeem` if called in the same transaction. - -MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the redemption would be accepted, regardless if the user has enough shares, etc. - -MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - -MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause `redeem` to revert. - -Note that any unfavorable discrepancy between `convertToAssets` and `previewRedeem` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by redeeming. - -```yaml -- name: previewRedeem - type: function - stateMutability: view - - inputs: - - name: shares - type: uint256 - - outputs: - - name: assets - type: uint256 -``` - -#### redeem - -Burns exactly `shares` from `owner` and sends `assets` of underlying tokens to `receiver`. - -MUST emit the `Withdraw` event. - -MUST support a redeem flow where the shares are burned from `owner` directly where `owner` is `msg.sender`. - -MUST support a redeem flow where the shares are burned from `owner` directly where `msg.sender` has EIP-20 approval over the shares of `owner`. - -MAY support an additional flow in which the shares are transferred to the Vault contract before the `redeem` execution, and are accounted for during `redeem`. - -SHOULD check `msg.sender` can spend owner funds using allowance. - -MUST revert if all of `shares` cannot be redeemed (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). - -Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. - -```yaml -- name: redeem - type: function - stateMutability: nonpayable - - inputs: - - name: shares - type: uint256 - - name: receiver - type: address - - name: owner - type: address - - outputs: - - name: assets - type: uint256 -``` - -### Events - -#### Deposit - -`sender` has exchanged `assets` for `shares`, and transferred those `shares` to `owner`. - -MUST be emitted when tokens are deposited into the Vault via the `mint` and `deposit` methods. - -```yaml -- name: Deposit - type: event - - inputs: - - name: sender - indexed: true - type: address - - name: owner - indexed: true - type: address - - name: assets - indexed: false - type: uint256 - - name: shares - indexed: false - type: uint256 -``` - -#### Withdraw - -`sender` has exchanged `shares`, owned by `owner`, for `assets`, and transferred those `assets` to `receiver`. - -MUST be emitted when shares are withdrawn from the Vault in `EIP-4626.redeem` or `EIP-4626.withdraw` methods. - -```yaml -- name: Withdraw - type: event - - inputs: - - name: sender - indexed: true - type: address - - name: receiver - indexed: true - type: address - - name: owner - indexed: true - type: address - - name: assets - indexed: false - type: uint256 - - name: shares - indexed: false - type: uint256 -``` - -## Rationale - -The Vault interface is designed to be optimized for integrators with a feature complete yet minimal interface. -Details such as accounting and allocation of deposited tokens are intentionally not specified, -as Vaults are expected to be treated as black boxes on-chain and inspected off-chain before use. - -EIP-20 is enforced because implementation details like token approval -and balance calculation directly carry over to the shares accounting. -This standardization makes the Vaults immediately compatible with all EIP-20 use cases in addition to EIP-4626. - -The mint method was included for symmetry and feature completeness. -Most current use cases of share-based Vaults do not ascribe special meaning to the shares such that -a user would optimize for a specific number of shares (`mint`) rather than specific amount of underlying (`deposit`). -However, it is easy to imagine future Vault strategies which would have unique and independently useful share representations. - -The `convertTo` functions serve as rough estimates that do not account for operation specific details like withdrawal fees, etc. -They were included for frontends and applications that need an average value of shares or assets, not an exact value possibly including slippage or other fees. -For applications that need an exact value that attempts to account for fees and slippage we have included a corresponding `preview` function to match each mutable function. These functions must not account for deposit or withdrawal limits, to ensure they are easily composable, the `max` functions are provided for that purpose. - -## Backwards Compatibility - -EIP-4626 is fully backward compatible with the EIP-20 standard and has no known compatibility issues with other standards. -For production implementations of Vaults which do not use EIP-4626, wrapper adapters can be developed and used. - -## Reference Implementation - -See [Solmate EIP-4626](https://github.com/Rari-Capital/solmate/blob/main/src/mixins/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): -a demo implementation of the standard in Vyper, with hooks for share price manipulation and other testing needs. - -## Security Considerations - -Fully permissionless use cases could fall prey to malicious implementations which only conform to the interface but not the specification. -It is recommended that all integrators review the implementation for potential ways of losing user deposits before integrating. - -If implementors intend to support EOA account access directly, they should consider adding an additional function call for `deposit`/`mint`/`withdraw`/`redeem` with the means to accommodate slippage loss or unexpected deposit/withdrawal limits, since they have no other means to revert the transaction if the exact output amount is not achieved. - -The methods `totalAssets`, `convertToShares` and `convertToAssets` are estimates useful for display purposes, -and do _not_ have to confer the _exact_ amount of underlying assets their context suggests. - -The `preview` methods return values that are as close as possible to exact as possible. For that reason, they are manipulable by altering the on-chain conditions and are not always safe to be used as price oracles. This specification includes `convert` methods that are allowed to be inexact and therefore can be implemented as robust price oracles. For example, it would be correct to implement the `convert` methods as using a time-weighted average price in converting between assets and shares. - -Integrators of EIP-4626 Vaults should be aware of the difference between these view methods when integrating with this standard. Additionally, note that the amount of underlying assets a user may receive from redeeming their Vault shares (`previewRedeem`) can be significantly different than the amount that would be taken from them when minting the same quantity of shares (`previewMint`). The differences may be small (like if due to rounding error), or very significant (like if a Vault implements withdrawal or deposit fees, etc). Therefore integrators should always take care to use the preview function most relevant to their use case, and never assume they are interchangeable. - -Finally, EIP-4626 Vault implementers should be aware of the need for specific, opposing rounding directions across the different mutable and view methods, as it is considered most secure to favor the Vault itself during calculations over its users: - -- If (1) it's calculating how many shares to issue to a user for a certain amount of the underlying tokens they provide or (2) it's determining the amount of the underlying tokens to transfer to them for returning a certain amount of shares, it should round _down_. - -- If (1) it's calculating the amount of shares a user has to supply to receive a given amount of the underlying tokens or (2) it's calculating the amount of underlying tokens a user has to provide to receive a certain amount of shares, it should round _up_. - -The only functions where the preferred rounding direction would be ambiguous are the `convertTo` functions. To ensure consistency across all EIP-4626 Vault implementations it is specified that these functions MUST both always round _down_. Integrators may wish to mimic rounding up versions of these functions themselves, like by adding 1 wei to the result. - -Although the `convertTo` functions should eliminate the need for any use of an EIP-4626 Vault's `decimals` variable, it is still strongly recommended to mirror -the underlying token's `decimals` if at all possible, to eliminate possible sources of confusion and simplify integration across front-ends and for other off-chain users. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4626.md diff --git a/EIPS/eip-4671.md b/EIPS/eip-4671.md index f7f55b91e7d710..e4e9fcf3750413 100644 --- a/EIPS/eip-4671.md +++ b/EIPS/eip-4671.md @@ -1,296 +1 @@ ---- -eip: 4671 -title: Non-Tradable Tokens Standard -description: A standard interface for non-tradable tokens, aka badges or souldbound NFTs. -author: Omar Aflak (@omaraflak), Pol-Malo Le Bris, Marvin Martin (@MarvinMartin24) -discussions-to: https://ethereum-magicians.org/t/eip-4671-non-tradable-token/7976 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-01-13 -requires: 165 ---- - -## Abstract - -A non-tradable token, or NTT, represents inherently personal possessions (material or immaterial), such as university diplomas, online training certificates, government issued documents (national id, driving license, visa, wedding, etc.), labels, and so on. - -As the name implies, non-tradable tokens are made to not be traded or transferred, they are "soulbound". They don't have monetary value, they are personally delivered to **you**, and they only serve as a **proof of possession/achievement**. - -In other words, the possession of a token carries a strong meaning in itself depending on **why** it was delivered. - -## Motivation - -We have seen in the past smart contracts being used to deliver university diplomas or driving licenses, for food labeling or attendance to events, and much more. All of these implementations are different, but they have a common ground: the tokens are **non-tradable**. - -The blockchain has been used for too long as a means of speculation, and non-tradable tokens want to be part of the general effort aiming to provide usefulness through the blockchain. - -By providing a common interface for non-tradable tokens, we allow more applications to be developed and we position blockchain technology as a standard gateway for verification of personal possessions and achievements. - -## Specification - -### Non-Tradable Token - -A NTT contract is seen as representing **one type of certificate** delivered by **one authority**. For instance, one NTT contract for the French National Id, another for Ethereum EIP creators, and so on... - -* An address might possess multiple tokens. Each token has a unique identifier: `tokenId`. -* An authority who delivers a certificate should be in position to revoke it. Think of driving licenses or weddings. However, it cannot delete your token, i.e. the record will show that you once owned a token from that contract. -* The most typical usage for third-parties will be to verify if a user has a valid token in a given contract. - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IERC165.sol"; - -interface IERC4671 is IERC165 { - /// Event emitted when a token `tokenId` is minted for `owner` - event Minted(address owner, uint256 tokenId); - - /// Event emitted when token `tokenId` of `owner` is revoked - event Revoked(address owner, uint256 tokenId); - - /// @notice Count all tokens assigned to an owner - /// @param owner Address for whom to query the balance - /// @return Number of tokens owned by `owner` - function balanceOf(address owner) external view returns (uint256); - - /// @notice Get owner of a token - /// @param tokenId Identifier of the token - /// @return Address of the owner of `tokenId` - function ownerOf(uint256 tokenId) external view returns (address); - - /// @notice Check if a token hasn't been revoked - /// @param tokenId Identifier of the token - /// @return True if the token is valid, false otherwise - function isValid(uint256 tokenId) external view returns (bool); - - /// @notice Check if an address owns a valid token in the contract - /// @param owner Address for whom to check the ownership - /// @return True if `owner` has a valid token, false otherwise - function hasValid(address owner) external view returns (bool); -} -``` - -#### Extensions - -##### Metadata - -An interface allowing to add metadata linked to each token. - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Metadata is IERC4671 { - /// @return Descriptive name of the tokens in this contract - function name() external view returns (string memory); - - /// @return An abbreviated name of the tokens in this contract - function symbol() external view returns (string memory); - - /// @notice URI to query to get the token's metadata - /// @param tokenId Identifier of the token - /// @return URI for the token - function tokenURI(uint256 tokenId) external view returns (string memory); -} -``` - -##### Enumerable - -An interface allowing to enumerate the tokens of an owner. - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Enumerable is IERC4671 { - /// @return emittedCount Number of tokens emitted - function emittedCount() external view returns (uint256); - - /// @return holdersCount Number of token holders - function holdersCount() external view returns (uint256); - - /// @notice Get the tokenId of a token using its position in the owner's list - /// @param owner Address for whom to get the token - /// @param index Index of the token - /// @return tokenId of the token - function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256); - - /// @notice Get a tokenId by it's index, where 0 <= index < total() - /// @param index Index of the token - /// @return tokenId of the token - function tokenByIndex(uint256 index) external view returns (uint256); -} -``` - -##### Delegation - -An interface allowing delegation rights of token minting. - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Delegate is IERC4671 { - /// @notice Grant one-time minting right to `operator` for `owner` - /// An allowed operator can call the function to transfer rights. - /// @param operator Address allowed to mint a token - /// @param owner Address for whom `operator` is allowed to mint a token - function delegate(address operator, address owner) external; - - /// @notice Grant one-time minting right to a list of `operators` for a corresponding list of `owners` - /// An allowed operator can call the function to transfer rights. - /// @param operators Addresses allowed to mint - /// @param owners Addresses for whom `operators` are allowed to mint a token - function delegateBatch(address[] memory operators, address[] memory owners) external; - - /// @notice Mint a token. Caller must have the right to mint for the owner. - /// @param owner Address for whom the token is minted - function mint(address owner) external; - - /// @notice Mint tokens to multiple addresses. Caller must have the right to mint for all owners. - /// @param owners Addresses for whom the tokens are minted - function mintBatch(address[] memory owners) external; - - /// @notice Get the issuer of a token - /// @param tokenId Identifier of the token - /// @return Address who minted `tokenId` - function issuerOf(uint256 tokenId) external view returns (address); -} -``` - -##### Consensus - -An interface allowing minting/revocation of tokens based on a consensus of a predefined set of addresses. - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Consensus is IERC4671 { - /// @notice Get voters addresses for this consensus contract - /// @return Addresses of the voters - function voters() external view returns (address[] memory); - - /// @notice Cast a vote to mint a token for a specific address - /// @param owner Address for whom to mint the token - function approveMint(address owner) external; - - /// @notice Cast a vote to revoke a specific token - /// @param tokenId Identifier of the token to revoke - function approveRevoke(uint256 tokenId) external; -} -``` - -##### Pull - -An interface allowing a token owner to pull his token to a another of his wallets (here `recipient`). The caller must provide a signature of the tuple `(tokenId, owner, recipient)` using the `owner` wallet. - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Pull is IERC4671 { - /// @notice Pull a token from the owner wallet to the caller's wallet - /// @param tokenId Identifier of the token to transfer - /// @param owner Address that owns tokenId - /// @param signature Signed data (tokenId, owner, recipient) by the owner of the token - function pull(uint256 tokenId, address owner, bytes memory signature) external; -} -``` - -### NTT Store - -Non-tradable tokens are meant to be fetched by third-parties, which is why there needs to be a convenient way for users to expose some or all of their tokens. We achieve this result using a store which must implement the following interface. - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IERC165.sol"; - -interface IERC4671Store is IERC165 { - // Event emitted when a IERC4671Enumerable contract is added to the owner's records - event Added(address owner, address token); - - // Event emitted when a IERC4671Enumerable contract is removed from the owner's records - event Removed(address owner, address token); - - /// @notice Add a IERC4671Enumerable contract address to the caller's record - /// @param token Address of the IERC4671Enumerable contract to add - function add(address token) external; - - /// @notice Remove a IERC4671Enumerable contract from the caller's record - /// @param token Address of the IERC4671Enumerable contract to remove - function remove(address token) external; - - /// @notice Get all the IERC4671Enumerable contracts for a given owner - /// @param owner Address for which to retrieve the IERC4671Enumerable contracts - function get(address owner) external view returns (address[] memory); -} -``` - -## Rationale - -### On-chain vs Off-chain - -A decision was made to keep the data off-chain (via `tokenURI()`) for two main reasons: -* Non-tradable tokens represent personal possessions. Therefore, there might be cases where the data should be encrypted. The standard should not outline decisions about encryption because there are just so many ways this could be done, and every possibility is specific to the use-case. -* Non-tradable tokens must stay generic. There could have been a possibility to make a `MetadataStore` holding the data of tokens in an elegant way, unfortunately we would have needed a support for generics in solidity (or struct inheritance), which is not available today. - -## Reference Implementation - -You can find an implementation of this standard in [../assets/eip-4671](https://github.com/ethereum/EIPs/tree/master/assets/eip-4671). - -Using this implementation, this is how you would create a token: - -```solidity -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./ERC4671.sol"; - -contract EIPCreatorBadge is ERC4671 { - constructor() ERC4671("EIP Creator Badge", "EIP") {} - - function giveThatManABadge(address owner) external { - require(_isCreator(), "You must be the contract creator"); - _mint(owner); - } - - function _baseURI() internal pure override returns (string memory) { - return "https://eips.ethereum.org/ntt/"; - } -} -``` - -This could be a contract managed by the Ethereum foundation and which allows them to deliver tokens to EIP creators. - -## Security Considerations - -One security aspect is related to the `tokenURI` method which returns the metadata linked to a token. Since the standard represents inherently personal possessions, users might want to encrypt the data in some cases e.g. national id cards. Moreover, it is the responsibility of the contract creator to make sure the URI returned by this method is available at all times. - -The standard does not define any way to transfer a token from one wallet to another. Therefore, users must be very cautious with the wallet they use to receive these tokens. If a wallet is lost, the only way to get the tokens back is for the issuing authorities to deliver the tokens again, akin real life. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4671.md diff --git a/EIPS/eip-4675.md b/EIPS/eip-4675.md index eee6b6e95df24c..3f1e79dbcf0e70 100644 --- a/EIPS/eip-4675.md +++ b/EIPS/eip-4675.md @@ -1,213 +1 @@ ---- -eip: 4675 -title: Multi-Fractional Non-Fungible Tokens -description: Fractionalize multiple NFTs using a single contract -author: David Kim (@powerstream3604) -discussions-to: https://ethereum-magicians.org/t/eip-4675-multi-fractional-non-fungible-token-standard/8008 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-01-13 -requires: 165, 721 ---- - -## Abstract -This standard outlines a smart contract interface eligible to represent any number of fractionalized non-fungible tokens. Existing projects utilizing standards like [EIP-1633](./eip-1633.md) conventionally deploy separate [EIP-20](./eip-20.md) compatible token contracts to fractionalize the non-fungible token into EIP-20 tokens. In contrast, this ERC allows each token ID to represent a token type representing(fractionalizing) the non-fungible token. - -This standard is approximate in terms of using `_id` for distinguishing token types. However, this ERC has a clear difference with [EIP-1155](./eip-1155.md) as each `_id` represents a distinct NFT. - -## Motivation -The conventional fractionalization process of fractionalizing a NFT to FT requires deployment of a FT token contract representing the ownership of NFT. This leads to inefficient bytecode usage on Ethereum Blockchain and limits functionalities since each token contract is separated into its own permissioned address. -With the rise of multiple NFT projects needing to fractionalize NFT to FT, new type of token standard is needed to back up them. - -## Specification - -```solidity -/** - @title Multi-Fractional Non-Fungible Token Standard - @dev Note : The ERC-165 identifier for this interface is 0x83f5d35f. -*/ -interface IMFNFT { - /** - @dev This emits when ownership of any token changes by any mechanism. - The `_from` argument MUST be the address of an account/contract sending the token. - The `_to` argument MUST be the address of an account/contract receiving the token. - The `_id` argument MUST be the token type being transferred. (represents NFT) - The `_value` argument MUST be the number of tokens the holder balance is decrease by and match the recipient balance is increased by. - */ - event Transfer(address indexed _from, address indexed _to, uint256 indexed _id, uint256 _value); - - /** - @dev This emits when the approved address for token is changed or reaffirmed. - The `_owner` argument MUST be the address of account/contract approving to withdraw. - The `_spender` argument MUST be the address of account/contract approved to withdraw from the `_owner` balance. - The `_id` argument MUST be the token type being transferred. (represents NFT) - The `_value` argument MUST be the number of tokens the `_approved` is able to withdraw from `_owner` balance. - */ - event Approval(address indexed _owner, address indexed _spender, uint256 indexed _id, uint256 _value); - - /** - @dev This emits when new token type is added which represents the share of the Non-Fungible Token. - The `_parentToken` argument MUST be the address of the Non-Fungible Token contract. - The `_parentTokenId` argument MUST be the token ID of the Non-Fungible Token. - The `_id` argument MUST be the token type being added. (represents NFT) - The `_totalSupply` argument MUST be the number of total token supply of the token type. - */ - event TokenAddition(address indexed _parentToken, uint256 indexed _parentTokenId, uint256 _id, uint256 _totalSupply); - - /** - @notice Transfers `_value` amount of an `_id` from the msg.sender address to the `_to` address specified - @dev msg.sender must have sufficient balance to handle the tokens being transferred out of the account. - MUST revert if `_to` is the zero address. - MUST revert if balance of msg.sender for token `_id` is lower than the `_value` being transferred. - MUST revert on any other error. - MUST emit the `Transfer` event to reflect the balance change. - @param _to Source address - @param _id ID of the token type - @param _value Transfer amount - @return True if transfer was successful, false if not - */ - function transfer(address _to, uint256 _id, uint256 _value) external returns (bool); - - /** - @notice Approves `_value` amount of an `_id` from the msg.sender to the `_spender` address specified. - @dev msg.sender must have sufficient balance to handle the tokens when the `_spender` wants to transfer the token on behalf. - MUST revert if `_spender` is the zero address. - MUST revert on any other error. - MUST emit the `Approval` event. - @param _spender Spender address(account/contract which can withdraw token on behalf of msg.sender) - @param _id ID of the token type - @param _value Approval amount - @return True if approval was successful, false if not - */ - function approve(address _spender, uint256 _id, uint256 _value) external returns (bool); - - /** - @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified. - @dev Caller must be approved to manage the tokens being transferred out of the `_from` account. - MUST revert if `_to` is the zero address. - MUST revert if balance of holder for token `_id` is lower than the `_value` sent. - MUST revert on any other error. - MUST emit `Transfer` event to reflect the balance change. - @param _from Source address - @param _to Target Address - @param _id ID of the token type - @param _value Transfer amount - @return True if transfer was successful, false if not - - */ - function transferFrom(address _from, address _to, uint256 _id, uint256 _value) external returns (bool); - - /** - @notice Sets the NFT as a new type token - @dev The contract itself should verify if the ownership of NFT is belongs to this contract itself with the `_parentNFTContractAddress` & `_parentNFTTokenId` before adding the token. - MUST revert if the same NFT is already registered. - MUST revert if `_parentNFTContractAddress` is address zero. - MUST revert if `_parentNFTContractAddress` is not ERC-721 compatible. - MUST revert if this contract itself is not the owner of the NFT. - MUST revert on any other error. - MUST emit `TokenAddition` event to reflect the token type addition. - @param _parentNFTContractAddress NFT contract address - @param _parentNFTTokenId NFT tokenID - @param _totalSupply Total token supply - */ - function setParentNFT(address _parentNFTContractAddress, uint256 _parentNFTTokenId, uint256 _totalSupply) external; - - /** - @notice Get the token ID's total token supply. - @param _id ID of the token - @return The total token supply of the specified token type - */ - function totalSupply(uint256 _id) external view returns (uint256); - - /** - @notice Get the balance of an account's tokens. - @param _owner The address of the token holder - @param _id ID of the token - @return The _owner's balance of the token type requested - */ - function balanceOf(address _owner, uint256 _id) external view returns (uint256); - - /** - @notice Get the amount which `_spender` is still allowed to withdraw from `_owner` - @param _owner The address of the token holder - @param _spender The address approved to withdraw token on behalf of `_owner` - @param _id ID of the token - @return The amount which `_spender` is still allowed to withdraw from `_owner` - */ - function allowance(address _owner, address _spender, uint256 _id) external view returns (uint256); - - /** - @notice Get the bool value which represents whether the NFT is already registered and fractionalized by this contract. - @param _parentNFTContractAddress NFT contract address - @param _parentNFTTokenId NFT tokenID - @return The bool value representing the whether the NFT is already registered. - */ - function isRegistered(address _parentNFTContractAddress, uint256 _parentNFTTokenId) external view returns (bool); -} - -interface ERC165 { - /** - @notice Query if a contract implements an interface - @param interfaceID The interface identifier, as specified in ERC-165 - @dev Interface identification is specified in ERC-165. This function - uses less than 30,000 gas. - @return `true` if the contract implements `interfaceID` and - `interfaceID` is not 0xffffffff, `false` otherwise - */ - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -To receive Non-Fungible Token on `safe Transfer` the contract should include `onERC721Received()`. -Including `onERC721Received()` is needed to be compatible with Safe Transfer Rules. -```solidity -/** - @notice Handle the receipt of an NFT - @param _operator The address which called `safeTransferFrom` function - @param _from The address which previously owned the token - @param _tokenId The NFT identifier which is being transferred - @param _data Additional data with no specified format - @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` -*/ -function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external pure returns (bytes4); -``` - -## Rationale - -**Metadata** - -The `symbol()` & `name()` functions were not included since the majority of users can just fetch it from the originating NFT contract. Also, copying the name & symbol every time when token gets added might place a lot of redundant bytecode on the Ethereum blockchain. -However, according to the need and design of the project it could also be added to each token type by fetching the metadata from the NFT contract. - -**Design** - -Most of the decisions made around the design of this ERC were done to keep it as flexible for diverse token design & architecture. -These minimum requirement for this standard allows for each project to determine their own system for minting, governing, burning their MFNFT tokens depending on their programmable architecture. - -## Backwards Compatibility - -To make this standard compatible with existing standards, this standard `event` & `function` names are identical with ERC-20 token standard with some more `events` & `functions` to add token type dynamically. - -Also, the sequence of parameter in use of `_id` for distinguishing token types in `functions` and `events` are very much similar to ERC-1155 Multi-Token Standard. - -Since this standard is intended to interact with the EIP-721 Non-Fungible Token Standard, it is kept purposefully agnostic to extensions beyond the standard in order to allow specific projects to design their own token usage and scenario. - -## Test Cases - -Reference Implementation of MFNFT Token includes test cases written using hardhat. (Test coverage : 100%) - -## Reference Implementation -[MFNFT - Implementation](../assets/eip-4675/README.md) - -## Security Considerations - -To fractionalize an already minted NFT, it is evident that ownership of NFT should be given to token contracts before fractionalization. -In the case of fractionalizing NFT, the token contract should thoroughly verify the ownership of NFT before fractionalizing it to prevent tokens from being a separate tokens with the NFT. - -If an arbitrary account has the right to call `setParentNFT()` there might be a front-running issue. The caller of `setParentNFT()` might be different from the real NFT sender. -To prevent this issue, implementors should just allow **admin** to call, or fractionalize and receive NFT in an atomic transaction similar to flash loan(swap). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4675.md diff --git a/EIPS/eip-4799.md b/EIPS/eip-4799.md index 922d6828122fb0..9d4c8f5e31ec57 100644 --- a/EIPS/eip-4799.md +++ b/EIPS/eip-4799.md @@ -1,200 +1 @@ ---- -eip: 4799 -title: Non-Fungible Token Ownership Designation Standard -description: A standardized interface for designating ownership of an NFT -author: David Buckman (@davidbuckman), Isaac Buckman (@isaacbuckman) -discussions-to: https://ethereum-magicians.org/t/erc-4799-non-fungible-token-wrapping-standard/8396 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-02-13 -requires: 165 ---- - -## Abstract - -The following defines a standard interface for designating ownership of an NFT to someone while the NFT is held in escrow by a smart contract. The standard allows for the construction of a directed acyclic graph of NFTs, where the designated owner of every NFT in a given chain is the terminal address of that chain. This enables the introduction of additional functionality to pre-existing NFTs, without having to give up the authenticity of the original. In effect, this means that all NFTs are composable and can be rented, used as collateral, fractionalized, and more. - -## Motivation - -Many NFTs aim to provide their holders with some utility - utility that can come in many forms. This can be the right to inhabit an apartment, access to tickets to an event, an airdrop of tokens, or one of the infinitely many other potential applications. However, in their current form, NFTs are limited by the fact that the only verifiable wallet associated with an NFT is the owner, so clients that want to distribute utility are forced to do so to an NFT's listed owner. This means that any complex ownership agreements must be encoded into the original NFT contract - there is no mechanism by which an owner can link the authenticity of their original NFT to any external contract. - -The goal of this standard is to allow users and developers the ability to define arbitrarily complex ownership agreements on NFTs that have already been minted. This way, new contracts with innovative ownership structures can be deployed, but they can still leverage the authenticity afforded by established NFT contracts - in the past a wrapping contract meant brand new NFTs with no established authenticity. - -Prior to this standard, wrapping an NFT inside another contract was the only way to add functionality after the NFT contract had been deployed, but this meant losing access to the utility of holding the original NFT. Any application querying for the owner of that NFT would determine the wrapping smart contract to be the owner. Using this standard, applications will have a standardized method of interacting with wrapping contracts so that they can continue to direct their utility to users even when the NFT has been wrapped. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IERC4799NFT is IERC165 { - /// @dev This emits when ownership of any NFT changes by any mechanism. - /// This event emits when NFTs are created (`from` == 0) and destroyed - /// (`to` == 0). Exception: during contract creation, any number of NFTs - /// may be created and assigned without emitting Transfer. At the time of - /// any transfer, the approved address for that NFT (if any) is reset to none. - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenId - ); - - /// @notice Find the owner of an NFT - /// @dev NFTs assigned to zero address are considered invalid, and queries - /// about them throw - /// @param tokenId The identifier for an NFT - /// @return The address of the owner of the NFT - function ownerOf(uint256 tokenId) external view returns (address); -} -``` -```solidity -/// @title ERC-4799 Non-Fungible Token Ownership Designation Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-4799 -/// Note: the ERC-165 identifier for this interface is [TODO]. - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "./IERC4799NFT.sol"; - -interface IERC4799 is IERC165 { - /// @dev Emitted when a source token designates its ownership to the owner of the target token - event OwnershipDesignation( - IERC4799NFT indexed sourceContract, - uint256 sourceTokenId, - IERC4799NFT indexed targetContract, - uint256 targetTokenId - ); - - /// @notice Find the designated NFT - /// @param sourceContract The contract address of the source NFT - /// @param sourceTokenId The tokenId of the source NFT - /// @return (targetContract, targetTokenId) contract address and tokenId of the parent NFT - function designatedTokenOf(IERC4799NFT sourceContract, uint256 sourceTokenId) - external - view - returns (IERC4799NFT, uint256); -} -``` - -The authenticity of designated ownership of an NFT is conferred by the designating ERC-4799 contract’s ownership of the original NFT according to the source contract. This MUST be verified by clients by querying the source contract. - -Clients respecting this specification SHALL NOT distribute any utility to the address of the ERC-4799 contract. Instead, they MUST distribute it to the owner of the designated token that the ERC-4799 contract points them to. - -## Rationale - -To maximize the future compatibility of the wrapping contract, we first defined a canonical NFT interface. We created `IERC4799NFT`, an interface implicitly implemented by virtually all popular NFT contracts, including all deployed contracts that are [ERC-721](./eip-721.md) compliant. This interface represents the essence of an NFT: a mapping from a token identifier to the address of a singular owner, represented by the function `ownerOf`. - -The core of our proposal is the `IERC4799` interface, an interface for a standard NFT ownership designation contract (ODC). ERC4799 requires the implementation of a `designatedTokenOf` function, which maps a source NFT to exactly one target NFT. Through this function, the ODC expresses its belief of designated ownership. This designated ownership is only authentic if the ODC is listed as the owner of the original NFT, thus maintaining the invariant that every NFT has exactly one designated owner. - -## Backwards Compatibility - -The `IERC4799NFT` interface is backwards compatible with `IERC721`, as `IERC721` implicitly extends `IERC4799NFT`. This means that the ERC-4799 standard, which wraps NFTs that implement `ERC4799NFT`, is fully backwards compatible with ERC-721. - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.8.0 <0.9.0; - -import "./IERC4799.sol"; -import "./IERC4799NFT.sol"; -import "./ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -contract ERC721Composable is IERC4799, IERC721Receiver { - mapping(IERC4799NFT => mapping(uint256 => IERC4799NFT)) private _targetContracts; - mapping(IERC4799NFT => mapping(uint256 => uint256)) private _targetTokenIds; - - function designatedTokenOf(IERC4799NFT sourceContract, uint256 sourceTokenId) - external - view - override - returns (IERC4799NFT, uint256) - { - return ( - IERC4799NFT(_targetContracts[sourceContract][sourceTokenId]), - _targetTokenIds[sourceContract][sourceTokenId] - ); - } - - function designateToken( - IERC4799NFT sourceContract, - uint256 sourceTokenId, - IERC4799NFT targetContract, - uint256 targetTokenId - ) external { - require( - ERC721(address(sourceContract)).ownerOf(sourceTokenId) == msg.sender || - ERC721(address(sourceContract)).getApproved(sourceTokenId) == msg.sender, - "ERC721Composable: Only owner or approved address can set a designate ownership"); - _targetContracts[sourceContract][sourceTokenId] = targetContract; - _targetTokenIds[sourceContract][sourceTokenId] = targetTokenId; - emit OwnershipDesignation( - sourceContract, - sourceTokenId, - targetContract, - targetTokenId - ); - } - - function onERC721Received( - address, - address from, - uint256 sourceTokenId, - bytes calldata - ) external override returns (bytes4) { - ERC721(msg.sender).approve(from, sourceTokenId); - return IERC721Receiver.onERC721Received.selector; - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - (interfaceId == type(IERC4799).interfaceId || - interfaceId == type(IERC721Receiver).interfaceId); - } -} -``` -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.8.0 <0.9.0; - -import "./IERC4799.sol"; -import "./IERC4799NFT.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; - -contract DesignatedOwner { - function designatedOwnerOf( - IERC4799NFT tokenContract, - uint256 tokenId, - uint256 maxDepth - ) public view returns (address owner) { - owner = tokenContract.ownerOf(tokenId); - if (ERC165Checker.supportsInterface(owner, type(IERC4799).interfaceId)) { - require(maxDepth > 0, "designatedOwnerOf: depth limit exceeded"); - (tokenContract, tokenId) = IERC4799(owner).designatedTokenOf( - tokenContract, - tokenId - ); - return designatedOwnerOf(tokenContract, tokenId, maxDepth - 1); - } - } -} -``` - -## Security Considerations - -### Long/Cyclical Chains of Ownership - -The primary security concern is that of malicious actors creating excessively long or cyclical chains of ownership, leading applications that attempt to query for the designated owner of a given token to run out of gas and be unable to function. To address this, clients are expected to always query considering a `maxDepth` parameter, cutting off computation after a certain number of chain traversals. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4799.md diff --git a/EIPS/eip-4804.md b/EIPS/eip-4804.md index 4a86e99ee83a78..4ab94518ed0960 100644 --- a/EIPS/eip-4804.md +++ b/EIPS/eip-4804.md @@ -1,147 +1 @@ ---- -eip: 4804 -title: Web3 URL to EVM Call Message Translation -description: A translation of an HTTP-style Web3 URL to an EVM call message -author: Qi Zhou (@qizhou), Chao Pi (@pichaoqkc), Sam Wilson (@SamWilsn) -discussions-to: https://ethereum-magicians.org/t/eip-4804-web3-url-to-evm-call-message-translation/8300 -status: Final -type: Standards Track -category: ERC -created: 2022-02-14 -requires: 137 ---- - -## Abstract - -This standard translates an RFC 2396 URI like `web3://uniswap.eth/` to an EVM message such as: - -``` -EVMMessage { - To: 0xaabbccddee.... // where uniswap.eth's address registered at ENS - Calldata: 0x - ... -} -``` - -## Motivation - -Currently, reading data from Web3 generally relies on a translation done by a Web2 proxy to Web3 blockchain. The translation is mostly done by the proxies such as dApp websites/node service provider/etherscan, which are out of the control of users. The standard here aims to provide a simple way for Web2 users to directly access the content of Web3, especially on-chain Web contents such as SVG/HTML. Moreover, this standard enables interoperability with other standards already compatible with URIs, like SVG/HTML. - -## Specification - -This specification only defines read-only (i.e. Solidity's `view` functions) semantics. State modifying functions may be defined as a future extension. - -A Web3 URL is in the following form - -``` -web3URL = web3Schema [userinfo "@"] contractName [":" chainid] path ["?" query] -web3Schema = [ "ethereum-web3://" | "eth-web3://" | "web3://" ] -contractName = address | [name "." [ subDomain0 "." ... ]] nsProviderSuffix -path = ["/" method ["/" argument_0 ["/" argument_1 ... ]]] -argument = [type "!"] value -query = "attribute_1=value_1 [ "&" attribute_2=value_2 ... ] -attribute = "returns" | "returnTypes" | other_attribute -``` - -where - -- **web3Schema** indicates the schema of the URL, which is `web3://` or `w3://` for short. -- **userinfo** indicates which user is calling the EVM, i.e., "From" field in EVM call message. If not specified, the protocol will use 0x0 as the sender address. -- **contractName** indicates the contract to be called, i.e., "To" field in the EVM call message. If the **contractName** is an **address**, i.e., 0x + 20-byte-data hex, then "To" will be the address. Otherwise, the name is from a name service. In the second case, **nsProviderSuffix** will be the suffix from name service providers such as "eth", etc. The way to translate the name from a name service to an address will be discussed in later EIPs. -- **chainid** indicates which chain to resolve **contractName** and call the message. If not specified, the protocol will use the same chain as the name service provider, e.g., 1 for eth. If no name service provider is available, the default chainid is 1. -- **query** is an optional component containing a sequence of attribute-value pairs separated by "&". - -### Resolve Mode - -Once the "To" address and chainid are determined, the protocol will check the resolver mode of contract by calling "resolveMode" method. The protocol currently supports two resolve modes: - -#### Manual Mode - -The manual mode will not do any interpretation of **path** and **query**, and put **path** [ "?" **query** ] as the calldata of the message directly. - -#### Auto Mode - -The auto mode is the default mode to resolve (also applies when the "resolveMode" method is unavailable in the target contract). In the auto mode, if **path** is empty, then the protocol will call the target contract with empty calldata. Otherwise, the calldata of the EVM message will use standard Solidity contract ABI, where - -- **method** is a string of function method be called -- **argument_i** is the ith argument of the method. If **type** is specified, the value will be translated to the corresponding type. The protocol currently supports the basic types such as uint256, bytes32, address, bytes, and string. If **type** is not specified, then the type will be automatically detected using the following rule in a sequential way: - -1. **type**="uint256", if **value** is numeric; or -2. **type**="bytes32", if **value** is in the form of 0x+32-byte-data hex; or -3. **type**="address", if **value** is in the form of 0x+20-byte-data hex; or -4. **type**="bytes", if **value** is in the form of 0x followed by any number of bytes besides 20 or 32; or -5. else **type**="address" and parse the argument as a domain name in the form of `[name "." [ subDomain0 "." ... ]] nsProviderSuffix`. In this case, the actual value of the argument will be obtained from **nsProviderSuffix**, e.g., eth. If **nsProviderSuffix** is not supported, an unsupported NS provider error will be returned. - -Note that if **method** does not exist, i.e., **path** is empty or "/", then the contract will be called with empty calldata. - -- **returns** attribute in **query** tells the format of the returned data. If not specified, the returned message data will be parsed in "(bytes32)" and MIME will be set based on the suffix of the last argument. If **returns** is "()", the returned data will be parsed in raw bytes in JSON. Otherwise, the returned message will be parsed in the specified **returns** attribute in JSON. If multiple **returns** attributes are present, the value of the last **returns** attribute will be applied. Note that **returnTypes** is the alias of **returns**, but it is not recommended to use and is mainly for backward-compatible purpose. - -### Examples - -#### Example 1 - -``` -web3://w3url.eth/ -``` - -The protocol will find the address of **w3url.eth** from ENS in chainid 1 (Mainnet), and then the protocol will call the address with "From" = "0x..." and "Calldata" = "0x2F". - -#### Example 2 - -``` -web3://cyberbrokers-meta.eth/renderBroker/9999 -``` - -The protocol will find the address of **cyberbrokers-meta.eth** from ENS on chainid 1 (Mainnet), and then call the address with "To" = "0x..." and "Calldata" = "0x" + `keccak("view(uint256)")[0:4] + abi.encode(uint256(9999))`. - -#### Example 3 - -``` -web3://vitalikblog.eth:5/ -``` - -The protocol will find the address of **vitalikblog.eth** from ENS on chainid 5 (Goerli), and then call the address with "From" = "0x..." and "Calldata" = "0x2F" with chainid = 5. - -#### Example 4 - -``` -web3://0xe4ba0e245436b737468c206ab5c8f4950597ab7f:42170/ -``` - -The protocol will call the address with "To" = "0x9e081Df45E0D167636DB9C61C7ce719A58d82E3b" and "Calldata" = "0x" with chainid = 42170 (Arbitrum Nova). - -#### Example 5 - -``` -web3://0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/balanceOf/vitalik.eth?returns=(uint256) -``` - -The protocol will find the addresses of **vitalik.eth** from ENS on chainid 1 (Mainnet) and then call the method "balanceOf(address)" of the contract with the **charles.eth**'s address. The returned data will be parsed as uint256 like `[ "10000000000000" ]`. - -#### Example 6 - -``` -web3://0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/balanceOf/vitalik.eth?returns=() -``` - -The protocol will find the address of **vitalik.eth** from ENS on chainid 1 (Mainnet) and then call the method "balanceOf(address)" of the address. The returned data will be parsed as raw bytes like `["0x000000000000000000000000000000000000000000000000000009184e72a000"]`. - -## Rationale - -The purpose of the proposal is to add a decentralized presentation layer for Ethereum. With the layer, we are able to render any web content (including HTML/CSS/JPG/PNG/SVG, etc) on-chain using human-readable URLs, and thus EVM can be served as decentralized Backend. The design of the standard is based on the following principles: - -- **Human-readable**. The Web3 URL should be easily recognized by human similar to Web2 URL (`http://`). As a result, we support names from name services to replace address for better readability. In addition, instead of using calldata in hex, we use human-readable method + arguments and translate them to calldata for better readability. - -- **Maximum-Compatible with HTTP-URL standard**. The Web3 URL should be compatible with HTTP-URL standard including relative pathing, query, fragment, etc so that the support of existing HTTP-URL (e.g., by browser) can be easily extended to Web3 URL with minimal modification. This also means that existing Web2 users can easily migrate to Web3 with minimal extra knowledge of this standard. - -- **Simple**. Instead of providing explicit types in arguments, we use a "maximum likelihood" principle of auto-detecting the types of the arguments such as address, bytes32, and uint256. This could greatly minimize the length of URL, while avoiding confusion. In addition, explicit types are also supported to clear the confusion if necessary. - -- **Flexible**. The contract is able to override the encoding rule so that the contract has fine-control of understanding the actual Web resources that the users want to locate. - -## Security Considerations - -No security considerations were found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4804.md diff --git a/EIPS/eip-4824.md b/EIPS/eip-4824.md index c9dd0459c40d6b..0665ce9903e511 100644 --- a/EIPS/eip-4824.md +++ b/EIPS/eip-4824.md @@ -1,473 +1 @@ ---- -eip: 4824 -title: Common Interfaces for DAOs -description: An API for decentralized autonomous organizations (DAOs). -author: Joshua Tan (@thelastjosh), Isaac Patka (@ipatka), Ido Gershtein , Eyal Eithcowich , Michael Zargham (@mzargham), Sam Furter (@nivida) -discussions-to: https://ethereum-magicians.org/t/eip-4824-decentralized-autonomous-organizations/8362 -status: Draft -type: Standards Track -category: ERC -created: 2022-02-17 ---- - -## Abstract - -An API standard for decentralized autonomous organizations (DAOs), focused on relating on-chain and off-chain representations of membership and proposals. - -## Motivation - -DAOs, since being invoked in the Ethereum whitepaper, have been vaguely defined. This has led to a wide range of patterns but little standardization or interoperability between the frameworks and tools that have emerged. Standardization and interoperability are necessary to support a variety of use-cases. In particular, a standard daoURI, similar to tokenURI in [ERC-721](./eip-721), will enhance DAO discoverability, legibility, proposal simulation, and interoperability between tools. More consistent data across the ecosystem is also a prerequisite for future DAO standards. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Every contract implementing this EIP MUST implement the [ERC-4824](./eip-4824) interface below: - -```solidity -pragma solidity ^0.8.1; - -/// @title ERC-4824 DAOs -/// @dev See -interface IERC-4824 { - event DAOURIUpdate(address daoAddress, string daoURI); - - /// @notice A distinct Uniform Resource Identifier (URI) pointing to a JSON object following the "ERC-4824 DAO JSON-LD Schema". This JSON file splits into four URIs: membersURI, proposalsURI, activityLogURI, and governanceURI. The membersURI should point to a JSON file that conforms to the "ERC-4824 Members JSON-LD Schema". The proposalsURI should point to a JSON file that conforms to the "ERC-4824 Proposals JSON-LD Schema". The activityLogURI should point to a JSON file that conforms to the "ERC-4824 Activity Log JSON-LD Schema". The governanceURI should point to a flatfile, normatively a .md file. Each of the JSON files named above can be statically-hosted or dynamically-generated. - function daoURI() external view returns (string memory _daoURI); -} -``` - -The DAO JSON-LD Schema mentioned above: - -```json -{ - "@context": "http://www.daostar.org/schemas", - "type": "DAO", - "name": "", - "description": "", - "membersURI": "", - "proposalsURI": "", - "activityLogURI": "", - "governanceURI": "", - "contractsURI": "" -} -``` - -A DAO MAY inherit the above interface above or it MAY create an external registration contract that is compliant with this EIP. If a DAO creates an external registration contract, the registration contract MUST store the DAO’s primary address. - -If the DAO inherits the above interface, it SHOULD define a method for updating daoURI. If the DAO uses an external registration contract, the registration contract SHOULD contain some access control logic to enable efficient updating for daoURI. - -```solidity -pragma solidity ^0.8.1; - -/// @title ERC-4824 Common Interfaces for DAOs -/// @dev See -/// @title ERC-4824: DAO Registration -contract ERC-4824Registration is IERC-4824, AccessControl { - bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); - - string private _daoURI; - - address daoAddress; - - constructor() { - daoAddress = address(0xdead); - } - - /// @notice Set the initial DAO URI and offer manager role to an address - /// @dev Throws if initialized already - /// @param _daoAddress The primary address for a DAO - /// @param _manager The address of the URI manager - /// @param daoURI_ The URI which will resolve to the governance docs - function initialize( - address _daoAddress, - address _manager, - string memory daoURI_, - address _ERC-4824Index - ) external { - initialize(_daoAddress, daoURI_, _ERC-4824Index); - _grantRole(MANAGER_ROLE, _manager); - } - - /// @notice Set the initial DAO URI - /// @dev Throws if initialized already - /// @param _daoAddress The primary address for a DAO - /// @param daoURI_ The URI which will resolve to the governance docs - function initialize( - address _daoAddress, - string memory daoURI_, - address _ERC-4824Index - ) public { - if (daoAddress != address(0)) revert AlreadyInitialized(); - daoAddress = _daoAddress; - _setURI(daoURI_); - - _grantRole(DEFAULT_ADMIN_ROLE, _daoAddress); - _grantRole(MANAGER_ROLE, _daoAddress); - - ERC-4824Index(_ERC-4824Index).logRegistration(address(this)); - } - - /// @notice Update the URI for a DAO - /// @dev Throws if not called by dao or manager - /// @param daoURI_ The URI which will resolve to the governance docs - function setURI(string memory daoURI_) public onlyRole(MANAGER_ROLE) { - _setURI(daoURI_); - } - - function _setURI(string memory daoURI_) internal { - _daoURI = daoURI_; - emit DAOURIUpdate(daoAddress, daoURI_); - } - - function daoURI() external view returns (string memory daoURI_) { - return _daoURI; - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override returns (bool) { - return - interfaceId == type(IERC-4824).interfaceId || - super.supportsInterface(interfaceId); - } -} -``` - -### Indexing - -If a DAO inherits the ERC-4824 interface from a 4824-compliant DAO factory, then the DAO factory SHOULD incorporate a call to an indexer contract as part of the DAO's initialization to enable efficient network indexing. If the DAO is [ERC-165](./eip-165)-compliant, the factory can do this without additional permissions. If the DAO is _not_ compliant with ERC-165, the factory SHOULD first obtain access control rights to the indexer contract and then call logRegistration directly with the address of the new DAO and the daoURI of the new DAO. Note that any user, including the DAO itself, MAY call logRegistration and submit a registration for a DAO which inherits the ERC-4824 interface and which is also ERC-165-compliant. - -```solidity -pragma solidity ^0.8.1; - -error ERC-4824InterfaceNotSupported(); - -contract ERC-4824Index is AccessControl { - using ERC165Checker for address; - - bytes32 public constant REGISTRATION_ROLE = keccak256("REGISTRATION_ROLE"); - - event DAOURIRegistered(address daoAddress); - - constructor() { - _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); - _grantRole(REGISTRATION_ROLE, msg.sender); - } - - function logRegistrationPermissioned( - address daoAddress - ) external onlyRole(REGISTRATION_ROLE) { - emit DAOURIRegistered(daoAddress); - } - - function logRegistration(address daoAddress) external { - if (!daoAddress.supportsInterface(type(IERC-4824).interfaceId)) - revert ERC-4824InterfaceNotSupported(); - emit DAOURIRegistered(daoAddress); - } -} -``` - -If a DAO uses an external registration contract, the DAO SHOULD use a common registration factory contract linked to a common indexer to enable efficient network indexing. - -```solidity -pragma solidity ^0.8.1; - -/// @title ERC-4824 Common Interfaces for DAOs -/// @dev See - -contract CloneFactory { - // implementation of eip-1167 - see https://eips.ethereum.org/EIPS/eip-1167 - function createClone(address target) internal returns (address result) { - bytes20 targetBytes = bytes20(target); - assembly { - let clone := mload(0x40) - mstore( - clone, - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 - ) - mstore(add(clone, 0x14), targetBytes) - mstore( - add(clone, 0x28), - 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 - ) - result := create(0, clone, 0x37) - } - } -} - -contract ERC-4824RegistrationSummoner { - event NewRegistration( - address indexed daoAddress, - string daoURI, - address registration - ); - - address public ERC-4824Index; - address public template; /*Template contract to clone*/ - - constructor(address _template, address _ERC-4824Index) { - template = _template; - ERC-4824Index = _ERC-4824Index; - } - - function registrationAddress( - address by, - bytes32 salt - ) external view returns (address addr, bool exists) { - addr = Clones.predictDeterministicAddress( - template, - _saltedSalt(by, salt), - address(this) - ); - exists = addr.code.length > 0; - } - - function summonRegistration( - bytes32 salt, - string calldata daoURI_, - address manager, - address[] calldata contracts, - bytes[] calldata data - ) external returns (address registration, bytes[] memory results) { - registration = Clones.cloneDeterministic( - template, - _saltedSalt(msg.sender, salt) - ); - - if (manager == address(0)) { - ERC-4824Registration(registration).initialize( - msg.sender, - daoURI_, - ERC-4824Index - ); - } else { - ERC-4824Registration(registration).initialize( - msg.sender, - manager, - daoURI_, - ERC-4824Index - ); - } - - results = _callContracts(contracts, data); - - emit NewRegistration(msg.sender, daoURI_, registration); - } -``` - -### Members - -Members JSON-LD Schema. Every contract implementing this EIP SHOULD implement a membersuRI pointing to a JSON object satisfying this schema. - -```json -{ - "@context": "", - "type": "DAO", - "name": "", - "members": [ - { - "type": "EthereumAddress", - "id": "
" - }, - { - "type": "EthereumAddress", - "id": "
" - } - ] -} -``` - -### Proposals - -Proposals JSON-LD Schema. Every contract implementing this EIP SHOULD implement a proposalsURI pointing to a JSON object satisfying this schema. - -In particular, any on-chain proposal MUST be associated to an id of the form CAIP10_ADDRESS + “?proposalId=” + PROPOSAL_COUNTER, where CAIP10_ADDRESS is an address following the CAIP-10 standard and PROPOSAL_COUNTER is an arbitrary identifier such as a uint256 counter or a hash that is locally unique per CAIP-10 address. Off-chain proposals MAY use a similar id format where CAIP10_ADDRESS is replaced with an appropriate URI or URL. - -```json -{ - "@context": "http://www.daostar.org/schemas", - "type": "DAO", - "name": "", - "proposals": [ - { - "type": "proposal", - "id": "", - "name": "", - "contentURI": "", - "status": "", - "calls": [ - { - "type": "CallDataEVM", - "operation": "", - "from": "", - "to": "", - "value": "", - "data": "" - } - ] - } - ] -} -``` - -### Activity Log - -Activity Log JSON-LD Schema. Every contract implementing this EIP SHOULD implement a activityLogURI pointing to a JSON object satisfying this schema. - -```json -{ - "@context": "", - "type": "DAO", - "name": "", - "activities": [ - { - "id": "", - "type": "activity", - "proposal": { - "type": "proposal" - "id": "", - }, - "member": { - "type": "EthereumAddress", - "id": "
" - } - }, - ], - "activities": [ - { - "id": "", - "type": "activity", - "proposal": { - "type": "proposal" - "id": "", - }, - "member": { - "type": "EthereumAddress", - "id": "
" - } - } - ] -} -``` - -### Contracts - -Contracts JSON-LD Schema. Every contract implementing this EIP SHOULD implement a contractsURI pointing to a JSON object satisfying this schema. - -Further, every contractsURI SHOULD include at least the contract inheriting the ERC-4824 interface. - -``` -{ - "@context": "", - "type": "DAO", - "name": "", - "contracts": [ - { - "type": "EthereumAddress", - "id": "
", - "name": "", - "description": "" - }, - { - "type": "EthereumAddress", - "id": "
", - "name": "", - "description": "" - } - { - "type": "EthereumAddress", - "id": "
", - "name": "", - "description": "" - } - ] -} -``` - -## Rationale - -In this standard, we assume that all DAOs possess at least two primitives: _membership_ and _behavior_. _Membership_ is defined by a set of addresses. _Behavior_ is defined by a set of possible contract actions, including calls to external contracts and calls to internal functions. _Proposals_ relate membership and behavior; they are objects that members can interact with and which, if and when executed, become behaviors of the DAO. - -### APIs, URIs, and off-chain data - -DAOs themselves have a number of existing and emerging use-cases. But almost all DAOs need to publish data off-chain for a number of reasons: communicating to and recruiting members, coordinating activities, powering user interfaces and governance applications such as Snapshot or Tally, or enabling search and discovery via platforms like DeepDAO, Messari, and Etherscan. Having a standardized schema for this data organized across multiple URIs, i.e. an API specification, would strengthen existing use-cases for DAOs, help scale tooling and frameworks across the ecosystem, and build support for additional forms of interoperability. - -While we considered standardizing on-chain aspects of DAOs in this standard, particularly on-chain proposal objects and proposal IDs, we felt that this level of standardization was premature given (1) the relative immaturity of use-cases, such as multi-DAO proposals or master-minion contracts, that would benefit from such standardization, (2) the close linkage between proposal systems and governance, which we did not want to standardize (see “governanceURI”, below), and (3) the prevalence of off-chain and L2 voting and proposal systems in DAOs (see “proposalsURI”, below). Further, a standard URI interface is relatively easy to adopt and has been actively demanded by frameworks (see “Community Consensus”, below). - -### membersURI - -Approaches to membership vary widely in DAOs. Some DAOs and DAO frameworks (e.g. Gnosis Safe, Tribute), maintain an explicit, on-chain set of members, sometimes called owners or stewards. But many DAOs are structured so that membership status is based on the ownership of a token or tokens (e.g. Moloch, Compound, DAOstack, 1Hive Gardens). In these DAOs, computing the list of current members typically requires some form of off-chain indexing of events. - -In choosing to ask only for an (off-chain) JSON schema of members, we are trading off some on-chain functionality for more flexibility and efficiency. We expect different DAOs to use membersURI in different ways: to serve a static copy of on-chain membership data, to contextualize the on-chain data (e.g. many Gnosis Safe stewards would not say that they are the only members of the DAO), to serve consistent membership for a DAO composed of multiple contracts, or to point at an external service that computes the list, among many other possibilities. We also expect many DAO frameworks to offer a standard endpoint that computes this JSON file, and we provide a few examples of such endpoints in the implementation section. - -We encourage extensions of the Membership JSON-LD Schema, e.g. for DAOs that wish to create a state variable that captures active/inactive status or different membership levels. - -### proposalsURI - -Proposals have become a standard way for the members of a DAO to trigger on-chain actions, e.g. sending out tokens as part of grant or executing arbitrary code in an external contract. In practice, however, many DAOs are governed by off-chain decision-making systems on platforms such as Discourse, Discord, or Snapshot, where off-chain proposals may function as signaling mechanisms for an administrator or as a prerequisite for a later on-chain vote. (To be clear, on-chain votes may also serve as non-binding signaling mechanisms or as “binding” signals leading to some sort of off-chain execution.) The schema we propose is intended to support both on-chain and off-chain proposals, though DAOs themselves may choose to report only on-chain, only off-chain, or some custom mix of proposal types. - -**Proposal ID**. Every unique on-chain proposal MUST be associated to a proposal ID of the form CAIP10_ADDRESS + “?proposalId=” + PROPOSAL_COUNTER, where PROPOSAL_COUNTER is an arbitrary string which is unique per CAIP10_ADDRESS. Note that PROPOSAL_COUNTER may not be the same as the on-chain representation of the proposal; however, each PROPOSAL_COUNTER should be unique per CAIP10_ADDRESS, such that the proposal ID is a globally unique identifier. We endorse the CAIP-10 standard to support multi-chain / layer 2 proposals and the “?proposalId=” query syntax to suggest off-chain usage. - -**ContentURI**. In many cases, a proposal will have some (off-chain) content such as a forum post or a description on a voting platform which predates or accompanies the actual proposal. - -**Status**. Almost all proposals have a status or state, but the actual status is tied to the governance system, and there is no clear consensus between existing DAOs about what those statuses should be (see table below). Therefore, we have defined a “status” property with a generic, free text description field. - -| Project | Proposal Statuses | -| --- | --- | -| Aragon | Not specified | -| Colony | [‘Null’, ‘Staking’, ‘Submit’, ‘Reveal’, ‘Closed’, ‘Finalizable’, ‘Finalized’, ‘Failed’] | -| Compound | [‘Pending’, ‘Active’, ‘Canceled’, ‘Defeated’, ‘Succeeded’, ‘Queued’, ‘Expired’, ‘Executed’] | -| DAOstack/ Alchemy | [‘None’, ‘ExpiredInQueue’, ‘Executed’, ‘Queued’, ‘PreBoosted’, ‘Boosted’, ‘QuietEndingPeriod’] | -| Moloch v2 | [sponsored, processed, didPass, cancelled, whitelist, guildkick] | -| Tribute | [‘EXISTS’, ‘SPONSORED’, ‘PROCESSED’] | - -**ExecutionData**. For on-chain proposals with non-empty execution, we include an array field to expose the call data. The main use-case for this data is execution simulation of proposals. - -### activityLogURI - -The activity log JSON is intended to capture the interplay between a member of a DAO and a given proposal. Examples of activities include the creation/submission of a proposal, voting on a proposal, disputing a proposal, and so on. - -_Alternatives we considered: history, interactions_ - -### governanceURI - -Membership, to be meaningful, usually implies rights and affordances of some sort, e.g. the right to vote on proposals, the right to ragequit, the right to veto proposals, and so on. But many rights and affordances of membership are realized off-chain (e.g. right to vote on a Snapshot, gated access to a Discord). Instead of trying to standardize these wide-ranging practices or forcing DAOs to locate descriptions of those rights on-chain, we believe that a flatfile represents the easiest and most widely-acceptable mechanism for communicating what membership means and how proposals work. These flatfiles can then be consumed by services such as Etherscan, supporting DAO discoverability and legibility. - -We chose the word “governance” as an appropriate word that reflects (1) the widespread use of the word in the DAO ecosystem and (2) the common practice of emitting a governance.md file in open-source software projects. - -_Alternative names considered: description, readme, constitution_ - -### contractsURI - -Over the course of community conversations, multiple parties raised the need to report on, audit, and index the different contracts belonging to a given DAO. Some of these contracts are deployed as part of the modular design of a single DAO framework, e.g. the core, voting, and timelock contracts within Open Zeppelin / Compound Governor. In other cases, a DAO might deploy multiple multsigs as treasuries and/or multiple subDAOs that are effectively controlled by the DAO. ContractsURI offers a generic way of declaring these many instruments. - -_Alternative names considered: contractsRegistry, contractsList_ - -### Why JSON-LD - -We chose to use JSON-LD rather than the more widespread and simpler JSON standard because (1) we want to support use-cases where a DAO wants to include members using some other form of identification than their Ethereum address and (2) we want this standard to be compatible with future multi-chain standards. Either use-case would require us to implement a context and type for addresses, which is already implemented in JSON-LD. - -Further, given the emergence of patterns such as subDAOs and DAOs of DAOs in large organizations such as Synthetix, as well as L2 and multi-chain use-cases, we expect some organizations will point multiple DAOs to the same URI, which would then serve as a gateway to data from multiple contracts and services. The choice of JSON-LD allows for easier extension and management of that data. - -### **Community Consensus** - -The initial draft standard was developed as part of the DAOstar One roundtable series, which included representatives from all major EVM-based DAO frameworks (Aragon, Compound, DAOstack, Gnosis, Moloch, OpenZeppelin, and Tribute), a wide selection of DAO tooling developers, as well as several major DAOs. Thank you to all the participants of the roundtable. We would especially like to thank Auryn Macmillan, Fabien of Snapshot, Selim Imoberdorf, Lucia Korpas, and Mehdi Salehi for their contributions. - -In-person events will be held at Schelling Point 2022 and at ETHDenver 2022, where we hope to receive more comments from the community. We also plan to schedule a series of community calls through early 2022. - -## Backwards Compatibility - -Existing contracts that do not wish to use this specification are unaffected. DAOs that wish to adopt the standard without updating or migrating contracts can do so via an external registration contract. - -## Security Considerations - -This standard defines the interfaces for the DAO URIs but does not specify the rules under which the URIs are set, or how the data is prepared. Developers implementing this standard should consider how to update this data in a way aligned with the DAO’s governance model, and keep the data fresh in a way that minimizes reliance on centralized service providers. - -Indexers that rely on the data returned by the URI should take caution if DAOs return executable code from the URIs. This executable code might be intended to get the freshest information on membership, proposals, and activity log, but it could also be used to run unrelated tasks. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4824.md diff --git a/EIPS/eip-4834.md b/EIPS/eip-4834.md index 2f86485726c0bd..bc98f2e61bd8d7 100644 --- a/EIPS/eip-4834.md +++ b/EIPS/eip-4834.md @@ -1,229 +1 @@ ---- -eip: 4834 -title: Hierarchical Domains -description: Extremely generic name resolution -author: Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/erc-4834-hierarchical-domains-standard/8388 -status: Final -type: Standards Track -category: ERC -created: 2022-02-22 ---- - -## Abstract - -This is a standard for generic name resolution with arbitrarily complex access control and resolution. It permits a contract that implements this EIP (referred to as a "domain" hereafter) to be addressable with a more human-friendly name, with a similar purpose to [ERC-137](./eip-137.md) (also known as "ENS"). - -## Motivation - -The advantage of this EIP over existing standards is that it provides a minimal interface that supports name resolution, adds standardized access control, and has a simple architecture. ENS, although useful, has a comparatively complex architecture and does not have standard access control. - -In addition, all domains (including subdomains, TLDs, and even the root itself) are actually implemented as domains, meaning that name resolution is a simple iterative algorithm, not unlike DNS itself. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Contract Interface - -```solidity -interface IDomain { - /// @notice Query if a domain has a subdomain with a given name - /// @param name The subdomain to query, in right to left order - /// @return `true` if the domain has a subdomain with the given name, `false` otherwise - function hasDomain(string[] memory name) external view returns (bool); - - /// @notice Fetch the subdomain with a given name - /// @dev This should revert if `hasDomain(name)` is `false` - /// @param name The subdomain to fetch, in right to left order - /// @return The subdomain with the given name - function getDomain(string[] memory name) external view returns (address); -} -``` - -### Name Resolution - -To resolve a name (like `"a.b.c"`), split it by the delimiter (resulting in something like `["a", "b", "c"]`). Set `domain` initially to the root domain, and `path` to be an empty list. - -Pop off the last element of the array (`"c"`) and add it to the path, then call `domain.hasDomain(path)`. If it's `false`, then the domain resolution fails. Otherwise, set the domain to `domain.getDomain(path)`. Repeat until the list of split segments is empty. - -There is no limit to the amount of nesting that is possible. For example, `0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z` would be valid if the root contains `z`, and `z` contains `y`, and so on. - -Here is a solidity function that resolves a name: - -```solidity -function resolve(string[] calldata splitName, IDomain root) public view returns (address) { - IDomain current = root; - string[] memory path = []; - for (uint i = splitName.length - 1; i >= 0; i--) { - // Append to back of list - path.push(splitName[i]); - // Require that the current domain has a domain - require(current.hasDomain(path), "Name resolution failed"); - // Resolve subdomain - current = current.getDomain(path); - } - return current; -} -``` - -### Optional Extension: Registerable - -```solidity -interface IDomainRegisterable is IDomain { - //// Events - - /// @notice Must be emitted when a new subdomain is created (e.g. through `createDomain`) - /// @param sender msg.sender for createDomain - /// @param name name for createDomain - /// @param subdomain subdomain in createDomain - event SubdomainCreate(address indexed sender, string name, address subdomain); - - /// @notice Must be emitted when the resolved address for a domain is changed (e.g. with `setDomain`) - /// @param sender msg.sender for setDomain - /// @param name name for setDomain - /// @param subdomain subdomain in setDomain - /// @param oldSubdomain the old subdomain - event SubdomainUpdate(address indexed sender, string name, address subdomain, address oldSubdomain); - - /// @notice Must be emitted when a domain is unmapped (e.g. with `deleteDomain`) - /// @param sender msg.sender for deleteDomain - /// @param name name for deleteDomain - /// @param subdomain the old subdomain - event SubdomainDelete(address indexed sender, string name, address subdomain); - - //// CRUD - - /// @notice Create a subdomain with a given name - /// @dev This should revert if `canCreateDomain(msg.sender, name, pointer)` is `false` or if the domain exists - /// @param name The subdomain name to be created - /// @param subdomain The subdomain to create - function createDomain(string memory name, address subdomain) external payable; - - /// @notice Update a subdomain with a given name - /// @dev This should revert if `canSetDomain(msg.sender, name, pointer)` is `false` of if the domain doesn't exist - /// @param name The subdomain name to be updated - /// @param subdomain The subdomain to set - function setDomain(string memory name, address subdomain) external; - - /// @notice Delete the subdomain with a given name - /// @dev This should revert if the domain doesn't exist or if `canDeleteDomain(msg.sender, name)` is `false` - /// @param name The subdomain to delete - function deleteDomain(string memory name) external; - - - //// Parent Domain Access Control - - /// @notice Get if an account can create a subdomain with a given name - /// @dev This must return `false` if `hasDomain(name)` is `true`. - /// @param updater The account that may or may not be able to create/update a subdomain - /// @param name The subdomain name that would be created/updated - /// @param subdomain The subdomain that would be set - /// @return Whether an account can update or create the subdomain - function canCreateDomain(address updater, string memory name, address subdomain) external view returns (bool); - - /// @notice Get if an account can update or create a subdomain with a given name - /// @dev This must return `false` if `hasDomain(name)` is `false`. - /// If `getDomain(name)` is also a domain implementing the subdomain access control extension, this should return `false` if `getDomain(name).canMoveSubdomain(msg.sender, this, subdomain)` is `false`. - /// @param updater The account that may or may not be able to create/update a subdomain - /// @param name The subdomain name that would be created/updated - /// @param subdomain The subdomain that would be set - /// @return Whether an account can update or create the subdomain - function canSetDomain(address updater, string memory name, address subdomain) external view returns (bool); - - /// @notice Get if an account can delete the subdomain with a given name - /// @dev This must return `false` if `hasDomain(name)` is `false`. - /// If `getDomain(name)` is a domain implementing the subdomain access control extension, this should return `false` if `getDomain(name).canDeleteSubdomain(msg.sender, this, subdomain)` is `false`. - /// @param updater The account that may or may not be able to delete a subdomain - /// @param name The subdomain to delete - /// @return Whether an account can delete the subdomain - function canDeleteDomain(address updater, string memory name) external view returns (bool); -} -``` - -### Optional Extension: Enumerable - -```solidity -interface IDomainEnumerable is IDomain { - /// @notice Query all subdomains. Must revert if the number of domains is unknown or infinite. - /// @return The subdomain with the given index. - function subdomainByIndex(uint256 index) external view returns (string memory); - - /// @notice Get the total number of subdomains. Must revert if the number of domains is unknown or infinite. - /// @return The total number of subdomains. - function totalSubdomains() external view returns (uint256); -} -``` - -### Optional Extension: Access Control - -```solidity -interface IDomainAccessControl is IDomain { - /// @notice Get if an account can move the subdomain away from the current domain - /// @dev May be called by `canSetDomain` of the parent domain - implement access control here!!! - /// @param updater The account that may be moving the subdomain - /// @param name The subdomain name - /// @param parent The parent domain - /// @param newSubdomain The domain that will be set next - /// @return Whether an account can update the subdomain - function canMoveSubdomain(address updater, string memory name, IDomain parent, address newSubdomain) external view returns (bool); - - /// @notice Get if an account can unset this domain as a subdomain - /// @dev May be called by `canDeleteDomain` of the parent domain - implement access control here!!! - /// @param updater The account that may or may not be able to delete a subdomain - /// @param name The subdomain to delete - /// @param parent The parent domain - /// @return Whether an account can delete the subdomain - function canDeleteSubdomain(address updater, string memory name, IDomain parent) external view returns (bool); -} -``` - -## Rationale - -This EIP's goal, as mentioned in the abstract, is to have a simple interface for resolving names. Here are a few design decisions and why they were made: - -- Name resolution algorithm - - Unlike ENS's resolution algorithm, this EIP's name resolution is fully under the control of the contracts along the resolution path. - - This behavior is more intuitive to users. - - This behavior allows for greater flexibility - e.g. a contract that changes what it resolves to based on the time of day. -- Parent domain access control - - A simple "ownable" interface was not used because this specification was designed to be as generic as possible. If an ownable implementation is desired, it can be implemented. - - This also gives parent domains the ability to call subdomains' access control methods so that subdomains, too, can choose whatever access control mechanism they desire -- Subdomain access control - - These methods are included so that subdomains aren't always limited to their parent domain's access control - - The root domain can be controlled by a DAO with a non-transferable token with equal shares, a TLD can be controlled by a DAO with a token representing stake, a domain of that TLD can be controlled by a single owner, a subdomain of that domain can be controlled by a single owner linked to an NFT, and so on. - - Subdomain access control functions are suggestions: an ownable domain might implement an owner override, so that perhaps subdomains might be recovered if the keys are lost. - -## Backwards Compatibility - -This EIP is general enough to support ENS, but ENS is not general enough to support this EIP. - -## Security Considerations - -### Malicious canMoveSubdomain (Black Hole) - -#### Description: Malicious `canMoveSubdomain` - -Moving a subdomain using `setDomain` is a potentially dangerous operation. - -Depending on the parent domain's implementation, if a malicious new subdomain unexpectedly returns `false` on `canMoveSubdomain`, that subdomain can effectively lock the ownership of the domain. - -Alternatively, it might return `true` when it isn't expected (i.e. a backdoor), allowing the contract owner to take over the domain. - -#### Mitigation: Malicious `canMoveSubdomain` - -Clients should help by warning if `canMoveSubdomain` or `canDeleteSubdomain` for the new subdomain changes to `false`. It is important to note, however, that since these are functions, it is possible for the value to change depending on whether or not it has already been linked. It is also still possible for it to unexpectedly return true. It is therefore recommended to **always** audit the new subdomain's source code before calling `setDomain`. - -### Parent Domain Resolution - -#### Description: Parent Domain Resolution - -Parent domains have full control of name resolution for their subdomains. If a particular domain is linked to `a.b.c`, then `b.c` can, depending on its code, set `a.b.c` to any domain, and `c` can set `b.c` itself to any domain. - -#### Mitigation: Parent Domain Resolution - -Before acquiring a domain that has been pre-linked, it is recommended to always have the contract **and** all the parents up to the root audited. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4834.md diff --git a/EIPS/eip-4883.md b/EIPS/eip-4883.md index 8f3d999d114d4c..bb63896a2233ce 100644 --- a/EIPS/eip-4883.md +++ b/EIPS/eip-4883.md @@ -1,71 +1 @@ ---- -eip: 4883 -title: Composable SVG NFT -description: Compose an SVG NFT by concatenating the SVG with the rendered SVG of another NFT. -author: Andrew B Coathup (@abcoathup), Alex (@AlexPartyPanda), Damian Martinelli (@damianmarti), blockdev (@0xbok), Austin Griffith (@austintgriffith) -discussions-to: https://ethereum-magicians.org/t/eip-4883-composable-svg-nft/8765 -status: Draft -type: Standards Track -category: ERC -created: 2022-03-08 -requires: 165, 721 ---- - -## Abstract - -Compose an SVG (Scalable Vector Graphics) NFT by concatenating the SVG with the SVG of another NFT rendered as a string for a specific token ID. - -## Motivation - -Onchain SVG NFTs allow for NFTs to be entirely onchain by returning artwork as SVG in a data URI of the `tokenUri` function. Composability allows onchain SVG NFTs to be crafted. e.g. adding glasses & hat NFTs to a profile pic NFT or a fish NFT to a fish tank NFT. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title EIP-4883 Non-Fungible Token Standard -interface IERC4883 is IERC165 { - function renderTokenById(uint256 id) external view returns (string memory); -} -``` - -`renderTokenById` must return the SVG body for the specified token `id` and must either be an empty string or valid SVG element(s). - -## Rationale - -SVG elements can be string concatenated to compose an SVG. - -### Ordering of concatenation - -SVG uses a "painters model" of rendering. - -**Scalable Vector Graphics (SVG) 1.1 (Second Edition)**, section: **3.3 Rendering Order** ->Elements in an SVG document fragment have an implicit drawing order, with the first elements in the SVG document fragment getting "painted" first. Subsequent elements are painted on top of previously painted elements. - -The ordering of the SVG concatenation determines the drawing order rather than any concept of a z-index. - -This EIP only specifies the rendering of the rendered SVG NFT and does not require any specific ordering when composing. This allows the SVG NFT to use a rendered SVG NFT as a foreground or a background as required. - -### Alternatives to concatenation - -SVG specifies a `link` tag. Linking could allow for complex SVGs to be composed but would require creating a URI format and then getting ecosystem adoption. As string concatenation of SVG's is already supported, the simpler approach of concatenation is used. - -### Sizing - -This EIP doesn't specify any requirements on the size of the rendered SVG. Any scaling based on sizing can be performed by the SVG NFT as required. - -### Render function name - -The render function is named `renderTokenById` as this function name was first used by Loogies and allows existing deployed NFTs to be compatible with this EIP. - -## Backwards Compatibility -This EIP has no backwards compatibility concerns - - -## Security Considerations - -- SVG uses a "painters model" of rendering. A rendered SVG body could be added and completely obscure the existing SVG NFT artwork. -- SVG is XML and can contain malicious content, and while it won't impact the contract, it could impact the use of the SVG. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4883.md diff --git a/EIPS/eip-4885.md b/EIPS/eip-4885.md index ce7959ecb5d8b5..8c649b9c7c7c55 100644 --- a/EIPS/eip-4885.md +++ b/EIPS/eip-4885.md @@ -1,201 +1 @@ ---- -eip: 4885 -title: Subscription NFTs and Multi Tokens -description: An interface for subscription tokens that gives holders subscriptions to NFTs and multi tokens -author: Jules Lai (@julesl23) -discussions-to: https://ethereum-magicians.org/t/eip-subscription-token-standard/8531 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-03-08 -requires: 165, 721, 1155 ---- - -## Abstract - -The following standard allows for the implementation of a standard API for subscribing to non-fungible and multi tokens. [EIP-20](./eip-20.md) tokens are deposited in exchange for subscription tokens that give the right to use said non-fungible and multi tokens for a specified time limited or unlimited period. - -## Motivation - -This standard offers a flexible, general purpose way to subscribe to the use of assets or services offered by [EIP-721](./eip-721.md) or [EIP-1155](./eip-1155.md) contracts. From here on in, for the sake of simplicity, these contracts will be known as NFTs; the provider is the issuer of said NFTs and the subscriber(s) uses them. - -This proposal was originally conceived from the want to give creators of music and film, back control. The distribution and delivery of digital content is currently the purview of centralised tech corporations who offer homogeneous subscription models to their customers. This proposal specifies a standard for dapp developers to give creators the ability to set their own custom subscription models and hence, open up new revenue streams that can lead to decentralised distribution and delivery models. - -Use cases include any sort of periodic (e.g. daily, weekly, monthly, quarterly, yearly/annual, or seasonal) use of or access to assets or services such as: - -- Subscriptions for streaming music, video, e-learning or book/news services -- Sharing of digital assets among subscribers -- Club memberships such as health clubs -- Season tickets for sports and e-sports -- Agreement between parties to exchange fixed rate subscription stream with variable income in DeFi -- Renting in-game assets -- Etc. - -The subscription token borrows a few functions from the EIP-20 specification. An implementer is free to implement the rest of the standard; allowing for example subscription tokens to be transferred in secondary markets, sent as gifts or for refunds etc. - -## Specification - -The subscriber deposits EIP-20 to receive an NFT and subscription. Subscription tokens balance automatically decreases linearly over the lifetime of usage of the NFT, and use of the NFT is disabled once the subscription token balance falls to zero. The subscriber can top up the balance to extend the lifetime of the subscription by depositing EIP-20 tokens in exchange for more subscription tokens. - -Smart contracts implementing this EIP standard MUST implement the [EIP-165](./eip-165.md) supportsInterface function and MUST return the constant value true if 0xC1A48422 is passed through the interfaceID argument. Note that revert in this document MAY mean a require, throw (not recommended as depreciated) or revert solidity statement with or without error messages. - -```solidity -interface ISubscriptionToken { - /** - @dev This emits when the subscription token constructor or initialize method is - executed. - @param name The name of the subscription token - @param symbol The symbol of the subscription token - @param provider The provider of the subscription whom receives the deposits - @param subscriptionToken The subscription token contract address - @param baseToken The ERC-20 compatible token to use for the deposits. - @param nft Address of the `nft` contract that the provider mints/transfers from. - All tokenIds referred to in this interface MUST be token instances of this `nft` contract. - */ - event InitializeSubscriptionToken( - string name, - string symbol, - address provider, - address indexed subscriptionToken, - address indexed baseToken, - address indexed nft, - string uri - ); - - /** - @dev This emits for every new subscriber to `nft` contract of token `tokenId`. - `subscriber` MUST have received `nft` of token `tokenId` in their account. - @param subscriber The subscriber account - @param tokenId MUST be token id of `nft` sent to `subscriber` - @param uri MUST be uri of the `nft` that was sent to `subscriber` or empty string - */ - event SubscribeToNFT( - address indexed subscriber, - uint256 indexed tokenId, - string uri - ); - - /** - @dev Emits when `subscriber` deposits ERC-20 of token type `baseToken` via the `deposit method. - This tops up `subscriber` balance of subscription tokens - @param depositAmount The amount of ERC-20 of type `baseToken` deposited - @param subscriptionTokenAmount The amount of subscription tokens sent in exchange to `subscriber` - @param subscriptionPeriod Amount of additional time in seconds subscription is extended - */ - event Deposit( - address indexed subscriber, - uint256 indexed tokenId, - uint256 depositAmount, - uint256 subscriptionTokenAmount, - uint256 subscriptionPeriod - ); - - /** - @return The name of the subscription token - */ - function name() external view returns (string memory); - - /** - @return The symbol of the subscription token - */ - function symbol() external view returns (string memory); - - /** - @notice Subscribes `subscriber` to `nft` of 'tokenId'. `subscriber` MUST receive `nft` - of token `tokenId` in their account. - @dev MUST revert if `subscriber` is already subscribed to `nft` of 'tokenId' - MUST revert if 'nft' has not approved the `subscriptionToken` contract address as operator. - @param subscriber The subscriber account. MUST revert if zero address. - @param tokenId MUST be token id of `nft` contract sent to `subscriber` - `tokenId` emitted from event `SubscribeToNFT` MUST be the same as tokenId except when - tokenId is zero; allows OPTIONAL tokenid that is then set internally and minted by - `nft` contract - @param uri The OPTIONAL uri of the `nft`. - `uri` emitted from event `SubscribeToNFT` MUST be the same as uri except when uri is empty. - */ - function subscribeToNFT( - address subscriber, - uint256 tokenId, - string memory uri - ) external; - - /** - @notice Top up balance of subscription tokens held by `subscriber` - @dev MUST revert if `subscriber` is not subscribed to `nft` of 'tokenId' - MUST revert if 'nft' has not approved the `subscriptionToken` contract address as operator. - @param subscriber The subscriber account. MUST revert if zero address. - @param tokenId The token id of `nft` contract to subscribe to - @param depositAmount The amount of ERC-20 token of contract address `baseToken` to deposit - in exchange for subscription tokens of contract address `subscriptionToken` - */ - function deposit( - address subscriber, - uint256 tokenId, - uint256 depositAmount - ) external payable; - - /** - @return The balance of subscription tokens held by `subscriber`. - RECOMMENDED that the balance decreases linearly to zero for time limited subscriptions - RECOMMENDED that the balance remains the same for life long subscriptions - MUST return zero balance if the `subscriber` does not hold `nft` of 'tokenId' - MUST revert if subscription has not yet started via the `deposit` function - When the balance is zero, the use of `nft` of `tokenId` MUST NOT be allowed for `subscriber` - */ - function balanceOf(address subscriber) external view returns (uint256); -} -``` - -### Subscription token balances - -An example implementation mints an amount of subscription token that totals to one subscription token per day of the subscription period length paid for by the subscriber; for example a week would be for seven subscription tokens. The subscription token balance then decreases automatically at a rate of one token per day continuously and linearly over time until zero. The `balanceOf` function can be implemented lazily by calculating the amount of subscription tokens left only when it is called as a view function, thus has no gas cost. - -### Subscription token price - -Subscription token price paid per token per second can be calculated from the `Deposit` event parameters as -`depositAmount` / (`subscriptionTokenAmount` \* `subscriptionPeriod`) - -### NFT metadata - -The NFT's metadata can store information of the asset/service offered to the subscriber by the provider for the duration of the subscription. This MAY be the terms and conditions of the agreed subscription service offered by the provider to the subscriber. It MAY also be the metadata of the NFT asset if this is offered directly. This standard is kept purposely general to cater for many different use cases of NFTs. - -### Subscription expiry - -When the subscription token balance falls to zero for a subscriber (signifying that the subscription has expired) then it is up to the implementer on how to handle this for their particular use case. For example, a provider may stop streaming media service to a subscriber. For an NFT that represents an image stored off-chain, perhaps the NFT's `uri` function no longer returns back a link to its metadata. - -### Caveats - -With some traditional subscription models based on fiat currencies, the subscribers' saved payment credentials are used to automatically purchase to extend the subscription period, at or just before expiry. This feature is not possible in this proposal specification as recurring payments will have to have allowance approved for signed by a subscriber for each payment when using purely cryptocurrencies. - -This proposal does not deal with pausing subscriptions directly, implementers can write their own or inherit off 3rd party smart contract abstractions such as OpenZeppelin's Pausable. In that case, `balanceOf` method would need extra logic and storage to account for the length of time the subscription tokens were paused. - -## Rationale - -### Tokenisation of subscriptions - -The subscription itself has value when it is exchanged for a deposit. This proposal enables subscriptions to be 'tokenised' thus secondary markets can exist where the subscription tokens can be bought and sold. For example, a fan might want to sell their season ticket, that gives access to live sporting events, on to another fan. This would not be as easily possible if there was only a date expiry extension feature added to NFTs. -An implementer can simply implement the rest of the EIP-20 functions for subscription tokens to be traded. It is left to the implementer to decide if the subscription service offered is non-fungible or fungible. If non-fungible then buying the subscription tokens would simply give the same period left to expiration. If fungible and the purchaser already had an existing subscription for the same service then their total subscription period can be extended by the amount of subscription tokens bought. - -### Cater for current and future uses of NFTs - -This proposal purposely keeps `tokenId` and `uri` optional in the `subcribeToNFT` method to keep the specification general purpose. Some use cases such as pre-computed image NFT collections don't require a different 'uri', just a different `tokenId` for each NFT. However, in other use cases such as those that require legal contracts between both parties, individual `uri` links are probably required as the NFT's metadata may require information from both parties to be stored on immutable storage. - -### Giving back users control - -Traditional subscription models, particularly with streaming services, control of the subscription model is totally with that of the central service provider. This proposal gives decentralised services a standard way to give control back to their users. Hence each user is able to develop their own subscription eco system and administer it towards one that suits theirs and their subscribers' needs. - -## Backwards Compatibility - -A subscription token contract can be fully compatible with EIP-20 specification to allow, for example, transfers from one subscriber to another subscriber or user. EIP-20 methods `name`, `symbol` and `balanceOf` are already part of the specification of this proposal, and it is left to the implementer to choose whether to implement the rest of EIP-20's interface by considering their own use case. - -Use of subscription tokens is in effect an indirect way to control the lifetime of an NFT. As such it is assumed that this arrangement would work best when the NFTs and subscription token contracts subscribing to the NFTs, are deployed by the same platform or decentralised app. It MUST NOT have an impact or dependencies to existing NFTs that have not approved the subscription token as an operator. Indeed in this case, any other parties wouldn't be aware of and any NFT lifetime dependencies will be ignored, hence should not work anyway. To this end, this proposal specifies that the 'nft' MUST have approved the `subscriptionToken` contract address as operator. - -## Security Considerations - -It is normal for service providers to receive subscriber payments upfront before the subscriber gets to use the service. Indeed this proposal via the `deposit` method follows this remit. It would therefore be possible that a service provider sets up, receives the deposits and then does not provide or provides the service poorly to its subscribers. This happens in the traditional world too and this proposal does not cover how to resolve this. - -The `subscribeToNFT` method takes a parameter `uri` link to the `nft` metadata. It is possible if stored on centralised storage that the owners can change the metadata, or perhaps the metadata is hacked which is an issue with vanilla NFT contracts too. But because the `uri` is provided at the time of subscription rather then deployment, it is RECOMMENDED that where the use case requires, implementers ensure that the `uri` link is to immutable storage. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4885.md diff --git a/EIPS/eip-4886.md b/EIPS/eip-4886.md index c9800feaf3b5f7..63e9702ce0aa0c 100644 --- a/EIPS/eip-4886.md +++ b/EIPS/eip-4886.md @@ -1,295 +1 @@ ---- -eip: 4886 -title: Proxy Ownership Register -description: A proxy ownership register allowing trustless proof of ownership between Ethereum addresses, with delegated asset delivery -author: Omnus Sunmo (@omnus) -discussions-to: https://ethereum-magicians.org/t/eip-4886-a-proxy-ownership-and-asset-delivery-register/8559 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-03 ---- - -## Abstract - -A proxy protocol that allows users to nominate a proxy address to act on behalf of another wallet address, together with a delivery address for new assets. Smart contracts and applications making use of the protocol can take a proxy address and lookup holding information for the nominator address. This has a number of practical applications, including allowing users to store valuable assets safely in a cold wallet and interact with smart contracts using a proxy address of low value. The assets in the nominator are protected as all contract interactions take place with the proxy address. This eliminates a number of exploits seen recently where users' assets are drained through a malicious contract interaction. In addition, the register holds a delivery address, allowing new assets to be delivered directly to a cold wallet address. - -## Motivation - -To make full use of Ethereum users often need to prove their ownership of existing assets. For example: - * Discord communities require users to sign a message with their wallet to prove they hold the tokens or NFTs of that community. - * Whitelist events (for example recent airdrops, or NFT mints), require the user to interact using a given address to prove eligibility. - * Voting in DAOs and other protocols require the user to sign using the address that holds the relevant assets. - - There are more examples, with the unifying theme being that the user must make use of the address with the assets to derive the platform benefit. This means the addresses holding these assets cannot be truly 'cold', and is a gift to malicious developers seeking to steal valuable assets. For example, a new project can offer free NFTs to holders of an existing NFT asset. The existing holders have to prove ownership by minting from the wallet with the asset that determined eligibility. This presents numerous possible attack vectors for a malicious developer who knows that all users interacting with the contract have an asset of that type. - - Possibly even more damaging is the effect on user confidence across the whole ecosystem. Users become reluctant to interact with apps and smart contracts for fear of putting their assets at risk. They may also decide not to store assets in cold wallet addresses as they need to prove they own them on a regular basis. A pertinent example is the user trying to decide whether to 'vault' their NFT and lose access to a discord channel, or keep their NFT in another wallet, or even to connect their 'vault' to discord. - - Ethereum is amazing at providing trustless proofs. The *only* time a user should need to interact using the wallet that holds an asset is if they intend to sell or transfer that asset. If a user merely wishes to prove ownership (to access a resource, get an airdrop, mint an NFT, or vote in a DAO), they should do this through a trustless proof stored on-chain. - - Furthermore, users should be able to decide where new assets are delivered, rather than them being delivered to the wallet providing the interaction. This allows hot wallets to acquire assets sent directly to a cold wallet 'vault', possibly even the one they are representing in terms of asset ownership. - - The aim of this EIP is to provide a convenient method to avoid this security concern and empower more people to feel confident leveraging the full scope of Ethereum functionality. Our vision is an Ethereum where users setup a new hardware wallet for assets they wish to hold long-term, then make one single contract interaction with that wallet: to nominate a hot wallet proxy. That user can always prove they own assets on that address, and they can specify it as a delivery address for new asset delivery. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Definitions - - * Delivery address: The address that assets will be delivered to for the current Proxy Record, i.e. a new NFT minted by the Proxy address, representing the Nominator address, should be delivered to the Delivery address. - * Nomination: Where a Nominator has nominated a Proxy address. Will only be active when the Proxy has accepted the nomination. - * Nominator address: The address that proposes a proxy relationship. This address nominates another address to act as its proxy, representing it and its holdings in all interactions. - * Proxy address: The address that will represent a Nominator on-chain. - * Proxy Record: An active proxy relationship encompassing a Nominator, Proxy and Delivery. - * Register: The main EPS contract, which holds details of both Nominations and Proxy Records. - -### EPS Specification - -There are two main parts to the register - a nomination and a proxy record: - - Contract / Dapp Register - - Nominator: 0x1234.. Nominator: 0x1234.. - Proxy: 0x5678.. ---------> Proxy: 0x4567.. - Delivery: 0x9876.. - -The first step to creating a proxy record is for an address to nominate another address as its proxy. This creates a nomination that maps the nominator (the address making the nomination) to the proposed proxy address. - -This is not a proxy record on the register at this stage, as the proxy address needs to first accept the nomination. Until the nomination is accepted it can be considered to be pending. Once the proxy address has accepted the nomination a proxy record is added to the register. - -When accepting a nomination the proxy address sets the delivery address for that proxy record. The proxy address remains in control of updating that delivery address as required. Both the nominator and proxy can delete the proxy record and nomination at any time. The proxy will continue forever if not deleted - it is eternal. - -The register is a single smart contract that stores all nomination and register records. The information held for each is as follows: - * Nomination: - * The address of the Nominator - * The address of the Proposed Proxy - -* Proxy Record: - * The address of the Nominator - * The address of the Proxy - * The delivery address for proxied deliveries - -Any address can act as a Nominator or a Proxy. A Nomination must have been made first in order for an address to accept acting as a Proxy. - -A Nomination cannot be made to an address that is already active as either a Proxy or a Nominator, i.e. that address is already in an active proxy relationship. - -The information for both Nominations and Proxy records is held as a mapping. For the Nomination this is address => address for the Nominator to the Proxy address. For the Proxy Record the mapping is from address => struct for the Proxy Address to a struct containing the Nominator and Delivery address. - -Mapping between an address and its Nominator and Delivery address is a simple process as shown below: - - Contract / Dapp Register - - | | - |------------- 0x4567..---------------> | - | | - | <-------nominator: 0x1234..---------- | - | delivery: 0x9876.. | - | | - -The protocol is fully backwards compatible. If it is passed an address that does not have an active mapping it will pass back the received address as both the Nominator and Delivery address, thereby preserving functionality as the address is acting on its own behalf. - - Contract / Dapp Register - - | | - |------------- 0x0222..---------------> | - | | - | <-------nominator: 0x0222..---------- | - | delivery: 0x0222.. | - | | - -If the EPS register is passed the address of a Nominator it will revert. This is of vital importance. The purpose of the proxy is that the Proxy address is operating on behalf of the Nominator. The Proxy address therefore can derive the same benefits as the Nominator (for example discord roles based on the Nominator's holdings, or mint NFTs that require another NFT to be held). It is therefore imperative that the Nominator in an active proxy cannot also interact and derive these benefits, otherwise two addresses represent the same holding. A Nominator can of course delete the Proxy Record at any time and interact on it's own behalf, with the Proxy address instantly losing any benefits associated with the proxy relationship. - -### Solidity Interface Definition - -**Nomination Exists** - - function nominationExists(address _nominator) external view returns (bool); - -Returns true if a Nomination exists for the address specified. - -**Nomination Exists for Caller** - - function nominationExistsForCaller() external view returns (bool); - -Returns true if a Nomination exists for the msg.sender. - -**Proxy Record Exists** - - function proxyRecordExists(address _proxy) external view returns (bool); - -Returns true if a Proxy Record exists for the passed Proxy address. - -**Proxy Record Exists for Caller** - - function proxyRecordExistsForCaller() external view returns (bool); - -Returns true if a Proxy Record exists for the msg.sender. - -**Nominator Record Exists** - - function nominatorRecordExists(address _nominator) external view returns (bool); - -Returns true if a Proxy Record exists for the passed Nominator address. - -**Nominator Record Exists for Caller** - - function nominatorRecordExistsForCaller() external view returns (bool); - -Returns true if a Proxy Record exists for the msg.sender. - -**Get Proxy Record** - - function getProxyRecord(address _proxy) external view returns (address nominator, address proxy, address delivery); - -Returns Nominator, Proxy and Delivery address for a passed Proxy address. - -**Get Proxy Record for Caller** - - function getProxyRecordForCaller() external view returns (address nominator, address proxy, address delivery); - -Returns Nominator, Proxy and Delivery address for msg.sender as Proxy address. - -**Get Nominator Record** - - function getNominatorRecord(address _nominator) external view returns (address nominator, address proxy, address delivery); - -Returns Nominator, Proxy and Delivery address for a passed Nominator address. - -**Get Nominator Record for Caller** - - function getNominatorRecordForCaller() external view returns (address nominator, address proxy, address delivery); - -Returns Nominator, Proxy and Delivery address for msg.sender address as Nominator. - -**Address Is Active** - - function addressIsActive(address _receivedAddress) external view returns (bool); - -Returns true if the passed address is Nominator or Proxy address on an active Proxy Record. - -**Address Is Active for Caller** - - function addressIsActiveForCaller() external view returns (bool); - -Returns true if msg.sender is Nominator or Proxy address on an active Proxy Record. - -**Get Nomination** - -function getNomination(address _nominator) external view returns (address proxy); - -Returns the proxy address for a Nomination when passed a Nominator. - -**Get Nomination for Caller** - -function getNominationForCaller() external view returns (address proxy); - -Returns the proxy address for a Nomination if msg.sender is a Nominator - -**Get Addresses** - - function getAddresses(address _receivedAddress) external view returns (address nominator, address delivery, bool isProxied); - -Returns the Nominator, Proxy, Delivery and a boolean isProxied for the passed address. If you pass an address that is not a Proxy address it will return address(0) for the Nominator, Proxy and Delivery address and isProxied of false. If you pass an address that is a Proxy address it will return the relvant addresses and isProxied of true. - -**Get Addresses for Caller** - - function getAddressesForCaller() external view returns (address nominator, address delivery, bool isProxied); - -Returns the Nominator, Proxy, Delivery and a boolean isProxied for msg.sender. If msg.sender is not a Proxy address it will return address(0) for the Nominator, Proxy and Delivery address and isProxied of false. If msg.sender is a Proxy address it will return the relvant addresses and isProxied of true. - -**Get Role** - - function getRole(address _roleAddress) external view returns (string memory currentRole); - -Returns a string value with a role for the passed address. Possible roles are: - -None The address does not appear on the Register as either a Record or a Nomination. - -Nominator - Pending The address is the Nominator on a Nomination which has yet to be accepted by the nominated Proxy address. - -Nominator - Active The address is a Nominator on an active Proxy Record (i.e. the Nomination has been accepted). - -Proxy - Active The address is a Proxy on an active Proxy Record. - -**Get Role for Caller** - - function getRoleForCaller() external view returns (string memory currentRole); - -Returns a string value with a role for msg.sender. Possible roles are: - -None The msg.sender does not appear on the Register as either a Record or a Nomination. - -Nominator - Pending The msg.sender is the Nominator on a Nomination which has yet to be accepted by the nominated Proxy address. - -Nominator - Active The msg.sender is a Nominator on an active Proxy Record (i.e. the Nomination has been accepted). - -Proxy - Active The msg.sender is a Proxy on an active Proxy Record. - -**Make Nomination** - - function makeNomination(address _proxy, uint256 _provider) external payable; - -Can be passed a Proxy address to create a Nomination for the msg.sender. - -Provider is a required argument. If you do not have a Provider ID you can pass 0 as the default EPS provider. For details on the EPS Provider Program please see . - -**Accept Nomination** - - function acceptNomination(address _nominator, address _delivery, uint256 _provider) external; - -Can be passed a Nominator and Delivery address to accept a Nomination for a msg.sender. Note that to accept a Nomination the Nomination needs to exists with the msg.sender as the Proxy. The Nominator passed to the function and that on the Nomination must match. - -Provider is a required argument. If you do not have a Provider ID you can pass 0 as the default EPS provider. For details on the EPS Provider Program please see . - -**Update Delivery Record** - - function updateDeliveryAddress(address _delivery, uint256 _provider) external; - -Can be passed a new Delivery address where the msg.sender is the Proxy on a Proxy Record. - -Provider is a required argument. If you do not have a Provider ID you can pass 0 as the default EPS provider. For details on the EPS Provider Program please see . - -**Delete Record by Nominator** - - function deleteRecordByNominator(uint256 _provider) external; - -Can be called to delete a Record and Nomination when the msg.sender is a Nominator. - -Note that when both a Record and Nomination exist both are deleted. If no Record exists (i.e. the Nomination hasn't been accepted by the Proxy address) the Nomination is deleted. - -Provider is a required argument. If you do not have a Provider ID you can pass 0 as the default EPS provider. For details on the EPS Provider Program please see . - -**Delete Record by Proxy** - - function deleteRecordByProxy(uint256 _provider) external; - -Can be called to delete a Record and Nomination when the msg.sender is a Proxy. - -## Rationale - -The rationale for this EIP was to provide a way for all existing and future Ethereum assets to be have a 'beneficial owner' (the proxy) that is different to the address custodying the asset. The use of a register to achieve this ensures that changes to existing tokens are not required. The register stores a trustless proof, signed by both the nominator and proxy, that can be relied upon as a true representation of asset ownership. - -## Backwards Compatibility - -The EIP is fully backwards compatible. - -## Test Cases - -The full SDLC for this proposal has been completed and it is operation at 0xfa3D2d059E9c0d348dB185B32581ded8E8243924 on mainnet, ropsten and rinkeby. The contract source code is validated and available on etherscan. The full unit test suite is available in `../assets/eip-4886/`, as is the source code and example implementations. - -## Reference Implementation - -Please see `../assets/eip-4886/contracts` - -## Security Considerations - -The core intention of the EIP is to improve user security by better safeguarding assets and allowing greater use of cold wallet storage. - -Potential negative security implications have been considered and none are envisaged. The proxy record can only become operational when a nomination has been confirmed by a proxy address, both addresses therefore having provided signed proof. - -From a usability perspective the key risk is in users specifying the incorrect asset delivery address, though it is noted that this burden of accuracy is no different to that currently on the network. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4886.md diff --git a/EIPS/eip-4906.md b/EIPS/eip-4906.md index 93e6492058a451..c4af83e488644c 100644 --- a/EIPS/eip-4906.md +++ b/EIPS/eip-4906.md @@ -1,93 +1 @@ ---- -eip: 4906 -title: EIP-721 Metadata Update Extension -description: Add a MetadataUpdate event to EIP-721. -author: Anders (@0xanders), Lance (@LanceSnow), Shrug , Nathan -discussions-to: https://ethereum-magicians.org/t/eip4906-erc-721-erc-1155-metadata-update-extension/8588 -status: Final -type: Standards Track -category: ERC -created: 2022-03-13 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [EIP-721](./eip-721.md). It adds a `MetadataUpdate` event to EIP-721 tokens. - -## Motivation - -Many [EIP-721](./eip-721.md) contracts emit an event when one of its tokens' metadata are changed. While tracking changes based on these different events is possible, it is an extra effort for third-party platforms, such as an NFT marketplace, to build individualized solutions for each NFT collection. - -Having a standard `MetadataUpdate` event will make it easy for third-party platforms to timely update the metadata of many NFTs. - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -The **metadata update extension** is OPTIONAL for EIP-721 contracts. - - -```solidity -/// @title EIP-721 Metadata Update Extension -interface IERC4906 is IERC165, IERC721 { - /// @dev This event emits when the metadata of a token is changed. - /// So that the third-party platforms such as NFT market could - /// timely update the images and related attributes of the NFT. - event MetadataUpdate(uint256 _tokenId); - - /// @dev This event emits when the metadata of a range of tokens is changed. - /// So that the third-party platforms such as NFT market could - /// timely update the images and related attributes of the NFTs. - event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); -} -``` - -The `MetadataUpdate` or `BatchMetadataUpdate` event MUST be emitted when the JSON metadata of a token, or a consecutive range of tokens, is changed. - -Not emitting `MetadataUpdate` event is RECOMMENDED when a token is minted. - -Not emitting `MetadataUpdate` event is RECOMMENDED when a token is burned. - -Not emitting `MetadataUpdate` event is RECOMMENDED when the tokenURI changes but the JSON metadata does not. - -The `supportsInterface` method MUST return `true` when called with `0x49064906`. - -## Rationale - -Different NFTs have different metadata, and metadata generally has multiple fields. `bytes data` could be used to represents the modified value of metadata. It is difficult for third-party platforms to identify various types of `bytes data`, so as to avoid unnecessary complexity, arbitrary metadata is not included in the `MetadataUpdate` event. - -After capturing the `MetadataUpdate` event, a third party can update the metadata with information returned from the `tokenURI(uint256 _tokenId)` of EIP-721. When a range of token ids is specified, the third party can query each token URI individually. - -## Backwards Compatibility - -No backwards compatibility issues were found - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC4906.sol"; - -contract ERC4906 is ERC721, IERC4906 { - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) { - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return interfaceId == bytes4(0x49064906) || super.supportsInterface(interfaceId); - } -} -``` - -## Security Considerations - -If there is an off-chain modification of metadata, a method that triggers `MetadataUpdate` can be added, but ensure that the function's permission controls are correct. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4906.md diff --git a/EIPS/eip-4907.md b/EIPS/eip-4907.md index ed02d6cc76169a..d8d4b360ea005e 100644 --- a/EIPS/eip-4907.md +++ b/EIPS/eip-4907.md @@ -1,249 +1 @@ ---- -eip: 4907 -title: Rental NFT, an Extension of EIP-721 -description: Add a time-limited role with restricted permissions to EIP-721 tokens. -author: Anders (@0xanders), Lance (@LanceSnow), Shrug -discussions-to: https://ethereum-magicians.org/t/idea-erc-721-user-and-expires-extension/8572 -status: Final -type: Standards Track -category: ERC -created: 2022-03-11 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [EIP-721](./eip-721.md). It proposes an additional role (`user`) which can be granted to addresses, and a time where the role is automatically revoked (`expires`). The `user` role represents permission to "use" the NFT, but not the ability to transfer it or set users. - -## Motivation - -Some NFTs have certain utilities. For example, virtual land can be "used" to build scenes, and NFTs representing game assets can be "used" in-game. In some cases, the owner and user may not always be the same. There may be an owner of the NFT that rents it out to a “user”. The actions that a “user” should be able to take with an NFT would be different from the “owner” (for instance, “users” usually shouldn’t be able to sell ownership of the NFT).  In these situations, it makes sense to have separate roles that identify whether an address represents an “owner” or a “user” and manage permissions to perform actions accordingly. - -Some projects already use this design scheme under different names such as “operator” or “controller” but as it becomes more and more prevalent, we need a unified standard to facilitate collaboration amongst all applications. - -Furthermore, applications of this model (such as renting) often demand that user addresses have only temporary access to using the NFT. Normally, this means the owner needs to submit two on-chain transactions, one to list a new address as the new user role at the start of the duration and one to reclaim the user role at the end. This is inefficient in both labor and gas and so an “expires” function is introduced that would facilitate the automatic end of a usage term without the need of a second transaction. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Contract Interface -Solidity Interface with NatSpec & OpenZeppelin v4 Interfaces (also available at [`IERC4907.sol`](../assets/eip-4907/contracts/IERC4907.sol)): - -```solidity -interface IERC4907 { - - // Logged when the user of an NFT is changed or expires is changed - /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed - /// The zero address for user indicates that there is no user address - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires); - - /// @notice set the user and expires of an NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - function setUser(uint256 tokenId, address user, uint64 expires) external; - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId) external view returns(address); - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) external view returns(uint256); -} -``` - -The `userOf(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `userExpires(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `setUser(uint256 tokenId, address user, uint64 expires)` function MAY be implemented as `public` or `external`. - -The `UpdateUser` event MUST be emitted when a user address is changed or the user expires is changed. - -The `supportsInterface` method MUST return `true` when called with `0xad092b5c`. - -## Rationale - -This model is intended to facilitate easy implementation. Here are some of the problems that are solved by this standard: - -### Clear Rights Assignment - -With Dual “owner” and “user” roles, it becomes significantly easier to manage what lenders and borrowers can and cannot do with the NFT (in other words, their rights). Additionally, owners can control who the user is and it’s easy for other projects to assign their own rights to either the owners or the users. - -### Simple On-chain Time Management - -Once a rental period is over, the user role needs to be reset and the “user” has to lose access to the right to use the NFT. This is usually accomplished with a second on-chain transaction but that is gas inefficient and can lead to complications because it’s imprecise. With the `expires` function, there is no need for another transaction because the “user” is invalidated automatically after the duration is over. - -### Easy Third-Party Integration - -In the spirit of permission less interoperability, this standard makes it easier for third-party protocols to manage NFT usage rights without permission from the NFT issuer or the NFT application. Once a project has adopted the additional `user` role and `expires`, any other project can directly interact with these features and implement their own type of transaction. For example, a PFP NFT using this standard can be integrated into both a rental platform where users can rent the NFT for 30 days AND, at the same time, a mortgage platform where users can use the NFT while eventually buying ownership of the NFT with installment payments. This would all be done without needing the permission of the original PFP project. - -## Backwards Compatibility - -As mentioned in the specifications section, this standard can be fully EIP-721 compatible by adding an extension function set. - -In addition, new functions introduced in this standard have many similarities with the existing functions in EIP-721. This allows developers to easily adopt the standard quickly. - -## Test Cases - -### Test Contract -`ERC4907Demo` Implementation: [`ERC4907Demo.sol`](../assets/eip-4907/contracts/ERC4907Demo.sol) - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC4907.sol"; - -contract ERC4907Demo is ERC4907 { - - constructor(string memory name, string memory symbol) - ERC4907(name,symbol) - { - } - - function mint(uint256 tokenId, address to) public { - _mint(to, tokenId); - } - -} -``` - -### Test Code -[test.js](../assets/eip-4907/test/test.js) - -```JavaScript -const { assert } = require("chai"); - -const ERC4907Demo = artifacts.require("ERC4907Demo"); - -contract("test", async accounts => { - - it("should set user to Bob", async () => { - // Get initial balances of first and second account. - const Alice = accounts[0]; - const Bob = accounts[1]; - - const instance = await ERC4907Demo.deployed("T", "T"); - const demo = instance; - - await demo.mint(1, Alice); - let expires = Math.floor(new Date().getTime()/1000) + 1000; - await demo.setUser(1, Bob, BigInt(expires)); - - let user_1 = await demo.userOf(1); - - assert.equal( - user_1, - Bob, - "User of NFT 1 should be Bob" - ); - - let owner_1 = await demo.ownerOf(1); - assert.equal( - owner_1, - Alice , - "Owner of NFT 1 should be Alice" - ); - }); -}); - - -``` - -run in Terminal: -``` -truffle test ./test/test.js -``` - -## Reference Implementation -Implementation: [`ERC4907.sol`](../assets/eip-4907/contracts/ERC4907.sol) -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC4907.sol"; - -contract ERC4907 is ERC721, IERC4907 { - struct UserInfo - { - address user; // address of user role - uint64 expires; // unix timestamp, user expires - } - - mapping (uint256 => UserInfo) internal _users; - - constructor(string memory name_, string memory symbol_) - ERC721(name_, symbol_) - { - } - - /// @notice set the user and expires of an NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - function setUser(uint256 tokenId, address user, uint64 expires) public virtual{ - require(_isApprovedOrOwner(msg.sender, tokenId), "ERC4907: transfer caller is not owner nor approved"); - UserInfo storage info = _users[tokenId]; - info.user = user; - info.expires = expires; - emit UpdateUser(tokenId, user, expires); - } - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId) public view virtual returns(address){ - if( uint256(_users[tokenId].expires) >= block.timestamp){ - return _users[tokenId].user; - } - else{ - return address(0); - } - } - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) public view virtual returns(uint256){ - return _users[tokenId].expires; - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC4907).interfaceId || super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId); - - if (from != to && _users[tokenId].user != address(0)) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0); - } - } -} -``` - -## Security Considerations - -This EIP standard can completely protect the rights of the owner, the owner can change the NFT user and expires at any time. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4907.md diff --git a/EIPS/eip-4910.md b/EIPS/eip-4910.md index bd02bf70f7a21d..a0fa148aeb86d1 100644 --- a/EIPS/eip-4910.md +++ b/EIPS/eip-4910.md @@ -1,746 +1 @@ ---- -eip: 4910 -title: Royalty Bearing NFTs -description: Extension of ERC-721 to correctly define, process, and pay (hierarchical) onchain NFT royalties. -author: Andreas Freund (@Therecanbeonlyone1969) -discussions-to: https://ethereum-magicians.org/t/royalty-bearing-nfts/8453 -status: Final -type: Standards Track -category: ERC -created: 2022-03-14 -requires: 165, 721 ---- - -## Abstract - -The proposal directly connects NFTs and royalties in a smart contract architecture extending the [ERC-721](./eip-721.md) standard, with the aim of precluding central authorities from manipulating or circumventing payments to those who are legally entitled to them. - -The proposal builds upon the OpenZeppelin Smart Contract Toolbox architecture, and extends it to include royalty account management (CRUD), royalty balance and payments management, simple trading capabilities -- Listing/De-Listing/Buying -- and capabilities to trace trading on exchanges. The royalty management capabilities allow for hierarchical royalty structures, referred to herein as royalty trees, to be established by logically connecting a "parent" NFT to its "children", and recursively enabling NFT "children" to have more children. - -## Motivation - -The management of royalties is an age-old problem characterized by complex contracts, opaque management, plenty of cheating and fraud. - -The above is especially true for a hierarchy of royalties, where one or more assets is derived from an original asset such as a print from an original painting, or a song is used in the creation of another song, or distribution rights and compensation are managed through a series of affiliates. - -In the example below, the artist who created the original is eligible to receive proceeds from every sale, and resale, of a print. - -![Fig1](../assets/eip-4910/eip-4910-print-families.png) - -The basic concept for hierarchical royalties utilizing the above "ancestry concept" is demonstrated in the figure below. - -![Fig2](../assets/eip-4910/eip-4910-royalties.png) - - -In order to solve for the complicated inheritance problem, this proposal breaks down the recursive problem of the hierarchy tree of depth N into N separate problems, one for each layer. This allows us to traverse the tree from its lowest level upwards to its root most efficiently. - -This affords creators, and the distributors of art derived from the original, the opportunity to achieve passive income from the creative process, enhancing the value of an NFT, since it now not only has intrinsic value but also comes with an attached cash flow. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Outline - -This proposal introduces several new concepts as extensions to the ERC-721 standard that warrant explanation: - -* **Royalty Account (RA)** - * A Royalty Account is attached to each NFT through its `tokenId` and consists of several sub-accounts which can be accounts of individuals or other RAs. A Royalty Account is identified by an account identifier. -* **Account Type** - * This specifies if an RA Sub Account belongs to an individual (user) or is another RA. If there is another RA as an RA Sub Account, the allocated balance needs to be reallocated to the Sub Accounts making up the referenced RA. -* **Royalty Split** - * The percentage each Sub Account receives based on a sale of an NFT that is associated with an RA -* **Royalty Balance** - * The royalty balance associated with an RA -* **Sub Account Royalty Balance** - * The royalty balance associated to each RA Sub Account. Note that only individual accounts can carry a balance that can be paid out. That means that if an RA Sub Account is an RA, its final Sub Account balance must be zero, since all RA balances must be allocated to individual accounts. -* **Token Type** - * Token Type is given as either ETH or the symbol of the supported utility tokens such as `DAI` -* **Asset ID** - * This is the `tokenId` the RA belongs to. -* **Parent** - * This indicates which `tokenId` is the immediate parent of the `tokenId` to which an RA belongs. - -Below a non-normative overview is given of the data structures and functionality that are covered by the requirements in this document. - -#### Data Structures - -In order to create an interconnected data structure linking NFTs to RAs certain global data structures are required: - -* A Royalty Account and associated Royalty Sub Accounts to establish the concept of a Royalty Account with sub accounts. -* Connecting a `tokenId` to a Royalty Account identifier. -* A structure mapping parent-to-child NFT relationships. -* A listing of token types and last validated balance (for trading and royalty payment purposes) -* A listing of registered payments to be made in the `executePayment` function and validated in `safeTransferFrom`. This is sufficient, because a payment once received and distributed in the `safeTransferFrom` function will be removed from the listing. -* A listing of NFTs to be sold - -#### Royalty Account Functions - -Definitions and interfaces for the Royalty Account RUD (Read-Update-Delete) functions. Because the RA is created in the minting function, there is no need to have a function to create a royalty account separately. - -#### Minting of a Royalty Bearing NFT - -When an NFT is minted, an RA must be created and associated with the NFT and the NFT owner, and, if there is an ancestor, with the ancestor's RA. To this end the specification utilizes the `_safemint` function in a newly defined `mint` function and applies various business rules on the input variables. - -#### Listing NFTs for Sale and removing a Listing - -Authorized user addresses can list NFTs for sale for non-exchange mediated NFT purchases. - -#### Payment Function from Buyer to Seller - -To avoid royalty circumvention, a buyer will always pay the NFT contract directly and not the seller. The seller is paid through the royalty distribution and can later request a payout. - -The payment process depends on whether the payment is received in ETH or an [ERC-20](./eip-20.md) token: - -* ERC-20 Token - 1. The Buyer must `approve` the NFT contract for the purchase price, `payment` for the selected payment token (ERC-20 contract address). - 2. For an ERC-20 payment token, the Buyer must then call the `executePayment` in the NFT contract -- the ERC-20 is not directly involved. -* For a non-ERC-20 payment, the Buyer must send a protocol token (ETH) to the NFT contract, and is required to send `msg.data` encoded as an array of purchased NFTs `uint256[] tokenId`. - -#### Modified NFT Transfer Function including required Trade data to allocate Royalties - -The input parameters must satisfy several requirements for the NFT to be transferred AFTER the royalties have been properly distributed. Furthermore, the ability to transfer more than one token at a time is also considered. - -The proposal defines: - -* Input parameter validation -* Payment Parameter Validation -* Distributing Royalties -* Update Royalty Account ownership with payout -* Transferring Ownership of the NFT -* Removing the Payment entry in `registeredPayment` after successful transfer - -Lastly, the approach to distributing royalties is to break down the hierarchical structure of interconnected Royalty Accounts into layers and then process one layer at time, where each relationship between a token and its ancestor is utilized to traverse the Royalty Account chain until the root ancestor and associated RA is reached. - -#### Paying out Royalties to the NFT Owner -- `from` address in `safeTransferFrom` Function - -This is the final part of the proposal. - -There are two versions of the payout function -- a `public` function and an `internal` function. - -The public function has the following interface: - -``` -function royaltyPayOut (uint256 tokenId, address RAsubaccount, address payable payoutAccount, uint256 amount) public virtual nonReentrant returns (bool) -``` - -where we only need the `tokenId`, the RA Sub Account address, `_RAsubaccount` which is the `owner`, and the amount to be paid out, `_amount`. Note that the function has `nonReentrant` modifier protection, because funds are being payed out. - -To finally send a Payout payment, the following steps need to be taken: - -* find the RA Sub Account based on `RAaccount` and the `subaccountPos` and extract the balance -* extract `tokenType` from the Sub Account -* based on the token type, send the payout payment (not exceeding the available balance) - -### Data Structures - -#### Royalty Account and Royalty Sub Accounts - -In order to create an interconnected data structure linking NFTs to RAs that is search optimized requires to make the following additions to the global data structures of an ERC-721. - -Note, a Royalty Account is defined as a collection of Royalty Sub Accounts linked to a meta account. This meta account is comprised of general account identifiers particular to the NFT it is linked to such as asset identifier, parent identifier etc. - -**[R1]** *One or more Royalty Sub-Account MUST be linked to a Royalty Account.* - -**[R2]** *The account identifier of a Royalty Account, `raAccountId`, MUST be unique.* - -**[R3]** *The `tokenId` of a NFT MUST be linked to a `raAccountID` in order to connect an `raAccountId` to a `tokenId`.* - - -#### Print (Child) NFTs - -The set of requirement to manage Parent-Child NFT Relationships and constraints at each level of the NFT (family) tree e.g. number of children permitted, NFT parents have to be linked to their immediate NFT children are as follows. - -**[R4]** *There MUST be a link for direct parent-child relationships* - -#### NFT Payment Tokens - -In order to capture royalties, an NFT contract must be involved in NFT trading. Therefore, the NFT contract needs to be aware of NFT payments, which in turn requires the NFT contract to be aware which tokens can be used for trading. - -**[R5]** *There MUST be a listing of supported token types* - -Since the NFT contract is managing royalty distributions and payouts as well as sales, it needs to track the last available balances of the allowed token types owned by the contract. - -**[R6]** *There MUST be a link of the last validated balance of an allowed token type in the contract to the respective allowed token contract.* - -#### NFT Listings and Payments - -Since the contract is directly involved in the sales process, a capability to list one or more NFTs for sale is required. - -**[R7]** *There MUST be a list of NFTs for sale.* - -**[R8]** *A sales listing MUST have a unique identifier.* - -Besides listings, the contract is required to manage sales as well. This requires the capability to register a payment, either for immediate execution or for later payment such as in an auction situation. - -**[R9]** *There MUST be a listing for registered payments* - -**[R10]** *A registered payment MUST have a unique identifier.* - -#### Contract Constructor and Global Variables and their update functions - -This standard extends the current ERC-721 constructor, and adds several global variables to recognize the special role of the creator of an NFT, and the fact that the contract is now directly involved in managing sales and royalties. - -**[R11]** *The minimal contract constructor MUST contain the following input elements.* - -``` -/// -/// @dev Definition of the contract constructor -/// -/// @param name as in ERC-721 -/// @param symbol as in ERC-721 -/// @param baseTokenURI as in ERC-721 -/// @param allowedTokenTypes is the array of allowed tokens for payment - -constructor( - string memory name, - string memory symbol, - string memory baseTokenURI, - address[] memory allowedTokenTypes - ) ERC721(name, symbol) {...} -``` - - -### Royalty Account Management - -Below are the definitions and interfaces for the Royalty Account RUD (Read-Update-Delete) functions. Since a Royalty Account is created in the NFT minting function, there is no need to have a separate function to create a royalty account. - -#### Get a Royalty Account - -There is only one get function required because a Royalty Account and its sub accounts can be retrieved through the `tokenId` in the `ancestry` field of the Royalty Account. - -**[R12]** *The `getRoyaltyAccount` function interface MUST adhere to the definition below:* - -``` -/// @dev Function to fetch a Royalty Account for a given tokenId -/// @param tokenId is the identifier of the NFT to which a Royalty Account is attached -/// @param RoyaltyAccount is a data structure containing the royalty account information -/// @param RASubAccount[] is an array of data structures containing the information of the royalty sub accounts associated with the royalty account - -function getRoyaltyAccount (uint256 tokenId) public view virtual returns (address, - RoyaltyAccount memory, - RASubAccount[] memory); -``` - - -**[R13]** *The following business rules MUST be enforced in the `getRoyaltyAccount` function:* - -* *`tokenId` exists and is not burned* - -#### Update a Royalty Account - -In order to update a Royalty Account, the caller must have both the 'tokenId' and the `RoyaltyAccount` itself which can be obtained from the Royalty Account getter function. - - -**[R14]** *The `updateRoyaltyAccount` function interface MUST adhere to the definition below:* - -``` -/// @dev Function to update a Royalty Account and its Sub Accounts -/// @param tokenId is the identifier of the NFT to which the Royalty Account to be updated is attached -/// @param RoyaltyAccount is the Royalty Account and associated Royalty Sub Accounts with updated values - -function updateRoyaltyAccount (uint256 _tokenId, `RoyaltyAccount memory _raAccount) public virtual returns (bool) -``` - -The update functionality of a Royalty Account, while straightforward, is also highly nuanced. To avoid complicated change control rules such as multi-signature rules, Royalty Account changes are kept simple. - -**[R15]** *The business rules for the update function are as follows:* - -1. *An NFTs asset identifier MUST NOT be changed.* -2. *An NFTs ancestor MUST NOT be updated.* -3. *An NFTs token type accepted for payment MUST NOT be updated.* -4. *The royalty balance in a Royalty Sub Account MUST NOT be changed.* -5. *The royalty split inherited by the children from the NFT parent MUST NOT be changed.* -6. *New royalty split values MUST be larger than, or less than, or equal to any established boundary value for royalty splits, if it exists.* -7. *The number of existing Royalty Sub Account plus the number of new Royalty Sub Accounts to be added MUST be smaller or equal to an established boundary value, if it exists.* -8. *The sum of all royalty splits across all existing and new Royalty Sub Accounts MUST equal to 1 or its equivalent numerical value at all times.* -9. *'msg.sender` MUST be equal to an account identifier in the Royalty Sub Account of the Royalty Account to be modified and that royalty sub account must be identified as not belonging to the parent NFT* - - 9.1 *the Sub Account belonging to the account identifier MUST NOT be removed* - - 9.2 *A royalty split MUST only be decreased, and either the existing sub account's royalty split MUST be increased accordingly such that the sum of all royalty splits remains equal to 1 or its numerical equivalent, or one or more new Royalty Sub Accounts MUST be added according to rule 10.* - - 9.3 *a royalty balance MUST NOT be changed* - - 9.4 *an account identifier MUST NOT be NULL* - -10. *If `msg.sender` is equal to the account identifier of one of the Sub Account owners which is not the parent NFT, an additional Royalty Sub Accounts MAY be added* - - 10.1 *if the royalty split of the Royalty Sub Account belonging to `msg.sender` is reduced* - - * then the royalty balance in each new Royalty Sub Account MUST be zero - - * and the sum of the new royalty splits data MUST be equal to the royalty split of the Royalty Sub Account of `msg.sender` before it was modified - - 10.2 *new account identifier MUST not be NULL* - -11. *If the Royalty Account update is correct, the function returns `true`, otherwise `false`.* - -#### Deleting a Royalty Account - -While sometimes deleting a Royalty Account is necessary, even convenient, it is a very costly function in terms of gas, and should not be used unless one is absolutely sure that the conditions enumerated below are met. - -**[R16]** *The `deleteRoyaltyAccount` function interface MUST adhere to the definition below:* - -``` -/// @dev Function to delete a Royalty Account -/// @param tokenId is the identifier of the NFT to which the Royalty Account to be updated is attached - -function deleteRoyaltyAccount (uint256 _tokenId) public virtual returns (bool) -``` - -**[R17]** *The business rules for this function are as follows:* - -* *`_tokenId` MUST be burned, i.e., have owner `address(0)`.* -* *all `tokenId` numbers genealogically related to `_tokenId` either as ancestors or offspring MUST also be burnt.* -* *all balances in the Royalty Sub Accounts MUST be zero.* - -### NFT Minting - -In extension to the ERC-721 minting capability, a Royalty Account with Royalty Sub Accounts are required to be added during the minting, besides establishing the NFT token specific data structures supporting constraints such as the maximum number of children an NFT can have. - -**[R18]** *When a new NFT is minted a Royalty Account with one or more Royalty Sub Accounts MUST be created and associated with the NFT and the NFT owner, and, if there is an ancestor, with the ancestor's Royalty Account.* - -To this end the specification utilizes the ERC-721 `_safemint` function in a newly defined `mint` function, and applies various business rules on the function's input variables. - -**[D1]** *Note, that the `mint` function SHOULD have the ability to mint more than one NFT at a time.* - -**[R19]** *Also, note that the `owner` of a new NFT MUST be the NFT contract itself.* - -**[R20]** *The non-contract owner of the NFT MUST be set as `isApproved` which allows the non-contract owner to operate just like the `owner`.* - -This strange choice in the two requirements above is necessary, because the NFT contract functions as an escrow for payments and royalties, and, hence, needs to be able to track payments received from buyers and royalties due to recipients, and to associate them with a valid `tokenId`. - -**[R21]** *For compactness of the input, and since the token meta data might vary from token to token the MUST be a minimal data structure containing:* - -``` -/// @param parent is the parent tokenId of the (child) token, and if set to 0 then there is no parent. -/// @param canBeParent indicates if a tokenId can have children or not. -/// @param maxChildren defines how many children an NFT can have. -/// @param royaltySplitForItsChildren is the royalty percentage split that a child has to pay to its parent. -/// @param uri is the unique token URI of the NFT -``` - -**[R22]** *The `mint` function interface MUST adhere to the definition below:* - -``` -/// @dev Function creates one or more new NFTs with its relevant meta data necessary for royalties, and a Royalty Account with its associated met data for `to` address. The tokenId(s) will be automatically assigned (and available on the emitted {IERC-721-Transfer} event). -/// @param to is the address to which the NFT(s) are minted -/// @param nfttoken is an array of struct type NFTToken for the meta data of the minted NFT(s) -/// @param tokenType is the type of allowed payment token for the NFT - -function mint(address to, NFTToken[] memory nfttoken, address tokenType) public virtual -``` - -**[R23]** *The following business rules for the `mint` function's input data MUST be fulfilled:* - -* *The number of tokens to be minted MUST NOT be zero.* -* *`msg.sender` MUST have either the `MINTER_ROLE` or the `CREATOR_Role` identifying the creator of the first NFT.* -* *`to` address MUST NOT be the zero address.* -* *`to` address MUST NOT be a contract, unless it has been whitelisted -- see [Security Considerations](#security-considerations) for more details.* -* *`tokenType` MUST be a token type supported by the contract.* -* *`royaltySplitForItsChildren` MUST be less or equal to 100% or numerical equivalent thereof less any constraints such as platform fees* -* *If the new NFT(s) cannot have children, `royaltySplitForItsChildren` MUST be zero.* -* *If the new NFT(s) has a parent, the parent NFT `tokenId` MUST exist.* -* *The ancestry level of the parent MUST be less than the maximum number of allowed NFT generations, if specified.* -* *The number of allowed children for an NFT to be minted MUST be less than the maximum number of allowed children, if specified.* - -### Listing and De-Listing of NFTs for Direct Sales - -In the sales process, we need to minimally distinguish two types of transactions - -* Exchange-mediated sales -* Direct sales - -The first type of transaction does not require that the smart contract is aware of a sales listing since the exchange contract will trigger payment and transfer transactions directly with the NFT contract as the owner. However, for the latter transaction type it is essential, since direct sales are required to be mediated at every step by the smart contract. - -**[R24]** *For direct sales, NFT listing, und de-listing, transactions MUST be executed through the NFT smart contract.* - -Exchange-mediated sales will be discussed when this document discusses payments. - -In direct sales, authorized user addresses can list NFTs for sale, see the business rules below. - -**[R25]** *The `listNFT` function interface MUST adhere to the definition below:* - -``` -/// @dev Function to list one or more NFTs for direct sales -/// @param tokenIds is the array of tokenIds to be included in the listing -/// @param price is the price set by the owner for the listed NFT(s) -/// @param tokenType is the payment token type allowed for the listing - -function listNFT (uint256[] calldata tokenIds, uint256 price, address tokenType) public virtual returns (bool) -``` - -The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. - -**[R26]** *The business rules of the `listNFT` function are as follows:* - -* there MUST NOT already be a listing for one or more NFTs in the `listedNFT` mapping of the proposed listing. -* `seller` MUST be equal to `getApproved(tokenId[i])` for all NFTs in the proposed listing. -* `tokenType` MUST be supported by the smart contract. -* `price` MUST be larger than `0`. - -**[R27]** *If the conditions in [**[R26]**](#r26) are met, then the NFT sales list MUST be updated.* - -Authorized user addresses can also remove a direct sale listing of NFTs. - -**[R28]** *The `removeNFTListing` function interface MUST adhere to the definition below:* - -``` -/// @dev Function to de-list one or more NFTs for direct sales -/// @param listingId is the identifier of the NFT listing - -function removeNFTListing (uint256 listingId) public virtual returns (bool) -``` - -The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. - -**[R29]** *The business rules of the `removeNFTListing` function below MUST be adhered to:* - -* *the registered payment entry MUST be NULL* -* *`msg.sender = getApproved(tokenId)` for the NFT listing* - -**[R30]** *If the conditions in [**[R29]**](#r29) are met, then the NFT sales listing MUST be removed.* - -### Payments for NFT Sales - -As noted before, a buyer will always pay the NFT contract directly and not the seller. The seller is paid through the royalty distribution and can later request a payout to their wallet. - -**[R31]** *The payment process requires either one or two steps:* - -1. *For an ERC-20 token* - * *The buyer MUST `approve` the NFT contract for the purchase price, `payment`, for the selected payment token type.* - * *The buyer MUST call the `executePayment` function.* -2. *For a protocol token* - * *The buyer MUST call a payment fallback function with `msg.data` not NULL.* - -**[R32]** *For an ERC-20 token type, the required `executePayment` function interface MUST adhere to the definition below*: - -``` -/// @dev Function to make a NFT direct sales or exchange-mediate sales payment -/// @param receiver is the address of the receiver of the payment -/// @param seller is the address of the NFT seller -/// @param tokenIds are the tokenIds of the NFT to be bought -/// @param payment is the amount of that payment to be made -/// @param tokenType is the type of payment token -/// @param trxnType is the type of payment transaction -- minimally direct sales or exchange-mediated - -function executePayment (address receiver, address seller, uint 256[] tokenIds, uint256 payment, string tokenType, int256 trxnType) public virtual nonReentrant returns (bool) -``` - -The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. - -**[R33]** *Independent of `trxnType`, the business rules for the input data are as follows:* - -* *All purchased NFTs in the `tokenIds` array MUST exist and MUST NOT be burned.* -* *`tokenType` MUST be a supported token.* -* *`trxnType` MUST be set to either `0` (direct sale) or `1` (exchange-mediate sale), or another supported type.* -* *`receiver` MAY be NULL but MUST NOT be the Zero Address.* -* *`seller` MUST be the address in the corresponding listing.* -* *`msg.sender` MUST not be a contract, unless it is whitelisted in the NFT contract.* - -In the following, this document will only discuss the differences between the two minimally required transaction types. - -**[R34]** *For `trxnType = 0`, the payment data MUST to be validated against the listing, based on the following rules:* - -* *NFT(s) MUST be listed* -* *`payment` MUST be larger or equal to the listing price.* -* *The listed NFT(s) MUST match the NFT(s) in the payment data.* -* *The listed NFT(s) MUST be controlled by `seller`.* - -**[R35]** *If all checks in [**[R33]**](#r33), and in [**[R34]**](#r34) for `trxnType = 0`, are passed, the `executePayment` function MUST call the `transfer` function in the ERC-20 contract identified by `tokenType` with `recipient = address(this)` and `amount = payment`.* - -Note the NFT contract pays itself from the available allowance set in the `approve` transaction from the buyer. - -**[R36]** *For `trxnType = 1`, and for a successful payment, the `registeredPayment` mapping MUST updated with the payment, such that it can be validated when the NFT is transferred in a separate `safeTransferFrom` call, and `true` MUST be returned as the return value of the function, if successful, `false` otherwise.* - -**[R37]** *For `trxnType = 0`, an `internal` version of the `safeTransferFrom` function with message data MUST be called to transfer the NFTs to the buyer, and upon success, the buyer MUST be given the `MINTER_ROLE`, unless the buyer already has that role.* - -Note, the `_safeTransferFrom` function has the same structure as `safeTransferFrom` but skips the input data validation. - -**[R38]** *For `trxnType = 0`, and if the NFT transfer is successful, the listing of the NFT MUST be removed.* - -**[R39]** *For a protocol token as a payment token, and independent of `trxnType`, the buyer MUST send protocol tokens to the NFT contract as the escrow, and `msg.data` MUST encode the array of paid for NFTs `uint256[] tokenIds`.* - -**[R40]** *For the NFT contract to receive a protocol token, a payable fallback function (`fallback() external payable`) MUST be implemented.* - -Note that since the information for which NFTs the payment was for must be passed, a simple `receive()` fallback function cannot be allowed since it does not allow for `msg.data` to be sent with the transaction. - -**[R41]** *`msg.data` for the fallback function MUST minimally contain the following data: -`address memory seller, uint256[] memory _tokenId, address memory receiver, int256 memory trxnType`* - -**[R42]** *If `trxnType` is not equal to either '0' or '1', or another supported type, then the fallback function MUST `revert`.* - -**[R43]** *For `trxnType` equal to either '0' or '1', the requirements [**[R33]**](#r33) through [**[R38]**](#r38) MUST be satisfied for the fallback function to successfully execute, otherwise the fallback function MUST `revert`.* - -**[R44]** *In case of a transaction failure (for direct sales, `trxnType = 0`), or the buyer of the NFT listing changing their mind (for exchange-mediated sales, `trxnType = 1`), the submitted payment MUST be able to revert using the `reversePayment` function where the function interface is defined below:* - -``` -/// @dev Definition of the function enabling the reversal of a payment before the sale is complete -/// @param paymentId is the unique identifier for which a payment was made -/// @param tokenType is the type of payment token used in the payment -function reversePayment(uint256 paymentId, string memory tokenType) public virtual returns (bool) -``` - -The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. - -Note, `reentrancy` protection through e.g. `nonReentrant` from the Open Zeppelin library is strongly advised since funds are being paid out. - -**[R45]** *The business rules for the `reversePayment` function are as follows:* - -* *There MUST be registered payment for a given `paymentId` and `tokenType`.* -* *`msg.sender` MUST be the buyer address in the registered payment.* -* *The payment amount must be larger than `0`.* -* *The registered payment MUST be removed when the payment has been successfully reverted, otherwise the function must fail.* - - -### Modified NFT Transfer function - -This document adheres to the ERC-721 interface format for the `safeTransferFrom` function as given below: - -``` -function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) external virtual override -``` - -Note, that the input parameters must satisfy several requirements for the NFT(s) to be transferred AFTER royalties have been properly distributed. Note also, that the ability to transfer more than one token at a time is required. However, the standard interface only allows one token to be transferred at a time. In order to remain compliant with the ERC-721 standard, this document uses `tokenId` only for the first NFT to be transferred. All other transfer relevant data is encoded in `_data`. - -The high-level requirements are as follows: - -* The payment parameters of the trade encoded in `_data` must be validated. -* The seller and the sold NFT token(s) must exist, and the seller must be the owner of the token. -* `msg.sender` must be the seller address or an approved address. -* the payment of the trade received by the NFT smart contract is correctly disbursed to all Royalty Sub Account owners. -* the NFT token is transferred after all Royalty Sub Accounts and their holders associated with the NFT token(s) have been properly credited. - -Also, note that in order to avoid royalty circumvention attacks, there is only one NFT transfer function. - -**[R46]** *Therefore, `transferFrom` and `safeTransferFrom` without `data` MUST be disabled.* - -This can be achieved through for example a `revert` statement in an `override` function. - -**[R47]** *The requirements on input parameters of the function are as follows*: - -* *`from` MUST not be `address(0)`.* -* *`from` MUST be the owner or `approved` for `tokenId` and the other tokens included in `_data`.* -* *`from` MUST not be a smart contract unless whitelisted.* -* *a Royalty Account MUST be associated to `tokenId` and the other tokens included in `_data`.* -* *`_data` MUST NOT be NULL.* -* *`msg.sender` MUST be equal to `from` or an `approved` address, or a whitelisted contract.* - -Note, that in the context of this document only the scenario where the calling contract is still being created, i.e., the constructor being executed is a possible attack vector, and should to be carefully treated in the transfer scenario. - -Turning to the `_data` object. - -**[R48]** *The `_data` object MUST minimally contain the following payment parameters:* - -* *Seller Address as `address`.* -* *Buyer Address as `address`.* -* *Receiver Address as `address.* -* *Token identifiers as `uint256[]`.* -* *Token type used for payment.* -* *Payment amount paid to NFT contract as `uint256`.* -* *a registered payment identifier.* -* *blockchain ID, `block.chainid`, of the underlying blockchain.* - -**[R49]** *The following business rules MUST be met for the payment data in '_data':* - -* *`seller == from`.* -* *`tokenId[0] == tokenId`.* -* *Each token in `_tokenId` has an associated Royalty Account.* -* *`chainid == block.chainid`.* -* *`buyer` is equal to the buyer address in the registered payment for the given ``paymentId.* -* *`receiver == to`.* -* *the receiver of the token is not the seller.* -* *the receiver of the token is not a contract or is a whitelisted contract* -* *For all NFTs in the payment, `tokenId[i] = registeredPayment[paymentId].boughtTokens[i]`.* -* *`tokenType` is supported in the contract.* -* *`allowedToken[tokenType]` is not NULL.* -* *`tokenType = registeredPayment[paymentId].tokenType`.* -* *`payment > lastBalanceAllowedToken[allowedToken[listingId]]`.* -* *`payment = registeredPayment[paymentId].payment`.* - -### Distributing Royalties in the Transfer Function - -The approach to distributing royalties is to break down the hierarchical structure of interconnected Royalty Accounts into layers, and then process one layer at time, where each relationship between a NFT and its ancestor is utilized to traverse the Royalty Account chain until the root ancestor and its associated Royalty Account. - -Note, that the distribution function assumes that the payment made is for ALL tokens in the requested transfer. That means, that `payment` for the distribution function is equally divided between all NFTs included in the payment. - -**[R50]** *The `distributePayment` function interface MUST adhere to the definition below: - -``` -/// @dev Function to distribute a payment as royalties to a chain of Royalty Accounts -/// @param tokenId is a tokenId included in the sale and used to look up the associated Royalty Account -/// @param payment is the payment (portion) to be distributed as royalties - -function distributePayment (uint256 tokenId, uint265 payment) internal virtual returns (bool) -``` - -The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. - -As mentioned before, the internal `distributePayment` function is called within the modified `safeTransferFrom` function. - -Note, that it is necessary to multiply two `uint256` numbers with each other -- the payment amount with the royalty split percentage expressed as a whole number e.g. `10000 = 100%`. And then divide the result by the whole number representing `100%` in order to arrive at the correct application of the royalty split percentage to the payment amount. This requires careful treatment of numbers in the implementation to prevent issues such as buffer over or under runs. - -**[R51]** *The processing logic of `distributePayment` function MUST be as follows:* - -* *Load the Royalty Account (`RA`) and associated Royalty Sub Accounts using the passed `tokenId`.* -* *For each Royalty Sub Account in `RA` apply the following rules:* - * *If a Royalty Sub Account in `RA` has `isIndividual` set to `true` then* - * *apply the royalty percentage of that Royalty Sub Account to `payment` and add the calculated amount, e.g. `royaltyAmountTemp`, to the `royaltybalance` of that Royalty Sub Account.* - * *emit an event as a notification of payment to the `accountId` of the Royalty Sub Account containing: assetId, accountId, tokenType, royaltybalance.* - * *in the RA add `royaltyamountTemp` amount to `balance`* - * *If a Royalty Sub Account in `RA` has `isIndividual` set to `false` then* - * *apply the royalty percentage of that Royalty Sub Account to `payment` and store temporarily in a new variable e.g. `RApaymenttemp`, but do not update the `royaltybalance` of the Royalty Sub Account which remains `0`.* - * *then use `ancestor` to obtain the `RA` connected to `ancestor` e.g. via a look up through a Royalty Account mapping.* - * *load the new RA* - * *if `isIndividual` of the Royalty Sub Account is set to `true`, pass through the Royalty Sub Accounts of the next `RA`, and apply the rule for `isIndividual = true`.* - * *if `isIndividual` of the Royalty Sub Account is set to `false`, pass through the Royalty Sub Accounts of the next `RA`, and apply the rule for `isIndividual = false`.* - * *Repeat the procedures for `isIndividual` equal to `true` and `false` until a `RA` is reached that does not have an `ancestor`, and where all Royalty Sub Accounts have`isIndividual` set to `true`, and apply the rule for a Royalty Sub Account that has `isIndividual` set to `true` to all Royalty Sub Accounts in that `RA`.* - -### Update Royalty Sub Account Ownership with Payout to approved Address (`from`) - -In order to simplify the ownership transfer, first the approved address -- the non-contract NFT owner --, `from`, is paid out its share of the royalties. And then the Royalty Sub Account is updated with the new owner, `to`. This step repeats for each token to be transferred. - -**[R52]** *The business rules are as follows:* - -* *the internal version of the`royaltyPayOut` function MUST pay out the entire royalty balance of the Royalty Sub Account owned by the `from` address to the `from` address.* -* *the Royalty Sub Account MUST only be updated with the new owner only once the payout function has successfully completed and the `royaltybalance = 0`.* - -The last step in the process chain is transferring the NFTs in the purchase to the `to` address. - -**[R53]** *For every NFT (in the batch) the 'to' address MUST be `approved' (ERC-721 function) to complete the ownership transfer:* - -``` -_approve(to, tokenId[i]); -``` - -The technical NFT owner remains the NFT contract. - -### Removing the Payment Entry after successful Transfer - -Only after the real ownership of the NFT, the approved address, has been updated, the payment registry entry can be removed to allow the transferred NFTs to be sold again. - -**[R54]** *After the `approve` relationship has been successfully updated to the `to` address, the registered payment MUST be removed.* - -### Paying out Royalties to the `from` Address in `safeTransferFrom` Function - -There are two versions of the payout function -- a `public` and an `internal` function -- depending on whether there is a payout during a purchase, or a payout is requested by a Royalty Sub Account owner. - -**[R55]** *The public `royaltyPayOut` function interface MUST adhere to the definition below:* - -``` -/// @dev Function to payout a royalty payment -/// @param tokenId is the identifier of the NFT token -/// @param RAsubaccount is the address of the Royalty Sub Account from which the payout should happen -/// @param receiver is the address to receive the payout -/// @param amount is the amount to be paid out - -function royaltyPayOut (uint256 tokenId, address RAsubaccount, address payable payoutAccount, uint256 amount) public virtual nonReentrant returns (bool) -``` - -The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. - -Note, that the function has `reentrancy` protection through `nonReentrant` from the Open Zeppelin library since funds are being paid out. - -**[R56]** *The input parameters of the `royaltyPayOut` function MUST satisfy the following requirements:* - -* *`msg.sender == RAsubaccount`.* -* *`tokenId` must exist and must not be burned.* -* *`tokenId` must be associated with a Royalty Account.* -* *`RAsubaccount` must be a valid `accountId` in a Royalty Sub Account of the Royalty Account of the `tokenId'.* -* *`isIndividual == true` for the Royalty Sub Account, `RAsubaccount`.* -* *`amount <= royaltybalance` of the Royalty Sub Account, `RAsubaccount.*` - -**[R57]** *The internal `_royaltyPayOut` function interface MUST adhere to the definition below*: - -``` -function _royaltyPayOut (uint256 tokenId, address RAsubaccount, address payable payoutAccount, uint256 amount) public virtual returns (bool) -``` - -**[R58]** *The internal `_royaltyPayOut` function MUST perform the following actions: - -* *send the payment to the `payoutaccount`.* -* *update the `royaltybalance` of the `RAsubaccount` of the Royalty Account upon successful transfer.* - -**[R59]** *The following steps MUST be taken to send out a royalty payment to its recipient:* - -* *find the Royalty Sub Account.* -* *extract `tokenType` from the Royalty Sub Account.* -* *based on the token type send to the `payoutAccount` either* - * *'ETH' / relevant protocol token or* - * *another token based on token type* -* *and only if the payout transaction is successful, deduct `amount` from `royaltybalance` of the Royalty Sub Account,`RAsubaccount`, and then return `true` as the function return parameter, otherwise return `false`.* - -## Rationale - -Royalties for NFTs is at its core a distribution licensing problem. A buyer obtains the right to an asset/content which might or might not be reproducible, alterable etc. by the buyer or agents of the buyer. Therefore, a comprehensive specification must address a hierarchy of royalties, where one or more assets are derived from an original asset as described in the Motivation section in detail. Consequently, a design must solve for a multi-level inheritance, and thus, recursion problem. - -In order to solve for the complicated inheritance problem, this proposal design breaks down the recursive problem of the hierarchy first into a tree of depth N. And the further breaks down the tree structure into N separate problems, one for each layer. This design allows one to traverse the tree from its lowest level upwards to its root most efficiently. This is achieved with the design for the `distributePayment` function and the NFT data structures allowing for the tree structure e.g. `ancestry`,`royaltyAccount`, `RAsubaccount`. - -In order to avoid massive gas costs during the payout of royalties, possibly exceeding block gas limits for large royalty trees, the design needed to create a royalty accounting system to maintain royalty balances for recipients as done with the `royaltyAccount`, 'RAsubaccount' data structures and the associated CRUD operations, as well as require that royalty payouts are done by individual and by request, only, as is achieved with the `royaltyPayout` function design. - -Furthermore, the design had to ensure that in order to account for and payout royalties the smart contract must be in the "know" of all buying and selling of an NFT including the exchange of monies. This buying and selling can be either direct through the NFT contract or can be exchange-mediated as is most often the case today -- which is a centralizing factor! The chosen design for purchasing is accounting for those two modes. - -Keeping the NFT contract in the "know" at the beginning of the purchase process requires that authorized user addresses can list NFTs for sale for direct sales , whereas for exchange-mediated purchases, a payment must be registered with the NFT contract before the purchase can be completed. - -The design needed to avoid royalty circumvention during the purchase process, therefore, the NFT must be kept in the "know", a buyer will always have to pay the NFT contract directly and not the seller for both purchasing modes. The seller is subsequently paid through the royalty distribution function in the NFT contract. As a consequence, and a key design choice, and to stay compliant with ERC-721, the NFT contract must be the owner of the NFT, and the actual owner is an `approved` address. - -The specification design also needed to account for that the payment process depends on whether the payment is received in ETH or an ERC-20 token: - -* ERC-20 Token - 1. The Buyer must `approve` the NFT contract for the purchase price, `payment` for the selected payment token (ERC-20 contract address). - 2. For an ERC-20 payment token, the Buyer must then call the `executePayment` in the NFT contract -- the ERC-20 is not directly involved. -* For a non-ERC-20 payment, the Buyer must send a protocol token (ETH) to the NFT contract, and is required to send encoded listing and payment information. - -In addition, the `executePayment` function had to be designed to handle both direct sales (through the NFT contract) and exchange-mediated sales which required the introduction of an indicator whether the purchase is direct or exchange-mediated. - -The `executePayment` function also has to handle the NFT transfer and purchase clean up -- removal of a listing, or removal of a registered payment, distribution of royalties, payment to the seller, and finally transfer to the seller. - -To stay compliant with the ERC-721 design but avoid royalty circumvention, all transfer functions must be disabled save the one that allows for additional information to be submitted with the function in order to manage the complicated purchase cleanup process -- `safeTransferFrom`. To ensure safety, the design enforces that input parameters must satisfy several requirements for the NFT to be transferred AFTER the royalties have been properly distributed, not before. The design accounts for the fact that we need to treat transfer somewhat differently for direct sales versus exchange mediated sales. - -Finally the specification needed to take into account that NFTs must be able to be `minted` and `burned` to maintain compliance with the ERC-721 specification while also having to set up all the data structures for the tree. - -The design enforces that when an NFT is minted, a royalty account for that NFT must be created and associated with the NFT and the NFT owner, and, if there is an ancestor of the NFT with the ancestor's royalty account to enforces the tree structure. To this end the specification utilizes the ERC-721 `_safemint` function in a newly defined `mint` function and applies various business rules on the input variables required to ensure proper set-up. - -An NFT with a royalty account can be burned. However, several things have to be true to avoid locking funds not only for the royalty account of the NFT but also its descendants, if they exist. That means that all royalties for the NFT and its descendants, if they exists, must be paid out. Furthermore, if descendants exist, they must have been burned before an ancestor can be burned. If those rules are not enforced the cleanly, the hierarchical royalty structure in part of the tree can break down and lead to lost funds, not paid out royalties etc. - - -## Backwards Compatibility - -This EIP is backwards compatible to the ERC-721 standard introducing new interfaces and functionality but retaining the core interfaces and functionality of the ERC-721 standard. - -## Test Cases - -A full test suite is part of the reference implementation. - -## Reference Implementation - -The Treetrunk reference implementation of the standard can be found in the public treetrunkio Github repo under treetrunk-nft-reference-implementation. - -## Security Considerations - -Given that this EIP introduces royalty collection, distribution, and payouts to the ERC-721 standard, the number of attack vectors increases. The most important attack vector categories and their mitigation are discussed below: - -* **Payments and Payouts**: - * Reentrancy attacks are mitigated through a reentrancy protection on all payment functions. See for example the Open Zeppelin reference implementation . - * Payouts from unauthorized accounts. Mitigation: Royalty Sub Accounts require at least that `msg.sender` is the Royalty Sub Account owner. - * Payments could get stuck in the NFT contract if the `executePayment` function fails. Mitigation: For exchange-mediated sales, a buyer can always reverse a payment with `reversePayment` if the `executePayment` function fails. For direct sales, `reversePayment` will be directly triggered in the `executePayment` function. -* **Circumventing Royalties**: - * Offchain Key exchanges - * Exchanging a private key for money off chain can not be prevented in any scenario. - * Smart Contract Wallets as NFT owners - * A Smart Contract Wallet controlled by multiple addresses could own an NFT and the owners could transfer the asset within the wallet with an off chain money exchange. Mitigation: Prohibit that Smart Contracts can own an NFT unless explicitly allowed to accommodate special scenarios such as collections. - * Denial of Royalty Disbursement - * An attacker who has purchased one or more NFTs in a given generation of an NFT family can cause out of gas errors or run time errors for the contract, if they add many spurious royalty sub-accounts with very low royalty split percentages, and then mint more prints of those purchased NFTs, and then repeat that step until the set `maxGeneration` limit is reached. An NFT trade at the bottom of the hierarchy will then require a lot of code cycles because of the recursive nature of the royalty distribution function. Mitigation: Limit the number of royalty sub-accounts per NFT and impose a royalty split percentage limit. - * Following the same approach as above but now targeting the `addListNFT` function, an attacker can force an out of gas error or run time errors in the `executePayment` function by listing many NFTs at a low price, and then performing a purchase from another account. Mitigation: Limit the number of NFTs that can be included in one listing. - * The creator of the NFT family could set the number of generations too high such that the royalty distribution function could incur and out of gas or run time error because of the recursive nature of the function. Mitigation: Limiting the `maxNumberGeneration` by the creator. - * General Considerations: The creator of an NFT family must carefully consider the business model for the NFT family and then set the parameters such as maximum number of generations, royalty sub-accounts, number of prints per print, number of NFTs in a listing, and the maximum and minimum royalty split percentage allowed. -* **Phishing Attacks** - * NFT phishing attacks often target the `approve` and `setApprovalForAll` functions by tricking owners of NFTs to sign transactions adding the attacker account as approved for one or all NFTs of the victim. Mitigation: This contract is not vulnerable to these type of phishing attacks because all NFT transfers are sales, and the NFT contract itself is the owner of all NFTs. This means that transfers after a purchase are achieved by setting the new owner in the `_approve` function. Calling the public `approve` function will cause the function call to error out because `msg.sender` of the malicious transaction cannot be the NFT owner. - * NFT phishing attack targeting the `addListNFT` function to trick victim to list one or more NFTs at a very low price and the attacker immediately registering a payment, and executing that payment right away. Mitigation: Implement a waiting period for a purchase can be affected giving the victim time to call the `removeListNFT` function. In addition, an implementer could require Two-Factor-Authentication either built into the contract or by utilizing an authenticator app such as Google Authenticator built into a wallet software. - -Besides the usage of professional security analysis tools, it is also recommended that each implementation performs a security audit of its implementation. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4910.md diff --git a/EIPS/eip-4931.md b/EIPS/eip-4931.md index 5fbf719b18072a..1db444eddbd541 100644 --- a/EIPS/eip-4931.md +++ b/EIPS/eip-4931.md @@ -1,350 +1 @@ ---- -eip: 4931 -title: Generic Token Upgrade Standard -description: Create a standard interface for upgrading ERC20 token contracts. -author: John Peterson (@John-peterson-coinbase), Roberto Bayardo (@roberto-bayardo), David Núñez (@cygnusv) -discussions-to: https://ethereum-magicians.org/t/eip-4931-generic-token-upgrade-standard/8687 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-11-02 -requires: 20 ---- - - -## Abstract - -The following standard allows for the implementation of a standard API for [ERC-20](./eip-20.md) token upgrades. This standard specifies an interface that supports the conversion of tokens from one contract (called the "source token") to those from another (called the "destination token"), as well as several helper methods to provide basic information about the token upgrade (i.e. the address of the source and destination token contracts, the ratio that source will be upgraded to destination, etc.). - -## Motivation - -Token contract upgrades typically require each asset holder to exchange their old tokens for new ones using a bespoke interface provided by the developers. This standard interface will allow asset holders as well as centralized and decentralized exchanges to conduct token upgrades more efficiently since token contract upgrade scripts will be essentially reusable. Standardization will reduce the security overhead involved in verifying the functionality of the upgrade contracts. It will also provide asset issuers clear guidance on how to effectively implement a token upgrade. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Please Note: Methods marked with (Optional Ext.) are a part of the optional extension for downgrade functionality and may remain unimplemented if downgrade functionality is not required. -### Token Upgrade Interface Contract -``` solidity -interface IEIP4931 { -``` -#### Methods - -##### upgradeSource - -Returns the address of the original (source) token that will be upgraded. - -``` solidity -/// @dev A getter to determine the contract that is being upgraded from ("source contract") -/// @return The address of the source token contract -function upgradeSource() external view returns(address) -``` - -##### upgradeDestination - -Returns the address of the token contract that is being upgraded to. - -``` solidity -/// @dev A getter to determine the contract that is being upgraded to ("destination contract") -/// @return The address of the destination token contract -function upgradeDestination() external view returns(address) -``` - -##### isUpgradeActive - -Returns the current status of the upgrade functionality. Status MUST return `true` when the upgrade contract is functional and serving upgrades. It MUST return `false` when the upgrade contract is not currently serving upgrades. - -``` solidity -/// @dev The method will return true when the contract is serving upgrades and otherwise false -/// @return The status of the upgrade as a boolean -function isUpgradeActive() external view returns(bool) -``` -##### isDowngradeActive - -Returns the current status of the downgrade functionality. Status MUST return `true` when the upgrade contract is functional and serving downgrades. It MUST return `false` when the upgrade contract is not currently serving downgrades. When the downgrade Optional Ext. is not implemented, this method will always return `false` to signify downgrades are not available. - -``` solidity -/// @dev The method will return true when the contract is serving downgrades and otherwise false -/// @return The status of the downgrade as a boolean -function isDowngradeActive() external view returns(bool) -``` -##### ratio - -Returns the ratio of destination token to source token, expressed as a 2-tuple, that the upgrade will use. E.g. `(3, 1)` means the upgrade will provide 3 destination tokens for every 1 source token being upgraded. - -``` solidity -/// @dev A getter for the ratio of destination tokens to source tokens received when conducting an upgrade -/// @return Two uint256, the first represents the numerator while the second represents -/// the denominator of the ratio of destination tokens to source tokens allotted during the upgrade -function ratio() external view returns(uint256, uint256) -``` - -##### totalUpgraded - -Returns the total number of tokens that have been upgraded from source to destination. If the downgrade Optional Ext. is implemented, calls to `downgrade` will reduce the `totalUpgraded` return value making it possible for the value to decrease between calls. The return value will be strictly increasing if downgrades are not implemented. - -``` solidity -/// @dev A getter for the total amount of source tokens that have been upgraded to destination tokens. -/// The value may not be strictly increasing if the downgrade Optional Ext. is implemented. -/// @return The number of source tokens that have been upgraded to destination tokens -function totalUpgraded() external view returns(uint256) -``` -##### computeUpgrade - -Computes the `destinationAmount` of destination tokens that correspond to a given `sourceAmount` of source tokens, according to the predefined conversion ratio, as well as the `sourceRemainder` amount of source tokens that can't be upgraded. For example, let's consider a (3, 2) ratio, which means that 3 destination tokens are provided for every 2 source tokens; then, for a source amount of 5 tokens, `computeUpgrade(5)` must return `(6, 1)`, meaning that 6 destination tokens are expected (in this case, from 4 source tokens) and 1 source token is left as remainder. -``` solidity -/// @dev A method to mock the upgrade call determining the amount of destination tokens received from an upgrade -/// as well as the amount of source tokens that are left over as remainder -/// @param sourceAmount The amount of source tokens that will be upgraded -/// @return destinationAmount A uint256 representing the amount of destination tokens received if upgrade is called -/// @return sourceRemainder A uint256 representing the amount of source tokens left over as remainder if upgrade is called -function computeUpgrade(uint256 sourceAmount) external view - returns (uint256 destinationAmount, uint256 sourceRemainder) -``` - -##### computeDowngrade (Optional Ext.) - -Computes the `sourceAmount` of source tokens that correspond to a given `destinationAmount` of destination tokens, according to the predefined conversion ratio, as well as the `destinationRemainder` amount of destination tokens that can't be downgraded. For example, let's consider a (3, 2) ratio, which means that 3 destination tokens are provided for every 2 source tokens; for a destination amount of 13 tokens, `computeDowngrade(13)` must return `(4, 1)`, meaning that 4 source tokens are expected (in this case, from 12 destination tokens) and 1 destination token is left as remainder. -``` solidity -/// @dev A method to mock the downgrade call determining the amount of source tokens received from a downgrade -/// as well as the amount of destination tokens that are left over as remainder -/// @param destinationAmount The amount of destination tokens that will be downgraded -/// @return sourceAmount A uint256 representing the amount of source tokens received if downgrade is called -/// @return destinationRemainder A uint256 representing the amount of destination tokens left over as remainder if upgrade is called -function computeDowngrade(uint256 destinationAmount) external view - returns (uint256 sourceAmount, uint256 destinationRemainder) -``` - - -##### upgrade - -Upgrades the `amount` of source token to the destination token in the specified ratio. The destination tokens will be sent to the `_to` address. The function MUST lock the source tokens in the upgrade contract or burn them. If the downgrade Optional Ext. is implemented, the source tokens MUST be locked instead of burning. The function MUST `throw` if the caller's address does not have enough source token to upgrade or if `isUpgradeActive` is returning `false`. The function MUST also fire the `Upgrade` event. `approve` MUST be called first on the source contract. -``` solidity -/// @dev A method to conduct an upgrade from source token to destination token. -/// The call will fail if upgrade status is not true, if approve has not been called -/// on the source contract, or if sourceAmount is larger than the amount of source tokens at the msg.sender address. -/// If the ratio would cause an amount of tokens to be destroyed by rounding/truncation, the upgrade call will -/// only upgrade the nearest whole amount of source tokens returning the excess to the msg.sender address. -/// Emits the Upgrade event -/// @param _to The address the destination tokens will be sent to upon completion of the upgrade -/// @param sourceAmount The amount of source tokens that will be upgraded -function upgrade(address _to, uint256 sourceAmount) external -``` - - -##### downgrade (Optional Ext.) -Downgrades the `amount` of destination token to the source token in the specified ratio. The source tokens will be sent to the `_to` address. The function MUST unwrap the destination tokens back to the source tokens. The function MUST `throw` if the caller's address does not have enough destination token to downgrade or if `isDowngradeActive` is returning `false`. The function MUST also fire the `Downgrade` event. `approve` MUST be called first on the destination contract. -``` solidity -/// @dev A method to conduct a downgrade from destination token to source token. -/// The call will fail if downgrade status is not true, if approve has not been called -/// on the destination contract, or if destinationAmount is larger than the amount of destination tokens at the msg.sender address. -/// If the ratio would cause an amount of tokens to be destroyed by rounding/truncation, the downgrade call will only downgrade -/// the nearest whole amount of destination tokens returning the excess to the msg.sender address. -/// Emits the Downgrade event -/// @param _to The address the source tokens will be sent to upon completion of the downgrade -/// @param destinationAmount The amount of destination tokens that will be downgraded -function downgrade(address _to, uint256 destinationAmount) external -``` - -#### Events - -##### Upgrade - -MUST trigger when tokens are upgraded. - -``` solidity -/// @param _from Address that called upgrade -/// @param _to Address that destination tokens were sent to upon completion of the upgrade -/// @param sourceAmount Amount of source tokens that were upgraded -/// @param destinationAmount Amount of destination tokens sent to the _to address -event Upgrade(address indexed _from, address indexed _to, uint256 sourceAmount, uint256 destinationAmount) -``` - -##### Downgrade (Optional Ext.) - -MUST trigger when tokens are downgraded. - -``` solidity -/// @param _from Address that called downgrade -/// @param _to Address that source tokens were sent to upon completion of the downgrade -/// @param sourceAmount Amount of source tokens sent to the _to address -/// @param destinationAmount Amount of destination tokens that were downgraded -event Downgrade(address indexed _from, address indexed _to, uint256 sourceAmount, uint256 destinationAmount) -} -``` - -## Rationale -There have been several notable ERC20 upgrades (Ex. Golem: GNT -> GLM) where the upgrade functionality is written directly into the token contracts. We view this as a suboptimal approach to upgrades since it tightly couples the upgrade with the existing tokens. This EIP promotes the use of a third contract to facilitate the token upgrade to decouple the functionality of the upgrade from the functionality of the token contracts. Standardizing the upgrade functionality will allow asset holders and exchanges to write simplified reusable scripts to conduct upgrades which will reduce the overhead of conducting upgrades in the future. The interface aims to be intentionally broad leaving much of the specifics of the upgrade to the implementer, so that the token contract implementations do not interfere with the upgrade process. Finally, we hope to create a greater sense of security and validity for token upgrades by enforcing strict means of disposing of the source tokens during the upgrade. This is achieved by the specification of the `upgrade` method. The agreed upon norm is that burnable tokens shall be burned. Otherwise, tokens shall be effectively burned by being sent to the `0x00` address. When downgrade Optional Ext. is implemented, the default is instead to lock source tokens in the upgrade contract to avoid a series of consecutive calls to `upgrade` and `downgrade` from artificially inflating the supply of either token (source or destination). - -## Backwards Compatibility -There are no breaking backwards compatibility issues. There are previously implemented token upgrades that likely do not adhere to this standard. In these cases, it may be relevant for the asset issuers to communicate that their upgrade is not EIP-4931 compliant. - -## Reference Implementation -``` solidity -//SPDX-License-Identifier: Apache-2.0 -pragma solidity 0.8.9; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./IEIP4931.sol"; - -contract SourceUpgrade is IEIP4931 { - using SafeERC20 for IERC20; - - uint256 constant RATIO_SCALE = 10**18; - - IERC20 private source; - IERC20 private destination; - bool private upgradeStatus; - bool private downgradeStatus; - uint256 private numeratorRatio; - uint256 private denominatorRatio; - uint256 private sourceUpgradedTotal; - - mapping(address => uint256) public upgradedBalance; - - constructor(address _source, address _destination, bool _upgradeStatus, bool _downgradeStatus, uint256 _numeratorRatio, uint256 _denominatorRatio) { - require(_source != _destination, "SourceUpgrade: source and destination addresses are the same"); - require(_source != address(0), "SourceUpgrade: source address cannot be zero address"); - require(_destination != address(0), "SourceUpgrade: destination address cannot be zero address"); - require(_numeratorRatio > 0, "SourceUpgrade: numerator of ratio cannot be zero"); - require(_denominatorRatio > 0, "SourceUpgrade: denominator of ratio cannot be zero"); - - source = IERC20(_source); - destination = IERC20(_destination); - upgradeStatus = _upgradeStatus; - downgradeStatus = _downgradeStatus; - numeratorRatio = _numeratorRatio; - denominatorRatio = _denominatorRatio; - } - - /// @dev A getter to determine the contract that is being upgraded from ("source contract") - /// @return The address of the source token contract - function upgradeSource() external view returns(address) { - return address(source); - } - - /// @dev A getter to determine the contract that is being upgraded to ("destination contract") - /// @return The address of the destination token contract - function upgradeDestination() external view returns(address) { - return address(destination); - } - - /// @dev The method will return true when the contract is serving upgrades and otherwise false - /// @return The status of the upgrade as a boolean - function isUpgradeActive() external view returns(bool) { - return upgradeStatus; - } - - /// @dev The method will return true when the contract is serving downgrades and otherwise false - /// @return The status of the downgrade as a boolean - function isDowngradeActive() external view returns(bool) { - return downgradeStatus; - } - - /// @dev A getter for the ratio of destination tokens to source tokens received when conducting an upgrade - /// @return Two uint256, the first represents the numerator while the second represents - /// the denominator of the ratio of destination tokens to source tokens allotted during the upgrade - function ratio() external view returns(uint256, uint256) { - return (numeratorRatio, denominatorRatio); - } - - /// @dev A getter for the total amount of source tokens that have been upgraded to destination tokens. - /// The value may not be strictly increasing if the downgrade Optional Ext. is implemented. - /// @return The number of source tokens that have been upgraded to destination tokens - function totalUpgraded() external view returns(uint256) { - return sourceUpgradedTotal; - } - - /// @dev A method to mock the upgrade call determining the amount of destination tokens received from an upgrade - /// as well as the amount of source tokens that are left over as remainder - /// @param sourceAmount The amount of source tokens that will be upgraded - /// @return destinationAmount A uint256 representing the amount of destination tokens received if upgrade is called - /// @return sourceRemainder A uint256 representing the amount of source tokens left over as remainder if upgrade is called - function computeUpgrade(uint256 sourceAmount) - public - view - returns (uint256 destinationAmount, uint256 sourceRemainder) - { - sourceRemainder = sourceAmount % (numeratorRatio / denominatorRatio); - uint256 upgradeableAmount = sourceAmount - (sourceRemainder * RATIO_SCALE); - destinationAmount = upgradeableAmount * (numeratorRatio / denominatorRatio); - } - - /// @dev A method to mock the downgrade call determining the amount of source tokens received from a downgrade - /// as well as the amount of destination tokens that are left over as remainder - /// @param destinationAmount The amount of destination tokens that will be downgraded - /// @return sourceAmount A uint256 representing the amount of source tokens received if downgrade is called - /// @return destinationRemainder A uint256 representing the amount of destination tokens left over as remainder if upgrade is called - function computeDowngrade(uint256 destinationAmount) - public - view - returns (uint256 sourceAmount, uint256 destinationRemainder) - { - destinationRemainder = destinationAmount % (denominatorRatio / numeratorRatio); - uint256 upgradeableAmount = destinationAmount - (destinationRemainder * RATIO_SCALE); - sourceAmount = upgradeableAmount / (denominatorRatio / numeratorRatio); - } - - /// @dev A method to conduct an upgrade from source token to destination token. - /// The call will fail if upgrade status is not true, if approve has not been called - /// on the source contract, or if sourceAmount is larger than the amount of source tokens at the msg.sender address. - /// If the ratio would cause an amount of tokens to be destroyed by rounding/truncation, the upgrade call will - /// only upgrade the nearest whole amount of source tokens returning the excess to the msg.sender address. - /// Emits the Upgrade event - /// @param _to The address the destination tokens will be sent to upon completion of the upgrade - /// @param sourceAmount The amount of source tokens that will be upgraded - function upgrade(address _to, uint256 sourceAmount) external { - require(upgradeStatus == true, "SourceUpgrade: upgrade status is not active"); - (uint256 destinationAmount, uint256 sourceRemainder) = computeUpgrade(sourceAmount); - sourceAmount -= sourceRemainder; - require(sourceAmount > 0, "SourceUpgrade: disallow conversions of zero value"); - - upgradedBalance[msg.sender] += sourceAmount; - source.safeTransferFrom( - msg.sender, - address(this), - sourceAmount - ); - destination.safeTransfer(_to, destinationAmount); - sourceUpgradedTotal += sourceAmount; - emit Upgrade(msg.sender, _to, sourceAmount, destinationAmount); - } - - /// @dev A method to conduct a downgrade from destination token to source token. - /// The call will fail if downgrade status is not true, if approve has not been called - /// on the destination contract, or if destinationAmount is larger than the amount of destination tokens at the msg.sender address. - /// If the ratio would cause an amount of tokens to be destroyed by rounding/truncation, the downgrade call will only downgrade - /// the nearest whole amount of destination tokens returning the excess to the msg.sender address. - /// Emits the Downgrade event - /// @param _to The address the source tokens will be sent to upon completion of the downgrade - /// @param destinationAmount The amount of destination tokens that will be downgraded - function downgrade(address _to, uint256 destinationAmount) external { - require(upgradeStatus == true, "SourceUpgrade: upgrade status is not active"); - (uint256 sourceAmount, uint256 destinationRemainder) = computeDowngrade(destinationAmount); - destinationAmount -= destinationRemainder; - require(destinationAmount > 0, "SourceUpgrade: disallow conversions of zero value"); - require(upgradedBalance[msg.sender] >= sourceAmount, - "SourceUpgrade: can not downgrade more than previously upgraded" - ); - - upgradedBalance[msg.sender] -= sourceAmount; - destination.safeTransferFrom( - msg.sender, - address(this), - destinationAmount - ); - source.safeTransfer(_to, sourceAmount); - sourceUpgradedTotal -= sourceAmount; - emit Downgrade(msg.sender, _to, sourceAmount, destinationAmount); - } -} -``` - - -## Security Considerations -The main security consideration is ensuring the implementation of the interface handles the source tokens during the upgrade in such a way that they are no longer accessible. Without careful handling, the validity of the upgrade may come into question since source tokens could potentially be upgraded multiple times. This is why EIP-4931 will strictly enforce the use of `burn` for source tokens that are burnable. For non-burnable tokens, the accepted method is to send the source tokens to the `0x00` address. When the downgrade Optional Ext. is implemented, the constraint will be relaxed, so that the source tokens can be held by the upgrade contract. - -## Copyright -Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4931.md diff --git a/EIPS/eip-4944.md b/EIPS/eip-4944.md index 59a3010f38645d..f4f28704c312a9 100644 --- a/EIPS/eip-4944.md +++ b/EIPS/eip-4944.md @@ -1,82 +1 @@ ---- -eip: 4944 -title: Contract with Exactly One Non-fungible Token -description: An ERC-721 compatible single-token NFT -author: Víctor Muñoz (@victormunoz), Josep Lluis de la Rosa (@peplluis7), Andres El-Fakdi (@Bluezfish) -discussions-to: https://ethereum-magicians.org/t/erc721-minting-only-one-token/8602/2 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-03-25 -requires: 721 ---- - -## Abstract - -The following describes standard functions for an [ERC-721](./eip-721.md) compatible contract with a total supply of one. -This allows an NFT to be associated uniquely with a single contract address. - -## Motivation - -If the ERC-721 was modified to mint only 1 token (per contract), then the contract address could be identified uniquely with that minted token (instead of the tuple contract address + token id, as ERC-721 requires). -This change would enable automatically all the capabilities of composable tokens [ERC-998](./eip-998.md) (own other ERC-721 or [ERC-20](./eip-20.md)) natively without adding any extra code, just forbidding to mint more than one token per deployed contract. -Then the NFT minted with this contract could operate with his "budget" (the ERC-20 he owned) and also trade with the other NFTs he could own. Just like an autonomous agent, that could decide what to do with his properties (sell his NFTs, buy other NFTs, etc). - -The first use case that is devised is for value preservation. Digital assets, as NFTs, have value that has to be preserved in order to not be lost. If the asset has its own budget (in other ERC-20 coins), could use it to autopreserve itself. - -## Specification - -The constructor should mint the unique token of the contract, and then the mint function should add a restriction to avoid further minting. - -Also, a `tokenTransfer` function should be added in order to allow the contract owner to transact with the ERC-20 tokens owned by the contract/NFT itself. So that if the contract receives a transfer of ERC-20 tokens, the owner of the NFT could spend it from the contract wallet. - -## Rationale - -The main motivation is to keep the contract compatible with current ERC-721 platforms. - -## Backwards Compatibility - -There are no backwards compatibility issues. - -## Reference Implementation - -Add the variable `_minted` in the contract: - -``` solidity - bool private _minted; -``` - -In the constructor, automint the first token and set the variable to true: - -``` solidity - constructor(string memory name, string memory symbol, string memory base_uri) ERC721(name, symbol) { - baseUri = base_uri; - mint(msg.sender,0); - _minted = true; - } -``` - -Add additional functions to interact with the NFT properties (for instance, ERC-20): - -``` solidity - modifier onlyOwner() { - require(balanceOf(msg.sender) > 0, "Caller is not the owner of the NFT"); - _; - } - - function transferTokens(IERC20 token, address recipient, uint256 amount) public virtual onlyOwner { - token.transfer(recipient, amount); - } - - function balanceTokens(IERC20 token) public view virtual returns (uint256) { - return token.balanceOf(address(this)); - } -``` - -## Security Considerations - -No security issues found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4944.md diff --git a/EIPS/eip-4950.md b/EIPS/eip-4950.md index 442b75545fe88f..579d2b50ac134a 100644 --- a/EIPS/eip-4950.md +++ b/EIPS/eip-4950.md @@ -1,93 +1 @@ ---- -eip: 4950 -title: Entangled Tokens -description: ERC-721 extension with two tokens minted that are tied together -author: Víctor Muñoz (@victormunoz), Josep Lluis de la Rosa (@peplluis7), Easy Innova (@easyinnova) -discussions-to: https://ethereum-magicians.org/t/entangled-tokens/8702 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-03-28 -requires: 20, 721, 1155 ---- - -## Abstract - -This EIP defines an interface for delegating control of a smart contract wallet to pairs of users using entangled [ERC-721](./eip-721.md) non-fungible tokens. - -## Motivation - -The motivation is to provide an easy way to share a wallet through NFTs, so that the act of buying an NFT (in a marketplace) gives the buyer the privilege to have access to a given wallet. This wallet could have budget in many tokens, or even be the owner of other NFTs. - -A use case is to keep contact between an artist and an buyer of its NFTs. If an artist T has created a digital piece of art P with an NFT, then T creates 2 entangled tokens A and B so that he keeps A and transfer B to P. By construction of entangled tokens, only one transfer is possible for them, thus the artist proofs he’s been the creator of P by sending a transaction to A that is visible from B. Otherwise, the owner of P might check the authenticity of the artist by sending a transaction to B so that the artist might proof by showing the outcome out of A. - -A version of this use case is when one user U mints his piece of art directly in the form of an entangled token A; then the user U sells/transfers it while keeping the entangled token B in the U's wallet. The piece of art and the artists will be entangled whoever is the A's owner. - -These applications of entangled tokens are envisaged to be useful for: - -1. NFT authorship / art creation -2. Distribution of royalties by the creator. -3. Authenticity of a work of art: creation limited to the author (e.g. only 1000 copies if there are 1000 1000 entangled tokens in that NFT). -4. Usowners (users that consume an NFT also become -partial- owners of the NFT) -5. Reformulation of property rights: the one who owns the property receives it without having to follow in the footsteps of the owners. -6. Identity: Only those credentials that have an entangled token with you are related to you. -7. Vreservers (value-reservers). - -## Specification - -An entangled token contract implements [ERC-721](./eip-721.md) with the additional restriction that it only ever mints exactly two tokens at contract deployment: one with a `tokenId` of `0`, the other with a `tokenId` of `1`. The entangled token contract also implements a smart contract wallet that can be operated by the owners of those two tokens. - -Also, a `tokenTransfer` function is to be be added in order to allow the token owners to transact with the [ERC-20](./eip-20.md) tokens owned by the contract/NFT itself. The function signature is as follows: - -```solidity - function tokenTransfer(IERC20 token, address recipient, uint256 amount) public onlyOwners; -``` - -## Rationale - -We decide to extend [ERC-721](./eip-721.md) ([ERC-1155](./eip-1155.md) could be also possible) because the main purpose of this is to be compatible with current marketplaces platforms. This entangled NFTs will be listed in a marketplace, and the user who buys it will have then the possibility to transact with the wallet properties (fungible and non fungible tokens). - -## Backwards Compatibility - -No backwards compatibility issues. - -## Reference Implementation - -Mint two tokens, and only two, at the contract constructor, and set the `minted` property to true: - -```solidity -bool private _minted; - -constructor(string memory name, string memory symbol, string memory base_uri) ERC721(name, symbol) { - baseUri = base_uri; - _mint(msg.sender,0); - _mint(msg.sender,1); - _minted = true; - } - -function _mint(address to, uint256 tokenId) internal virtual override { - require(!_minted, "ERC4950: already minted"); - super._mint(to, tokenId); -} -``` - -Add additional functions to allow both NFT user owners to operate with other ERC-20 tokens owned by the contract: - -```solidity - modifier onlyOwners() { - require(balanceOf(msg.sender) > 0, "Caller does not own any of the tokens"); - _; - } - -function tokenTransfer(IERC20 token, address recipient, uint256 amount) public onlyOwners { - token.transfer(recipient, amount); - } -``` - -## Security Considerations - -There are no security considerations. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4950.md diff --git a/EIPS/eip-4955.md b/EIPS/eip-4955.md index 9a3b98b74f53d3..115b29023a81a4 100644 --- a/EIPS/eip-4955.md +++ b/EIPS/eip-4955.md @@ -1,198 +1 @@ ---- -eip: 4955 -title: Vendor Metadata Extension for NFTs -description: Add a new field to NFT metadata to store vendor specific data -author: Ignacio Mazzara (@nachomazzara) -discussions-to: https://ethereum-magicians.org/t/eip-4955-non-fungible-token-metadata-namespaces-extension/8746 -status: Final -type: Standards Track -category: ERC -created: 2022-03-29 -requires: 721, 1155 ---- - -## Abstract - -This EIP standardizes a schema for NFTs metadata to add new field namespaces to the JSON schema for [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) NFTs. - -## Motivation - -A standardized NFT metadata schema allows wallets, marketplaces, metaverses, and similar applications to interoperate with any NFT. Applications such as NFT marketplaces and metaverses could usefully leverage NFTs by rendering them using custom 3D representations or any other new attributes. - -Some projects like Decentraland, TheSandbox, Cryptoavatars, etc. need their own 3D model in order to represent an NFT. These models are not cross-compatible because of distinct aesthetics and data formats. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Schema - -(subject to "caveats" below) - -A new property called `namespaces` is introduced. This property expects one object per project as shown in the example below. - -```jsonc -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset that this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset that this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset that this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "namespaces": { - "type": "object", - "description": "Application-specific NFT properties" - } - } -} -``` - -### Example - -```jsonc -{ - "name": "My NFT", - "description": "NFT description", - "image": "ipfs://QmZfmRZHuawJDtDVMaEaPWfgWFV9iXoS9SzLvwX76wm6pa", - "namespaces": { - "myAwesomeCompany": { - "prop1": "value1", - "prop2": "value2", - }, - "myAwesomeCompany2": { - "prop3": "value3", - "prop4": "value4", - }, - } -} - -// Or by simply using a `URI` to reduce the size of the JSON response. - -{ - "name": "My NFT", - "description": "NFT description", - "image": "ipfs://QmZfmRZHuawJDtDVMaEaPWfgWFV9iXoS9SzLvwX76wm6pa", - "namespaces": { - "myAwesomeCompany": "URI", - "myAwesomeCompany2": "URI", - } -} -``` - -## Rationale - -There are many projects which need custom properties in order to display a current NFT. Each project may have its own way to render the NFTs and therefore they need different values. An example of this is the metaverses like Decentraland or TheSandbox where they need different 3d models to render the NFT based on the visual/engine of each. NFTs projects like Cryptopunks, Bored Apes, etc. can create the 3d models needed for each project and therefore be supported out of the box. - -The main differences between the projects that are rendering 3d NFTs (models) are: - -### Armatures - -Every metaverse uses its own armature. There is a standard for humanoids but it is not being used for every metaverse and not all the metaverses use humanoids. For example, Decentraland has a different aesthetic than Cryptovoxels and TheSandbox. It means that every metaverse will need a different model and they may have the same extension (GLB, GLTF) - -![](../assets/eip-4955/different-renders.jpeg) - -### Metadata (Representations Files) - -For example, every metaverse uses its own metadata representation files to make it work inside the engine depending on its game needs. - -This is the JSON config of a wearable item in Decentraland: - -```jsonc -"data": { - "replaces": [], - "hides": [], - "tags": [], - "category": "upper_body", - "representations": [ - { - "bodyShapes": [ - "urn:decentraland:off-chain:base-avatars:BaseMale" - ], - "mainFile": "male/Look6_Tshirt_A.glb", - "contents": [ - { - "key": "male/Look6_Tshirt_A.glb", - "url": "https://peer-ec2.decentraland.org/content/contents/QmX3yMhmx4AvGmyF3CM5ycSQB4F99zXh9rL5GvdxTTcoCR" - } - ], - "overrideHides": [], - "overrideReplaces": [] - }, - { - "bodyShapes": [ - "urn:decentraland:off-chain:base-avatars:BaseFemale" - ], - "mainFile": "female/Look6_Tshirt_B (1).glb", - "contents": [ - { - "key": "female/Look6_Tshirt_B (1).glb", - "url": "https://peer-ec2.decentraland.org/content/contents/QmcgddP4L8CEKfpJ4cSZhswKownnYnpwEP4eYgTxmFdav8" - } - ], - "overrideHides": [], - "overrideReplaces": [] - } - ] -}, -"image": "https://peer-ec2.decentraland.org/content/contents/QmPnzQZWAMP4Grnq6phVteLzHeNxdmbRhKuFKqhHyVMqrK", -"thumbnail": "https://peer-ec2.decentraland.org/content/contents/QmcnBFjhyFShGo9gWk2ETbMRDudiX7yjn282djYCAjoMuL", -"metrics": { - "triangles": 3400, - "materials": 2, - "textures": 2, - "meshes": 2, - "bodies": 2, - "entities": 1 -} -``` - -`replaces`, `overrides`, `hides`, and different body shapes representation for the same asset are needed for Decentraland in order to render the 3D asset correctly. - -Using `namespaces` instead of objects like the ones below make it easy for the specific vendor/third-parties to access and index the required models. Moreover, `styles` do not exist because there are no standards around for how an asset will be rendered. As I mentioned above, each metaverse for example uses its own armature and aesthetic. There is no Decentraland-style or TheSandbox-style that other metaverses use. Each of them is unique and specific for the sake of the platform's reason of being. Projects like Cryptoavatars are trying to push different standards but without luck for the same reasons related to the uniquity of the armature/animations/metadata. - -```jsonc -{ - "id": "model", - "type": "model/gltf+json", - "style": "Decentraland", - "uri": "..." -}, - -// Or - -{ - "id": "model", - "type": "model/gltf+json", - "style": "humanoide", - "uri": "..." -}, -``` - -With `namespaces`, each vendor will know how to render an asset by doing: - -```ts -fetch(metadata.namespaces["PROJECT_NAME"].uri).then(res => render(res)) -``` - -The idea behind extending the [EIP-721](./eip-721.md) metadata schema is for backward compatibility. Most projects on Ethereum use non-upgradeable contracts. If this EIP required new implementations of those contracts, they would have to be re-deployed. This is time-consuming and wastes money. Leveraging EIP-721's existing metadata field minimizes the number of changes necessary. Finally, the JSON metadata is already used to store representations using the `image` field. It seems reasonable to have all the representations of an asset in the same place. - -## Backwards Compatibility - -Existing projects that can't modify the metadata response (schema), may be able to create a new smart contract that based on the `tokenId` returns the updated metadata schema. Of course, the projects may need to accept these linked smart contracts as valid in order to fetch the metadata by the `tokenURI` function. - -## Security Considerations - -The same security considerations as with [EIP-721](./eip-721.md) apply related to using http gateways or IPFS for the tokenURI method. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4955.md diff --git a/EIPS/eip-4972.md b/EIPS/eip-4972.md index a0a57dba7a3a7c..2974d18317bbce 100644 --- a/EIPS/eip-4972.md +++ b/EIPS/eip-4972.md @@ -1,86 +1 @@ ---- -eip: 4972 -title: Name-Owned Account -description: Name-Owned Account for Social Identity -author: Shu Dong (@dongshu2013), Qi Zhou (@qizhou), Zihao Chen (@zihaoccc) -discussions-to: https://ethereum-magicians.org/t/eip-4972-name-owned-account/8822 -status: Draft -type: Standards Track -category: ERC -created: 2022-04-04 -requires: 137 ---- - -## Abstract - -The ERC suggests expanding the capabilities of the name service, such as ENS, by enabling each human-readable identity to be linked to a single smart contract account that can be controlled by the owner of the name identity. - -## Motivation - -Name itself cannot hold any context. We want to build an extension of name service to give name rich context by offering each name owner an extra ready to use smart contract account, which may help the general smart contract account adoption. With NOA, it is possible to hold assets and information for its name node, opening up new use cases such as name node transfers, which involve transferring ownership of the name node as well as the NOA, including any assets and information it holds. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Name-Owned Account - -An NOA has - -- a human readable name defined by [ERC-137](./eip-137.md); and -- an owned account(NOA), which is an smart contract account whose address is derived from the name; and -- owner(s) of the name that can deploy and manipulate the owned account. - -The following diagram illustrates the relationship between NOA, name node, and name owner, with the ownership being guaranteed by the name service. - - ┌───────────────┐ ┌───────────┐ ┌───────────────┐ - │ Owned Account ◄──own───┤ Name Node ◄───own───┤ Name Owner │ - └───────────────┘ └───────────┘ └───────────────┘ - -### Interface - -The core interface required for a name service to have is: - - interface INameServiceRegistry { - /// @notice get account address owned by the name node - /// @params node represents a name node - /// @return the address of an account - function ownedAccount( - bytes32 node - ) external view returns(address); - } - -The core interface required for the name owned account is: - - interface INameOwnedAccount { - /// @notice get the name node is mapped to this account address - /// @return return a name node - function name() external view returns(bytes32); - - /// @notice get the name service contract address where - /// the name is registered - /// @return return the name service the name registered at - function nameService() external view returns(address); - } - -## Rationale - -To achieve a one-to-one mapping from the name to the NOA, where each NOA's address is derived from the name node, we must include the name node information in each NOA to reflect its name node ownership. The "name()" function can be used to retrieve this property of each NOA and enable reverse tracking to its name node. The "nameService()" function can get the name service contract address where the name is registered, to perform behaviors such as validation checks. Through these two methods, the NOA has the ability to track back to its actual owner who owns the name node. - -## Backwards Compatibility - -The name registry interface is compatible with ERC-137. - -## Reference Implementation - -### Name Owned Account Creation - -The NOA creation is done by a “factory” contract. The factory could be the name service itself and is expected to use CREATE2 (not CREATE) to create the NOA. NOAs should have identical initcode and factory contract in order to achieve deterministic preservation of address. The name node can be used as the salt to guarantee the bijection from name to its owned account. - -## Security Considerations - -No security considerations were found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4972.md diff --git a/EIPS/eip-4973.md b/EIPS/eip-4973.md index 9ddb3bc5efddc6..75d22ba57c610a 100644 --- a/EIPS/eip-4973.md +++ b/EIPS/eip-4973.md @@ -1,203 +1 @@ ---- -eip: 4973 -title: Account-bound Tokens -description: An interface for non-transferrable NFTs binding to an Ethereum account like a legendary World of Warcraft item binds to a character. -author: Tim Daubenschütz (@TimDaub) -discussions-to: https://ethereum-magicians.org/t/eip-4973-non-transferrable-non-fungible-tokens-soulbound-tokens-or-badges/8825 -status: Review -type: Standards Track -category: ERC -created: 2022-04-01 -requires: 165, 712, 721, 1271 ---- - -## Abstract - -Proposes a standard API for account-bound Tokens (ABT) within smart contracts. An ABT is a non-fungible token bound to a single account. ABTs don't implement a canonical interface for transfers. This EIP defines basic functionality to mint, assign, revoke and track ABTs. - -## Motivation - -In the popular MMORPG World of Warcraft, its game designers intentionally took some items out of the world's auction house market system to prevent them from having a publicly-discovered price and limit their accessibility. - -Vanilla WoW's "Thunderfury, Blessed Blade of the Windseeker" was one such legendary item, and it required a forty-person raid, among other sub-tasks, to slay the firelord "Ragnaros" to gain the "Essence of the Firelord," a material needed to craft the sword once. - -Upon voluntary pickup, the sword permanently **binds** to a character's "soul," making it impossible to trade, sell or even swap it between a player's characters. - -In other words, "Thunderfury"'s price was the aggregate of all social costs related to completing the difficult quest line with friends and guild members. Other players spotting Thunderfuries could be sure their owner had slain "Ragnaros," the blistering firelord. - -World of Warcraft players could **trash** legendary and soulbound items like the Thunderfury to permanently remove them from their account. It was their choice to visibly **equip** or **unequip** an item and hence show their achievements to everyone. - -The Ethereum community has expressed a need for non-transferrable, non-fungible, and socially-priced tokens similar to WoW's soulbound items. Popular contracts implicitly implement account-bound interaction rights today. A principled standardization helps interoperability and improves on-chain data indexing. - -The purpose of this document is to make ABTs a reality on Ethereum by creating consensus around a **maximally backward-compatible** but otherwise **minimal** interface definition. - -## Specification - -### Solidity Interface - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -ABTs _must_ implement the interfaces: - -- [ERC-165](./eip-165.md)'s `ERC165` (`0x01ffc9a7`) -- [ERC-721](./eip-721.md)'s `ERC721Metadata` (`0x5b5e139f`) - -ABTs _must not_ implement the interfaces: - -- [ERC-721](./eip-721.md)'s `ERC721` (`0x80ac58cd`) - -An ABT receiver must be able to always call `function unequip(address _tokenId)` to take their ABT off-chain. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.6; - -/// @title Account-bound tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-4973 -/// Note: the ERC-165 identifier for this interface is 0xeb72bb7c -interface IERC4973 { - /// @dev This emits when ownership of any ABT changes by any mechanism. - /// This event emits when ABTs are given or equipped and unequipped - /// (`to` == 0). - event Transfer( - address indexed from, address indexed to, uint256 indexed tokenId - ); - - /// @notice Count all ABTs assigned to an owner - /// @dev ABTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param owner An address for whom to query the balance - /// @return The number of ABTs owned by `address owner`, possibly zero - function balanceOf(address owner) external view returns (uint256); - - /// @notice Find the address bound to an ERC4973 account-bound token - /// @dev ABTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param tokenId The identifier for an ABT. - /// @return The address of the owner bound to the ABT. - function ownerOf(uint256 tokenId) external view returns (address); - - /// @notice Removes the `uint256 tokenId` from an account. At any time, an - /// ABT receiver must be able to disassociate themselves from an ABT - /// publicly through calling this function. After successfully executing this - /// function, given the parameters for calling `function give` or - /// `function take` a token must be re-equipable. - /// @dev Must emit a `event Transfer` with the `address to` field pointing to - /// the zero address. - /// @param tokenId The identifier for an ABT. - function unequip(uint256 tokenId) external; - - /// @notice Creates and transfers the ownership of an ABT from the - /// transaction's `msg.sender` to `address to`. - /// @dev Throws unless `bytes signature` represents a signature of the - // EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` expressing - /// `address to`'s explicit agreement to be publicly associated with - /// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be - /// generated by type-casting the `bytes32` EIP-712 structured data hash to a - /// `uint256`. If `bytes signature` is empty or `address to` is a contract, - /// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must - /// be made to `address to`. A successful execution must result in the - /// `event Transfer(msg.sender, to, tokenId)`. Once an ABT exists as an - /// `uint256 tokenId` in the contract, `function give(...)` must throw. - /// @param to The receiver of the ABT. - /// @param metadata The metadata that will be associated to the ABT. - /// @param signature A signature of the EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` signed by - /// `address to`. - /// @return A unique `uint256 tokenId` generated by type-casting the `bytes32` - /// EIP-712 structured data hash to a `uint256`. - function give(address to, bytes calldata metadata, bytes calldata signature) - external - returns (uint256); - - /// @notice Creates and transfers the ownership of an ABT from an - /// `address from` to the transaction's `msg.sender`. - /// @dev Throws unless `bytes signature` represents a signature of the - /// EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` expressing - /// `address from`'s explicit agreement to be publicly associated with - /// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be - /// generated by type-casting the `bytes32` EIP-712 structured data hash to a - /// `uint256`. If `bytes signature` is empty or `address from` is a contract, - /// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must - /// be made to `address from`. A successful execution must result in the - /// emission of an `event Transfer(from, msg.sender, tokenId)`. Once an ABT - /// exists as an `uint256 tokenId` in the contract, `function take(...)` must - /// throw. - /// @param from The origin of the ABT. - /// @param metadata The metadata that will be associated to the ABT. - /// @param signature A signature of the EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` signed by - /// `address from`. - - /// @return A unique `uint256 tokenId` generated by type-casting the `bytes32` - /// EIP-712 structured data hash to a `uint256`. - function take(address from, bytes calldata metadata, bytes calldata signature) - external - returns (uint256); - - /// @notice Decodes the opaque metadata bytestring of an ABT into the token - /// URI that will be associated with it once it is created on chain. - /// @param metadata The metadata that will be associated to an ABT. - /// @return A URI that represents the metadata. - function decodeURI(bytes calldata metadata) external returns (string memory); -} -``` - -See [ERC-721](./eip-721.md) for a definition of its metadata JSON Schema. - -### [EIP-712](./eip-712.md) Typed Structured Data Hashing and Bytearray Signature Creation - -To invoke `function give(...)` and `function take(...)` a bytearray signature must be created using [EIP-712](./eip-712.md). A tested reference implementation in Node.js is attached at [index.mjs](../assets/eip-4973/sdk/src/index.mjs), [index_test.mjs](../assets/eip-4973/sdk/test/index_test.mjs) and [package.json](../assets/eip-4973/package.json). In Solidity, this bytearray signature can be created as follows: - -```solidity -bytes32 r = 0x68a020a209d3d56c46f38cc50a33f704f4a9a10a59377f8dd762ac66910e9b90; -bytes32 s = 0x7e865ad05c4035ab5792787d4a0297a43617ae897930a6fe4d822b8faea52064; -uint8 v = 27; -bytes memory signature = abi.encodePacked(r, s, v); -``` - -## Rationale - -### Interface - -ABTs shall be maximally backward-compatible but still only expose a minimal and simple to implement interface definition. - -As [ERC-721](./eip-721.md) tokens have seen widespread adoption with wallet providers and marketplaces, using its `ERC721Metadata` interface with [ERC-165](./eip-165.md) for feature-detection potentially allows implementers to support ABTs out of the box. - -If an implementer of [ERC-721](./eip-721.md) properly built [ERC-165](./eip-165.md)'s `function supportsInterface(bytes4 interfaceID)` function, already by recognizing that [ERC-721](./eip-721.md)'s track and transfer interface component with the identifier `0x80ac58cd` is not implemented, transferring of a token should not be suggested as a user interface option. - -Still, since ABTs support [ERC-721](./eip-721.md)'s `ERC721Metadata` extension, wallets and marketplaces should display an account-bound token with no changes needed. - -Although other implementations of account-bound tokens are possible, e.g., by having all transfer functions revert, ABTs are superior as it supports feature detection through [ERC-165](./eip-165.md). - -We expose `function unequip(address _tokenId)` and require it to be callable at any time by an ABT's owner as it ensures an owner's right to publicly disassociate themselves from what has been issued towards their account. - -### Exception handling - -Given the non-transferable between accounts property of ABTs, if a user's keys to an account or a contract get compromised or rotated, a user may lose the ability to associate themselves with the token. In some cases, this can be the desired effect. Therefore, ABT implementers should build re-issuance and revocation processes to enable recourse. We recommend implementing strictly decentralized, permissionless, and censorship-resistant re-issuance processes. - -But this document is deliberately abstaining from offering a standardized form of exception handling in cases where user keys are compromised or rotated. - -In cases where implementers want to make account-bound tokens shareable among different accounts, e.g., to avoid losing access when keys get compromised, we suggest issuing the account-bound token towards a contract's account that implements a multi-signature functionality. - -### Provenance Indexing - -ABTs can be indexed by tracking the emission of `event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)`. As with [ERC-721](./eip-721.md), transfers between two accounts are represented by `address from` and `address to` being non-zero addresses. Unequipping a token is represented through emitting a transfer with `address to` being set to the zero address. Mint operations where `address from` is set to zero don't exist. To avoid being spoofed by maliciously-implemented `event Transfer` emitting contracts, an indexer should ensure that the transaction's sender is equal to `event Transfer`'s `from` value. - -## Backwards Compatibility - -We have adopted the [ERC-165](./eip-165.md) and `ERC721Metadata` functions purposefully to create a high degree of backward compatibility with [ERC-721](./eip-721.md). We have deliberately used [ERC-721](./eip-721.md) terminology such as `function ownerOf(...)`, `function balanceOf(...)` to minimize the effort of familiarization for ABT implementers already familiar with, e.g., [ERC-20](./eip-20.md) or [ERC-721](./eip-721.md). For indexers, we've re-used the widely-implemented `event Transfer` event signature. - -## Reference Implementation - -You can find an implementation of this standard in [ERC-4973-flat.sol](../assets/eip-4973/ERC4973-flat.sol). - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4973.md diff --git a/EIPS/eip-4974.md b/EIPS/eip-4974.md index ea447ceb7f0b80..14b6ae6e1c813c 100644 --- a/EIPS/eip-4974.md +++ b/EIPS/eip-4974.md @@ -1,161 +1 @@ ---- -eip: 4974 -title: Ratings -description: An interface for assigning and managing numerical ratings -author: Daniel Tedesco (@dtedesco1) -discussions-to: https://ethereum-magicians.org/t/8805 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-04-02 -requires: 165 ---- - -## Abstract - -This standard defines a standardized interface for assigning and managing numerical ratings on the Ethereum blockchain. This allows ratings to be codified within smart contracts and recognized by other applications, enabling a wide range of new use cases for tokens. - -## Motivation - -Traditionally, blockchain applications have focused on buying and selling digital assets. However, the asset-centric model has often been detrimental to community-based blockchain projects, as seen in the pay-to-play dynamics of many EVM-based games and DAOs in 2021. - -This proposal addresses this issue by allowing ratings to be assigned to contracts and wallets, providing a new composable primitive for blockchain applications. This allows for a diverse array of new use cases, such as: - -- Voting weight in a DAO: Ratings assigned using this standard can be used to determine the voting weight of members in a decentralized autonomous organization (DAO). For example, a DAO may assign higher ratings to members who have demonstrated a strong track record of contributing to the community, and use these ratings to determine the relative influence of each member in decision-making processes. - -- Experience points in a decentralized game ecosystem: Ratings can be used to track the progress of players in a decentralized game ecosystem, and to reward them for achieving specific milestones or objectives. For example, a game may use ratings to assign experience points to players, which can be used to unlock new content or abilities within the game. - -- Loyalty points for customers of a business: Ratings can be used to track the loyalty of customers to a particular business or service, and to reward them for their continued support. For example, a business may use ratings to assign loyalty points to customers, which can be redeemed for special offers or discounts. - -- Asset ratings for a decentralized insurance company: Ratings can be used to evaluate the risk profile of assets in a decentralized insurance company, and to determine the premiums and coverage offered to policyholders. For example, a decentralized insurance company may use ratings to assess the risk of different types of assets, and to provide lower premiums and higher coverage to assets with lower risk ratings. - -This standard is influenced by the [EIP-20](./eip-20.md) and [EIP-721](./eip-721.md) token standards and takes cues from each in its structure, style, and semantics. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Every compliant contract MUST implement the following interfaces: - -``` -// SPDX-License-Identifier: CC0 - -pragma solidity ^0.8.0; - -/// @title EIP-4974 Ratings -/// @dev See https://eips.ethereum.org/EIPS/EIP-4974 -/// Note: the EIP-165 identifier for this interface is #######. -/// Must initialize contracts with an `operator` address that is not `address(0)`. -interface IERC4974 /* is ERC165 */ { - - /// @dev Emits when operator changes. - /// MUST emit when `operator` changes by any mechanism. - /// MUST ONLY emit by `setOperator`. - event NewOperator(address indexed _operator); - - /// @dev Emits when operator issues a rating. - /// MUST emit when rating is assigned by any mechanism. - /// MUST ONLY emit by `rate`. - event Rating(address _rated, int8 _rating); - - /// @dev Emits when operator removes a rating. - /// MUST emit when rating is removed by any mechanism. - /// MUST ONLY emit by `remove`. - event Removal(address _removed); - - /// @notice Appoint operator authority. - /// @dev MUST throw unless `msg.sender` is `operator`. - /// MUST throw if `operator` address is either already current `operator` - /// or is the zero address. - /// MUST emit an `Appointment` event. - /// @param _operator New operator of the smart contract. - function setOperator(address _operator) external; - - /// @notice Rate an address. - /// MUST emit a Rating event with each successful call. - /// @param _rated Address to be rated. - /// @param _rating Total EXP tokens to reallocate. - function rate(address _rated, int8 _rating) external; - - /// @notice Remove a rating from an address. - /// MUST emit a Remove event with each successful call. - /// @param _removed Address to be removed. - function removeRating(address _removed) external; - - /// @notice Return a rated address' rating. - /// @dev MUST register each time `Rating` emits. - /// SHOULD throw for queries about the zero address. - /// @param _rated An address for whom to query rating. - /// @return int8 The rating assigned. - function ratingOf(address _rated) external view returns (int8); -} - -interface IERC165 { - /// @notice Query if a contract implements an interface. - /// @dev Interface identification is specified in EIP-165. This function - /// uses less than 30,000 gas. - /// @param interfaceID The interface identifier, as specified in EIP-165. - /// @return bool `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise. - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -## Rationale - -### Rating Assignment - -Ratings SHALL be at the sole discretion of the contract operator. This party may be a sports team coach or a multisig DAO wallet. We decide not to specify how governance occurs, but only *that* governance occurs. This allows for a wider range of potential use cases than optimizing for particular decision-making forms. - -This proposal standardizes a control mechanism to allocate community reputation without encouraging financialization of that recognition. While it does not ensure meritocracy, it opens the door. - -### Choice of int8 - -It's signed: Reviewers should be able to give neutral and negative ratings for the wallets and contracts they interact with. This is especially important for decentralized applications that may be subject to malicious actors. - -It's 8bit: The objective here is to keep ratings within some fathomably comparable range. Longer term, this could encourage easy aggregation of ratings, versus using larger numbers where users might employ a great variety of scales. - -### Rating Changes - -Ratings SHOULD allow rating updates by contract operators. If Bob has contributed greatly to the community, but then is caught stealing from Alice, the community may decide this should lower Bob's standing and influence in the community. Again, while this does not ensure an ethical standard within the community, it opens the door. - -Relatedly, ratings SHOULD allow removal of ratings to rescind a rating if the rater does not have confidence in their ability to rate effectively. - -### Interface Detection - -We chose Standard Interface Detection ([EIP-165](./eip-165.md)) to expose the interfaces that a compliant smart contract supports. - -### Metadata Choices - -We have required `name` and `description` functions in the metadata extension. `name` common among major standards for blockchain-based primitives. We included a `description` function that may be helpful for games or other applications with multiple ratings systems. - -We remind implementation authors that the empty string is a valid response to `name` and `description` if you protest to the usage of this mechanism. We also remind everyone that any smart contract can use the same name and description as your contract. How a client may determine which ratings smart contracts are well-known (canonical) is outside the scope of this standard. - -### Drawbacks - -One potential drawback of using this standard is that ratings are subjective and may not always accurately reflect the true value or quality of a contract or wallet. However, the standard provides mechanisms for updating and removing ratings, allowing for flexibility and evolution over time. - -Users identified in the motivation section have a strong need to identify how a contract or community evaluates another. While some users may be proud of ratings they receive, others may rightly or wrongly receive negative ratings from certain contracts. Negative ratings may allow for nefarious activities such as bullying and discrimination. We implore all implementers to be mindful of the consequences of any ratings systems they create with this standard. - -## Backwards Compatibility - -We have adopted the `name` semantics from the EIP-20 and EIP-721 specifications. - -## Reference Implementation - -A reference implementation of this standard can be found in the assets folder. - - -## Security Considerations - -One potential security concern with this standard is the risk of malicious actors assigning false or misleading ratings to contracts or wallets. This could be used to manipulate voting weights in a DAO, or to deceive users into making poor decisions based on inaccurate ratings. - -To address this concern, the standard includes mechanisms for updating and removing ratings, allowing for corrections to be made in cases of false or misleading ratings. Additionally, the use of a single operator address to assign and update ratings provides a single point of control, which can be used to enforce rules and regulations around the assignment of ratings. - -Another potential security concern is the potential for an attacker to gain control of the operator address and use it to manipulate ratings for their own benefit. To mitigate this risk, it is recommended that the operator address be carefully managed and protected, and that multiple parties be involved in its control and oversight. - -Overall, the security of compliant contracts will depend on the careful management and protection of the operator address, as well as the development of clear rules and regulations around the assignment of ratings. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4974.md diff --git a/EIPS/eip-4987.md b/EIPS/eip-4987.md index 8c969ea16508e9..2b05581d07a0bc 100644 --- a/EIPS/eip-4987.md +++ b/EIPS/eip-4987.md @@ -1,269 +1 @@ ---- -eip: 4987 -title: Held token interface -description: Interface to query ownership and balance of held tokens -author: Devin Conley (@devinaconley) -discussions-to: https://ethereum-magicians.org/t/eip-4987-held-token-standard-nfts-defi/7117 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-09-21 -requires: 20, 165, 721, 1155 ---- - -## Abstract - -The proposed standard defines a lightweight interface to expose functional ownership and balances of held tokens. A held token is a token owned by a contract. This standard may be implemented by smart contracts which hold [EIP-20](./eip-20.md), [EIP-721](./eip-721.md), or [EIP-1155](./eip-1155.md) tokens and is intended to be consumed by both on-chain and off-chain systems that rely on ownership and balance verification. - -## Motivation - -As different areas of crypto (DeFi, NFTs, etc.) converge and composability improves, there will more commonly be a distinction between the actual owner (likely a contract) and the functional owner (likely a user) of a token. Currently, this results in a conflict between mechanisms that require token deposits and systems that rely on those tokens for ownership or balance verification. - -This proposal aims to address that conflict by providing a standard interface for token holders to expose ownership and balance information. This will allow users to participate in these DeFi mechanisms without giving up existing token utility. Overall, this would greatly increase interoperability across systems, benefiting both users and protocol developers. - -Example implementers of this ERC standard include - -- staking or farming contracts -- lending pools -- time lock or vesting vaults -- fractionalized NFT contracts -- smart contract wallets - -Example consumers of this ERC standard include - -- governance systems -- gaming -- PFP verification -- art galleries or showcases -- token based membership programs - -## Specification - -Smart contracts implementing the `ERC20` held token standard MUST implement all of the functions in the `IERC20Holder` interface. - -Smart contracts implementing the `ERC20` held token standard MUST also implement `ERC165` and return true when the interface ID `0x74c89d54` is passed. - -```solidity -/** - * @notice the ERC20 holder standard provides a common interface to query - * token balance information - */ -interface IERC20Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenAmount held token amount - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 tokenAmount - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenAmount held token amount - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 tokenAmount - ); - - /** - * @notice get the held balance of the token owner - * @dev should throw for invalid queries and return zero for no balance - * @param tokenAddress held token address - * @param owner functional token owner - * @return held token balance - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - returns (uint256); -} - -``` - -Smart contracts implementing the `ERC721` held token standard MUST implement all of the functions in the `IERC721Holder` interface. - -Smart contracts implementing the `ERC721` held token standard MUST also implement `ERC165` and return true when the interface ID `0x16b900ff` is passed. - -```solidity -/** - * @notice the ERC721 holder standard provides a common interface to query - * token ownership and balance information - */ -interface IERC721Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId - ); - - /** - * @notice get the functional owner of a held token - * @dev should throw for invalid queries and return zero for a token ID that is not held - * @param tokenAddress held token address - * @param tokenId held token ID - * @return functional token owner - */ - function heldOwnerOf(address tokenAddress, uint256 tokenId) - external - view - returns (address); - - /** - * @notice get the held balance of the token owner - * @dev should throw for invalid queries and return zero for no balance - * @param tokenAddress held token address - * @param owner functional token owner - * @return held token balance - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - returns (uint256); -} -``` - -Smart contracts implementing the `ERC1155` held token standard MUST implement all of the functions in the `IERC1155Holder` interface. - -Smart contracts implementing the `ERC1155` held token standard MUST also implement `ERC165` and return true when the interface ID `0xced24c37` is passed. - -```solidity -/** - * @notice the ERC1155 holder standard provides a common interface to query - * token balance information - */ -interface IERC1155Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - * @param tokenAmount held token amount - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 tokenAmount - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - * @param tokenAmount held token amount - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 tokenAmount - ); - - /** - * @notice get the held balance of the token owner - * @dev should throw for invalid queries and return zero for no balance - * @param tokenAddress held token address - * @param owner functional token owner - * @param tokenId held token ID - * @return held token balance - */ - function heldBalanceOf( - address tokenAddress, - address owner, - uint256 tokenId - ) external view returns (uint256); -} -``` - -## Rationale - -This interface is designed to be extremely lightweight and compatible with any existing token contract. Any token holder contract likely already stores all relevant information, so this standard is purely adding a common interface to expose that data. - -The token address parameter is included to support contracts that can hold multiple token contracts simultaneously. While some contracts may only hold a single token address, this is more general to either scenario. - -Separate interfaces are proposed for each token type (EIP-20, EIP-721, EIP-1155) because any contract logic to support holding these different tokens is likely independent. In the scenario where a single contract does hold multiple token types, it can simply implement each appropriate held token interface. - - -## Backwards Compatibility - -Importantly, the proposed specification is fully compatible with all existing EIP-20, EIP-721, and EIP-1155 token contracts. - -Token holder contracts will need to be updated to implement this lightweight interface. - -Consumer of this standard will need to be updated to respect this interface in any relevant ownership logic. - - -## Reference Implementation - -A full example implementation including [interfaces](../assets/eip-4987/IERC721Holder.sol), a vault [token holder](../assets/eip-4987/Vault.sol), and a [consumer](../assets/eip-4987/Consumer.sol), can be found at `assets/eip-4987/`. - -Notably, consumers of the `IERC721Holder` interface can do a chained lookup for the owner of any specific token ID using the following logic. - -```solidity - /** - * @notice get the functional owner of a token - * @param tokenId token id of interest - */ - function getOwner(uint256 tokenId) external view returns (address) { - // get raw owner - address owner = token.ownerOf(tokenId); - - // if owner is not contract, return - if (!owner.isContract()) { - return owner; - } - - // check for token holder interface support - try IERC165(owner).supportsInterface(0x16b900ff) returns (bool ret) { - if (!ret) return owner; - } catch { - return owner; - } - - // check for held owner - try IERC721Holder(owner).heldOwnerOf(address(token), tokenId) returns (address user) { - if (user != address(0)) return user; - } catch {} - - return owner; - } -``` - - -## Security Considerations - -Consumers of this standard should be cautious when using ownership information from unknown contracts. A bad actor could implement the interface, but report invalid or malicious information with the goal of manipulating a governance system, game, membership program, etc. - -Consumers should also verify the overall token balance and ownership of the holder contract as a sanity check. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-4987.md diff --git a/EIPS/eip-5005.md b/EIPS/eip-5005.md index e1dc3822ae4efa..1f47fb0261fd65 100644 --- a/EIPS/eip-5005.md +++ b/EIPS/eip-5005.md @@ -1,233 +1 @@ ---- -eip: 5005 -title: Zodiac Modular Accounts -description: Composable interoperable programmable accounts -author: Auryn Macmillan (@auryn-macmillan), Kei Kreutler (@keikreutler) -discussions-to: https://ethereum-magicians.org/t/eip-zodiac-a-composable-design-philosophy-for-daos/8963 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-04-14 -requires: 165 ---- - -## Abstract -This EIP standardizes interfaces for composable and interoperable tooling for programmable Ethereum accounts. These interfaces separate contract accounts ("avatars") from their authentication and execution logic ("guards" and "modules"). Avatars implement the `IAvatar` interface, and guards implement the `IGuard` interface. Modules may take any form. - -## Motivation -Currently, most programmable accounts (like DAO tools and frameworks) are built as monolithic systems where the authorization and execution logic are coupled, either within the same contract or in a tightly integrated system of contracts. This needlessly inhibits the flexibility of these tools and encourages platform lock-in via high switching costs. - -By using the this EIP standard to separate concerns (decoupling authentication and execution logic), users are able to: - -1. Enable flexible, module-based control of programmable accounts -2. Easily switch between tools and frameworks without unnecessary overhead. -3. Enable multiple control mechanism in parallel. -4. Enable cross-chain / cross-layer governance. -5. Progressively decentralize their governance as their project and community matures. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -This EIP consists of four key concepts: - -- **Avatars** are programmable Ethereum accounts. Avatars are the address that holds balances, owns systems, executes transaction, is referenced externally, and ultimately represents your DAO. Avatars MUST implement the `IAvatar` interface. -- **Modules** are contracts enabled by an avatar that implement some execution logic. -- **Modifiers** are contracts that sit between modules and avatars to modify the module's behavior. For example, they might enforce a delay on all functions a module attempts to execute or limit the scope of transactions that can be initiated by the module. Modifiers MUST implement the `IAvatar` interface. -- **Guards** are contracts that MAY be enabled on modules or modifiers and implement pre- or post-checks on each transaction executed by those modules or modifiers. This allows avatars to do things like limit the scope of addresses and functions that a module or modifier can call or ensure a certain state is never changed by a module or modifier. Guards MUST expose the `IGuard` interface. Modules, modifiers, and avatars that wish to be guardable MUST inherit `Guardable`, MUST call `checkTransaction()` before triggering execution on their target, and MUST call `checkAfterExecution()` after execution is complete. - -```solidity -/// @title Avatar - A contract that manages modules that can execute transactions via this contract. - -pragma solidity >=0.7.0 <0.9.0; - -import "./Enum.sol"; - - -interface IAvatar { - event EnabledModule(address module); - event DisabledModule(address module); - event ExecutionFromModuleSuccess(address indexed module); - event ExecutionFromModuleFailure(address indexed module); - - /// @dev Enables a module on the avatar. - /// @notice Can only be called by the avatar. - /// @notice Modules should be stored as a linked list. - /// @notice Must emit EnabledModule(address module) if successful. - /// @param module Module to be enabled. - function enableModule(address module) external; - - /// @dev Disables a module on the avatar. - /// @notice Can only be called by the avatar. - /// @notice Must emit DisabledModule(address module) if successful. - /// @param prevModule Address that pointed to the module to be removed in the linked list - /// @param module Module to be removed. - function disableModule(address prevModule, address module) external; - - /// @dev Allows a Module to execute a transaction. - /// @notice Can only be called by an enabled module. - /// @notice Must emit ExecutionFromModuleSuccess(address module) if successful. - /// @notice Must emit ExecutionFromModuleFailure(address module) if unsuccessful. - /// @param to Destination address of module transaction. - /// @param value Ether value of module transaction. - /// @param data Data payload of module transaction. - /// @param operation Operation type of module transaction: 0 == call, 1 == delegate call. - function execTransactionFromModule( - address to, - uint256 value, - bytes memory data, - Enum.Operation operation - ) external returns (bool success); - - /// @dev Allows a Module to execute a transaction and return data - /// @notice Can only be called by an enabled module. - /// @notice Must emit ExecutionFromModuleSuccess(address module) if successful. - /// @notice Must emit ExecutionFromModuleFailure(address module) if unsuccessful. - /// @param to Destination address of module transaction. - /// @param value Ether value of module transaction. - /// @param data Data payload of module transaction. - /// @param operation Operation type of module transaction: 0 == call, 1 == delegate call. - function execTransactionFromModuleReturnData( - address to, - uint256 value, - bytes memory data, - Enum.Operation operation - ) external returns (bool success, bytes memory returnData); - - /// @dev Returns if an module is enabled - /// @return True if the module is enabled - function isModuleEnabled(address module) external view returns (bool); - - /// @dev Returns array of modules. - /// @param start Start of the page. - /// @param pageSize Maximum number of modules that should be returned. - /// @return array Array of modules. - /// @return next Start of the next page. - function getModulesPaginated(address start, uint256 pageSize) - external - view - returns (address[] memory array, address next); -} -``` - -```solidity -pragma solidity >=0.7.0 <0.9.0; - -import "./Enum.sol"; - -interface IGuard { - function checkTransaction( - address to, - uint256 value, - bytes memory data, - Enum.Operation operation, - uint256 safeTxGas, - uint256 baseGas, - uint256 gasPrice, - address gasToken, - address payable refundReceiver, - bytes memory signatures, - address msgSender - ) external; - - function checkAfterExecution(bytes32 txHash, bool success) external; -} - -``` - -```solidity -pragma solidity >=0.7.0 <0.9.0; - -import "./Enum.sol"; -import "./BaseGuard.sol"; - -/// @title Guardable - A contract that manages fallback calls made to this contract -contract Guardable { - address public guard; - - event ChangedGuard(address guard); - - /// `guard_` does not implement IERC165. - error NotIERC165Compliant(address guard_); - - /// @dev Set a guard that checks transactions before execution. - /// @param _guard The address of the guard to be used or the 0 address to disable the guard. - function setGuard(address _guard) external { - if (_guard != address(0)) { - if (!BaseGuard(_guard).supportsInterface(type(IGuard).interfaceId)) - revert NotIERC165Compliant(_guard); - } - guard = _guard; - emit ChangedGuard(guard); - } - - function getGuard() external view returns (address _guard) { - return guard; - } -} -``` - -```solidity -pragma solidity >=0.7.0 <0.9.0; - -import "./Enum.sol"; -import "./IERC165.sol"; -import "./IGuard.sol"; - -abstract contract BaseGuard is IERC165 { - function supportsInterface(bytes4 interfaceId) - external - pure - override - returns (bool) - { - return - interfaceId == type(IGuard).interfaceId || // 0xe6d7a83a - interfaceId == type(IERC165).interfaceId; // 0x01ffc9a7 - } - - /// @dev Module transactions only use the first four parameters: to, value, data, and operation. - /// Module.sol hardcodes the remaining parameters as 0 since they are not used for module transactions. - function checkTransaction( - address to, - uint256 value, - bytes memory data, - Enum.Operation operation, - uint256 safeTxGas, - uint256 baseGas, - uint256 gasPrice, - address gasToken, - address payable refundReceiver, - bytes memory signatures, - address msgSender - ) external virtual; - - function checkAfterExecution(bytes32 txHash, bool success) external virtual; -} -``` - -```solidity -pragma solidity >=0.7.0 <0.9.0; - -/// @title Enum - Collection of enums - -contract Enum { - - enum Operation {Call, DelegateCall} - -} -``` - -## Rationale -The interface defined in this standard is designed to be mostly compatible with most popular programmable accounts in use right now, to minimize the need for changes to existing tooling. - -## Backwards Compatibility -No backward compatibility issues are introduced by this standard. - -## Security Considerations -There are some considerations that module developers and users should take into account: -1. **Modules have absolute control:** Modules have absolute control over any avatar on which they are enabled, so any module implementation should be treated as security critical and users should be vary cautious about enabling new modules. ONLY ENABLE MODULES THAT YOU TRUST WITH THE FULL VALUE OF THE AVATAR. -2. **Race conditions:** A given avatar may have any number of modules enabled, each with unilateral control over the safe. In such cases, there may be race conditions between different modules and/or other control mechanisms. -3. **Don't brick your avatar:** There are no safeguards to stop you adding or removing modules. If you remove all of the modules that let you control an avatar, the avatar will cease to function and all funds will be stuck. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5005.md diff --git a/EIPS/eip-5006.md b/EIPS/eip-5006.md index f735e39f9c3acb..0712b66f696a66 100644 --- a/EIPS/eip-5006.md +++ b/EIPS/eip-5006.md @@ -1,158 +1 @@ ---- -eip: 5006 -title: Rental NFT, NFT User Extension -description: Add a user role with restricted permissions to ERC-1155 tokens -author: Lance (@LanceSnow), Anders (@0xanders), Shrug -discussions-to: https://ethereum-magicians.org/t/eip5006-erc-1155-usage-rights-extension/8941 -status: Final -type: Standards Track -category: ERC -created: 2022-04-12 -requires: 165, 1155 ---- - -## Abstract - -This standard is an extension of [ERC-1155](./eip-1155.md). It proposes an additional role (`user`) which can be granted to addresses that represent a `user` of the assets rather than an `owner`. - -## Motivation - -Like [ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md) tokens may have utility of some kind. The people who “use” the token may be different than the people who own it (such as in a rental). Thus, it would be useful to have separate roles for the “owner” and the “user” so that the “user” would not be able to take actions that the owner could (for example, transferring ownership). - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5006 { - struct UserRecord { - uint256 tokenId; - address owner; - uint64 amount; - address user; - uint64 expiry; - } - - /** - * @dev Emitted when permission for `user` to use `amount` of `tokenId` token owned by `owner` - * until `expiry` are given. - */ - event CreateUserRecord( - uint256 recordId, - uint256 tokenId, - uint64 amount, - address owner, - address user, - uint64 expiry - ); - - /** - * @dev Emitted when record of `recordId` are deleted. - */ - event DeleteUserRecord(uint256 recordId); - - /** - * @dev Returns the usable amount of `tokenId` tokens by `account`. - */ - function usableBalanceOf(address account, uint256 tokenId) - external - view - returns (uint256); - - /** - * @dev Returns the amount of frozen tokens of token type `id` by `account`. - */ - function frozenBalanceOf(address account, uint256 tokenId) - external - view - returns (uint256); - - /** - * @dev Returns the `UserRecord` of `recordId`. - */ - function userRecordOf(uint256 recordId) - external - view - returns (UserRecord memory); - - /** - * @dev Gives permission to `user` to use `amount` of `tokenId` token owned by `owner` until `expiry`. - * - * Emits a {CreateUserRecord} event. - * - * Requirements: - * - * - If the caller is not `owner`, it must be have been approved to spend ``owner``'s tokens - * via {setApprovalForAll}. - * - `owner` must have a balance of tokens of type `id` of at least `amount`. - * - `user` cannot be the zero address. - * - `amount` must be greater than 0. - * - `expiry` must after the block timestamp. - */ - function createUserRecord( - address owner, - address user, - uint256 tokenId, - uint64 amount, - uint64 expiry - ) external returns (uint256); - - /** - * @dev Atomically delete `record` of `recordId` by the caller. - * - * Emits a {DeleteUserRecord} event. - * - * Requirements: - * - * - the caller must have allowance. - */ - function deleteUserRecord(uint256 recordId) external; -} - -``` - -The `supportsInterface` method MUST return `true` when called with `0xc26d96cc`. - -## Rationale - -This model is intended to facilitate easy implementation. The following are some problems that are solved by this standard: - -### Clear Rights Assignment - -With Dual “owner” and “user” roles, it becomes significantly easier to manage what lenders and borrowers can and cannot do with the NFT (in other words, their rights).  For example, for the right to transfer ownership, the project simply needs to check whether the address taking the action represents the owner or the user and prevent the transaction if it is the user.  Additionally, owners can control who the user is and it is easy for other projects to assign their own rights to either the owners or the users. - -### Easy Third-Party Integration - -In the spirit of permissionless interoperability, this standard makes it easier for third-party protocols to manage NFT usage rights without permission from the NFT issuer or the NFT application. Once a project has adopted the additional `user` role, any other project can directly interact with these features and implement their own type of transaction. For example, a PFP NFT using this standard can be integrated into both a rental platform where users can rent the NFT for 30 days AND, at the same time, a mortgage platform where users can use the NFT while eventually buying ownership of the NFT with installment payments. This would all be done without needing the permission of the original PFP project. - -## Backwards Compatibility - -As mentioned in the specifications section, this standard can be fully ERC compatible by adding an extension function set, and there are no conflicts between [ERC-5006](./eip-5006.md) and ERC-1155. - -In addition, new functions introduced in this standard have many similarities with the existing functions in ERC-1155. This allows developers to easily adopt the standard quickly. - -## Test Cases - -Test cases are included in [test.js](../assets/eip-5006/test/test.ts). - -Run in terminal: - -1. ```cd ../assets/eip-5006``` -1. ```npm install``` -1. ```npx hardhat test``` - -## Reference Implementation - -See [`ERC5006.sol`](../assets/eip-5006/contracts/ERC5006.sol). - -## Security Considerations - -This EIP standard can completely protect the rights of the owner, the owner can change the NFT user, the user can not transfer the NFT. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5006.md diff --git a/EIPS/eip-5007.md b/EIPS/eip-5007.md index a378410137f03a..3feaf8534259d4 100644 --- a/EIPS/eip-5007.md +++ b/EIPS/eip-5007.md @@ -1,149 +1 @@ ---- -eip: 5007 -title: Time NFT, ERC-721 Time Extension -description: Add start time and end time to ERC-721 tokens. -author: Anders (@0xanders), Lance (@LanceSnow), Shrug -discussions-to: https://ethereum-magicians.org/t/eip-5007-eip-721-time-extension/8924 -status: Final -type: Standards Track -category: ERC -created: 2022-04-13 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It proposes some additional functions (`startTime`, `endTime`) to help with on-chain time management. - -## Motivation - -Some NFTs have a defined usage period and cannot be used outside of that period. With traditional NFTs that do not include time information, if you want to mark a token as invalid or enable it at a specific time, you need to actively submit a transaction—a process both cumbersome and expensive. - -Some existing NFTs contain time functions, but their interfaces are not consistent, so it is difficult to develop third-party platforms for them. - -By introducing these functions (`startTime`, `endTime`), it is possible to enable and disable NFTs automatically on chain. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -```solidity -/** - * @dev the ERC-165 identifier for this interface is 0xf140be0d. - */ -interface IERC5007 /* is IERC721 */ { - /** - * @dev Returns the start time of the NFT as a UNIX timestamp. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function startTime(uint256 tokenId) external view returns (uint64); - - /** - * @dev Returns the end time of the NFT as a UNIX timestamp. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function endTime(uint256 tokenId) external view returns (uint64); - -} -``` - -The **composable extension** is OPTIONAL for this standard. This allows your NFT to be minted from an existing NFT or to merge two NFTs into one NFT. - -```solidity -/** - * @dev the ERC-165 identifier for this interface is 0x75cf3842. - */ -interface IERC5007Composable /* is IERC5007 */ { - /** - * @dev Returns the asset id of the time NFT. - * Only NFTs with same asset id can be merged. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function assetId(uint256 tokenId) external view returns (uint256); - - /** - * @dev Split an old token to two new tokens. - * The assetId of the new token is the same as the assetId of the old token - * - * Requirements: - * - * - `oldTokenId` must exist. - * - `newToken1Id` must not exist. - * - `newToken1Owner` cannot be the zero address. - * - `newToken2Id` must not exist. - * - `newToken2Owner` cannot be the zero address. - * - `splitTime` require(oldToken.startTime <= splitTime && splitTime < oldToken.EndTime) - */ - function split( - uint256 oldTokenId, - uint256 newToken1Id, - address newToken1Owner, - uint256 newToken2Id, - address newToken2Owner, - uint64 splitTime - ) external; - - /** - * @dev Merge the first token and second token into the new token. - * - * Requirements: - * - * - `firstTokenId` must exist. - * - `secondTokenId` must exist. - * - require((firstToken.endTime + 1) == secondToken.startTime) - * - require((firstToken.assetId()) == secondToken.assetId()) - * - `newTokenOwner` cannot be the zero address. - * - `newTokenId` must not exist. - */ - function merge( - uint256 firstTokenId, - uint256 secondTokenId, - address newTokenOwner, - uint256 newTokenId - ) external; -} -``` - -## Rationale - -### Time Data Type - -The max value of `uint64` is 18,446,744,073,709,551,615. As a timestamp, 18,446,744,073,709,551,615 is about year 584,942,419,325. `uint256` is too big for C, C++, Java, Go, etc, and `uint64` is natively supported by mainstream programming languages. - -## Backwards Compatibility - -This standard is fully ERC-721 compatible. - -## Test Cases - -Test cases are included in [test.js](../assets/eip-5007/test/test.js). - -Run in terminal: - -```shell -cd ../assets/eip-5007 -npm install truffle -g -npm install -truffle test -``` - -## Reference Implementation - -See [`ERC5007.sol`](../assets/eip-5007/contracts/ERC5007.sol). - -## Security Considerations - -No security issues found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5007.md diff --git a/EIPS/eip-5008.md b/EIPS/eip-5008.md index 5dde543fc75c27..a45e2c429e40ff 100644 --- a/EIPS/eip-5008.md +++ b/EIPS/eip-5008.md @@ -1,78 +1 @@ ---- -eip: 5008 -title: ERC-721 Nonce Extension -description: Add a `nonce` function to ERC-721. -author: Anders (@0xanders), Lance (@LanceSnow), Shrug -discussions-to: https://ethereum-magicians.org/t/eip5008-eip-721-nonce-and-metadata-update-extension/8925 -status: Last Call -last-call-deadline: 2023-08-15 -type: Standards Track -category: ERC -created: 2022-04-10 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It proposes adding a `nonce` function to ERC-721 tokens. - -## Motivation - -Some orders of NFT marketplaces have been attacked and the NFTs sold at a lower price than the current market floor price. This can happen when users transfer an NFT to another wallet and, later, back to the original wallet. This reactivates the order, which may list the token at a much lower price than the owner would have intended. - -This EIP proposes adding a `nonce` property to ERC-721 tokens, and the `nonce` will be changed when a token is transferred. If a `nonce` is added to an order, the order can be checked to avoid attacks. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -```solidity - -/// @dev the ERC-165 identifier for this interface is 0xce03fdab. -interface IERC5008 /* is IERC165 */ { - /// @notice Emitted when the `nonce` of an NFT is changed - event NonceChanged(uint256 tokenId, uint256 nonce); - - /// @notice Get the nonce of an NFT - /// Throws if `tokenId` is not a valid NFT - /// @param tokenId The id of the NFT - /// @return The nonce of the NFT - function nonce(uint256 tokenId) external view returns(uint256); -} -``` - -The `nonce(uint256 tokenId)` function MUST be implemented as `view`. - -The `supportsInterface` method MUST return `true` when called with `0xce03fdab`. - -## Rationale - -At first `transferCount` was considered as function name, but there may some case to change the `nonce` besides transfer, such as important properties changed, then we changed `transferCount` to `nonce`. - -## Backwards Compatibility - -This standard is compatible with ERC-721. - -## Test Cases - -Test cases are included in [test.js](../assets/eip-5008/test/test.ts). - -Run: - -```sh -cd ../assets/eip-5008 -npm install -npm run test -``` - -## Reference Implementation - -See [`ERC5008.sol`](../assets/eip-5008/contracts/ERC5008.sol). - -## Security Considerations - -No security issues found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5008.md diff --git a/EIPS/eip-5018.md b/EIPS/eip-5018.md index b83e93bfdfe544..b53edb2fa7816c 100644 --- a/EIPS/eip-5018.md +++ b/EIPS/eip-5018.md @@ -1,161 +1 @@ ---- -eip: 5018 -title: Filesystem-like Interface for Contracts -description: An interface to provide access to binary objects similar to filesystems. -author: Qi Zhou (@qizhou) -discussions-to: https://ethereum-magicians.org/t/eip-5018-directory-standard/8958 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-04-18 ---- - - -## Abstract - -The following standardizes an API for directories and files within smart contracts, similar to traditional filesystems. -This standard provides basic functionality to read/write binary objects of any size, as well as allow reading/writing chunks of the object if the object is too large to fit in a single transaction. - -## Motivation - -A standard interface allows any binary objects on EVM-based blockchain to be re-used by other dApps. - -With [EIP-4804](./eip-4804.md), we are able to locate a Web3 resource on blockchain using HTTP-style URIs. One application of Web3 resources are web contents that are referenced within a directory using relative paths such as HTML/SVG. This standard proposes a contract-based directory to simplify the mapping between local web contents and on-chain web contents. Further, with relative paths referenced in the web contents and EIP-4804, the users will have a consistent view of the web contents locally and on-chain. - -## Specification - -### Directory - -#### Methods - -##### write - -Writes binary `data` to the file `name` in the directory by an account with write permission. - -``` -function write(bytes memory name, bytes memory data) external payable -``` - -##### read - -Returns the binary `data` from the file `name` in the directory and existence of the file. - -``` -function read(bytes memory name) external view returns (bytes memory data, bool exist) -``` - -##### fallback read - -Returns the binary `data` from the file `prefixedName` (prefixed with `/`) in the directory. - -``` -fallback(bytes calldata prefixedName) external returns (bytes memory data) -``` - -##### size - -Returns the size of the `data` from the file `name` in the directory and the number of chunks of the data. - -``` -function size(bytes memory name) external view returns (uint256 size, uint256 chunks) -``` - -##### remove - -Removes the file `name` in the directory and returns the number of chunks removed (0 means the file does not exist) by an account with write permission. - -``` -function remove(bytes memory name) external returns (uint256 numOfChunksRemoved) -``` - -##### countChunks - -Returns the number of chunks of the file `name`. - -``` -function countChunks(bytes memory name) external view returns (uint256 numOfChunks); -``` - -##### writeChunk - -Writes a chunk of data to the file by an account with write permission. The write will fail if `chunkId > numOfChunks`, i.e., the write must append the file or replace the existing chunk. - -``` - function writeChunk(bytes memory name, uint256 chunkId, bytes memory chunkData) external payable; -``` - -##### readChunk - -Returns the chunk data of the file `name` and the existence of the chunk. - -``` -function readChunk(bytes memory name, uint256 chunkId) external view returns (bytes memory chunkData, bool exist); -``` - -##### chunkSize - -Returns the size of a chunk of the file `name` and the existence of the chunk. - -``` -function chunkSize(bytes memory name, uint256 chunkId) external view returns (uint256 chunkSize, bool exist); -``` - -##### removeChunk - -Removes a chunk of the file `name` and returns `false` if such chunk does not exist. The method should be called by an account with write permission. - -``` -function removeChunk(bytes memory name, uint256 chunkId) external returns (bool exist); -``` - -##### truncate - -Removes the chunks of the file `name` in the directory from the given `chunkId` and returns the number of chunks removed by an account with write permission. When `chunkId = 0`, the method is essentially the same as `remove()`. - -``` -function truncate(bytes memory name, uint256 chunkId) external returns (uint256 numOfChunksRemoved); -``` - -##### getChunkHash - -Returns the hash value of the chunk data. - -``` -function getChunkHash(bytes memory name, uint256 chunkId) external view returns (bytes32); -``` - -## Rationale - -One issue of uploading the web contents to the blockchain is that the web contents may be too large to fit into a single transaction. As a result, the standard provides chunk-based operations so that uploading a content can be split into several transactions. Meanwhile, the read operation can be done in a single transaction, i.e., with a single Web3 URL defined in EIP-4804. - -### Interactions Between Unchunked/Chunked Functions - -`read` method should return the concatenated chunked data written by `writeChunk` method. The following gives some examples of the interactions: - -- `read("hello.txt")` => "" (file is empty) -- `writeChunk("hello.txt", 0, "abc")` will succeed -- `read("hello.txt")` => "abc" -- `writeChunk("hello.txt", 1, "efg")` will succeed -- `read("hello.txt")` => "abcefg" -- `writeChunk("hello.txt", 0, "aaa")` will succeed (replace chunk 0's data) -- `read("hello.txt")` => "aaaefg" -- `writeChunk("hello.txt", 3, "hij")` will fail because the operation is not replacement or append. - -With `writeChunk` method, we allow writing a file with external data that exceeds the current calldata limit (e.g., 1.8MB now), and it is able to read the whole file in a single `read` method (which is friendly for large web objects such as HTML/SVG/PNG/JPG, etc). - -For `write` method, calling a `write` method will replace all data chunks of the file with `write` method data, and one implementation can be: - -1. `writeChunk(filename, chunkId=0, data_from_write)` to chunk 0 with the same `write` method data; and -2. `truncate(filename, chunkId=1)`, which will remove the rest chunks. - -## Backwards Compatibility - -No backwards compatibility issues were identified. - -## Security Considerations - -No security considerations were found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5018.md diff --git a/EIPS/eip-5023.md b/EIPS/eip-5023.md index c3e36b57e2b83c..4ab320600b778f 100644 --- a/EIPS/eip-5023.md +++ b/EIPS/eip-5023.md @@ -1,169 +1 @@ ---- -eip: 5023 -title: Shareable Non-Fungible Token -description: An interface for creating value-holding tokens shareable by multiple owners -author: Jarno Marttila (@yaruno), Martin Moravek (@mmartinmo) -discussions-to: https://ethereum-magicians.org/t/new-nft-concept-shareable-nfts/8681 -status: Final -type: Standards Track -category: ERC -created: 2022-01-28 -requires: 165 ---- - -## Abstract - -This EIP standardizes an interface for non-fungible value-holding shareable tokens. Shareability is accomplished by minting copies of existing tokens for new recipients. Sharing and associated events allow the construction of a graph describing who has shared what to which party. - - -## Motivation - -NFT standards such as [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) have been developed to standardize scarce digital resources. However, many non-fungible digital resources need not be scarce. - -We have attempted to capture positive externalities in ecosystems with new types of incentive mechanisms that exhibit anti-rival logic, serve as an unit of accounting and function as medium of sharing. We envision that shareable tokens can work both as incentives but also as representations of items that are typically digital in their nature and gain more value as they are shared. - -These requirements have set us to define shareable NFTs and more specifically a variation of shareable NFTs called non-transferable shareable NFTs. These shareable NFTs can be “shared” in the same way digital goods can be shared, at an almost zero technical transaction cost. We have utilized them to capture anti-rival value in terms of accounting positive externalities in an economic system. - -Typical NFT standards such as EIP-721 and EIP-1155 do not define a sharing modality. Instead ERC standards define interfaces for typical rival use cases such as token minting and token transactions that the NFT contract implementations should fulfil. The ‘standard contract implementations' may extend the functionalities of these standards beyond the definition of interfaces. The shareable tokens that we have designed and developed in our experiments are designed to be token standard compatible at the interface level. However the implementation of token contracts may contain extended functionalities to match the requirements of the experiments such as the requirement of 'shareability'. In reflection to standard token definitions, shareability of a token could be thought of as re-mintability of an existing token to another party while retaining the original version of it. - -Sharing is an interesting concept as it can be thought and perceived in different ways. For example, when we talk about sharing we can think about it is as digital copying, giving a copy of a digital resource while retaining a version by ourselves. Sharing can also be fractional or sharing could be about giving rights to use a certain resource. The concept of shareability and the context of shareability can take different forms and one might use different types of implementatins for instances of shareable tokens. Hence we haven't restricted that the interface should require any specific token type. - -Shareable tokens can be made non-transferable at the contract implementaiton level. Doing so, makes them shareable non-transferable tokens. In the reference implementation we have distilled a general case from our use cases that defines a shareable non-transferable NFTs using the shareable NFT interface. - -We believe that the wider audience should benefit from an abstraction level higher definition for shareability, such as this interface implementation, that defines minimum amount of functions that would be implemented to satisfy the concept of shareability. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// Note: the ERC-165 identifier for this interface is 0xded6338b -interface IERC5023 is IERC165 { - - /// @dev This emits when a token is shared, reminted and given to another wallet that isn't function caller - event Share(address indexed from, address indexed to, uint256 indexed tokenId, uint256 derivedFromtokenId); - - /// @dev Shares, remints an existing token, gives a newly minted token a fresh token id, keeps original token at function callers possession and transfers newly minted token to receiver which should be another address than function caller. - function share(address to, uint256 tokenIdToBeShared) external returns(uint256 newTokenId); - -} -``` - -The Share event is expected to be emitted when function method share is succesfully called and a new token on basis of a given token id is minted and transferred to a recipient. - -## Rationale - -Current NFT standards define transferable non-fungible tokens, but not shareable non-fungible tokens. To be able to create shareable NFTs we see that existing NFT contracts could be extended with an interface which defines the basic principles of sharing, namely the Event of sharing and the function method of sharing. Definition of how transferability of tokens should be handled is left to the contract implementor. In case transfering is left enable shareable tokens behave similarily to the existing tokens, except when they are shared, a version of token is retained. In case transfering is disabled, shareable tokens become shareable non-transferable tokens, where they can be minted and given or shared to other people, but they cannot be transferred away. - -Imagine that Bob works together with Alice on a project. Bob earns an unique NFT indicating that he has made effort to the project, but Bob feels that his accomplishments are not only out of his own accord. Bob wants to share his token with Alice to indicate that also Alice deserves recognition of having put effort on their project. Bob initiates token sharing by calling `Share` method on the contract which has his token and indicates which one of his tokens he wishes to share and to whom by passing address and token id parameters. A new token is minted for Alice and a `Share` event is initiated to communicate that it was Bob whom shared his token to Alice by logging addresses who shared a token id to whose address and which token id was this new token derived from. - -Over time, a tree-like structures can be formed from the Share event information. If Bob shared to Alice, and Alice shared further to Charlie and Alice also shared to David a rudimentary tree structure forms out from sharing activity. This share event data can be later on utilized to gain more information of share activities that the tokens represent. - -```text -B -> A -> C - \ - > D -``` - -These tree structures can be further aggregated and collapsed to network representations e.g. social graphs on basis of whom has shared to whom over a span of time. E.g. if Bob shared a token to Alice, and Alice has shared a different token to Charlie and Bob has shared a token to Charlie, connections form between all these parties through sharing activities. - -```text - B----A----C - \_______/ -``` - -## Backwards Compatibility - -This proposal is backwards compatible with EIP-721 and EIP-1155. - -## Reference Implementation - -Following reference implementation demonstrates a general use case of one of our pilots. In this case a shareable non-transferable token represents a contribution done to a community that the contract owner has decided to merit with a token. Contract owner can mint a merit token and give it to a person. This token can be further shared by the receiver to other parties for example to share the received merit to others that have participated or influenced his contribution. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC5023.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract ShareableERC721 is ERC721URIStorage, Ownable, IERC5023 /* EIP165 */ { - - string baseURI; - - uint256 internal _currentIndex; - - constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} - - function mint( - address account, - uint256 tokenId - ) external onlyOwner { - _mint(account, tokenId); - } - - function setTokenURI( - uint256 tokenId, - string memory tokenURI - ) external { - _setTokenURI(tokenId, tokenURI); - } - - function setBaseURI(string memory baseURI_) external { - baseURI = baseURI_; - } - - function _baseURI() internal view override returns (string memory) { - return baseURI; - } - - function share(address to, uint256 tokenIdToBeShared) external returns(uint256 newTokenId) { - require(to != address(0), "ERC721: mint to the zero address"); - require(_exists(tokenIdToBeShared), "ShareableERC721: token to be shared must exist"); - - require(msg.sender == ownerOf(tokenIdToBeShared), "Method caller must be the owner of token"); - - string memory _tokenURI = tokenURI(tokenIdToBeShared); - _mint(to, _currentIndex); - _setTokenURI(_currentIndex, _tokenURI); - - emit Share(msg.sender, to, _currentIndex, tokenIdToBeShared); - - return _currentIndex; - } - - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - revert('In this reference implementation tokens are not transferrable'); - } - - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - revert('In this reference implementation tokens are not transferrable'); - } -} - -``` - -## Security Considerations - -Reference implementation should not be used as is in production. -There are no other security considerations related directly to implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5023.md diff --git a/EIPS/eip-5050.md b/EIPS/eip-5050.md index aff711cd8babf9..6e920b58e549e0 100644 --- a/EIPS/eip-5050.md +++ b/EIPS/eip-5050.md @@ -1,351 +1 @@ ---- -eip: 5050 -title: Interactive NFTs with Modular Environments -description: Action messaging and discovery protocol for interactions on and between NFTs -author: Alexi (@alexi) -discussions-to: https://ethereum-magicians.org/t/eip-5050-nft-interaction-standard/9922 -status: Stagnant -type: Standards Track -category: ERC -created: 2021-4-18 -requires: 165, 173, 721, 1155, 1820, 4906 ---- - -## Abstract - -This standard defines a broadly applicable action messaging protocol for the transmission of user-initiated actions between tokens. Modular statefulness is achieved with optional state controller contracts (i.e. environments) that manage shared state, and provide arbitration and settlement of the action process. - -## Motivation - -Tokenized item standards such as [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) serve as the objects of the Ethereum computing environment. A growing number of projects are seeking to build interactivity and *"digital physics"* into NFTs, especially in the contexts of gaming and decentralized identity. A standard action messaging protocol will allow this physics layer to be developed in the same open, Ethereum-native way as the objects they operate on. - -The messaging protocol outlined defines how an action is initiated and transmitted between tokens and (optional) shared state environments. It is paired with a common interface for defining functionality that allows off-chain services to aggregate and query supported contracts for functionality and interoperability; creating a discoverable, human-readable network of interactive token contracts. Not only can contracts that implement this standard be automatically discovered by such services, their *policies for interaction* can be as well. This allows clients to easily discover compatible senders and receivers, and allowed actions. - -Aggregators can also parse action event logs to derive analytics on new action types, trending/popular/new interactive contracts, which token and state contract pairs users are likely to interact with, and other discovery tools to facilitate interaction. - -### Benefits - -1. Make interactive token contracts **discoverable and usable** by applications -2. Create a decentralized "digital physics" layer for gaming and other applications -3. Provide developers a simple solution with viable validity guarantees to make dynamic NFTs and other tokens -4. Allow for generalized action bridges to transmit actions between chains (enabling actions on L1 assets to be saved to L2s, L1 assets to interact with L2 assets, and L2 actions to be "rolled-up"/finalized on L1). - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -Smart contracts implementing this EIP standard MUST implement the [EIP-165](./eip-165.md) supportsInterface function and MUST return the constant value `true` if the `IERC5050Sender` interface ID `0xc8c6c9f3` and/or the `IERC5050Receiver` interface ID `0x1a3f02f4` is passed through the `interfaceID` argument (depending on which interface(s) the contract implements). - -```solidity -pragma solidity ^0.8.0; - -/// @param _address The address of the interactive object -/// @param tokenId The token that is interacting (optional) -struct Object { - address _address; - uint256 _tokenId; -} - -/// @param selector The bytes4(keccack256()) encoding of the action string -/// @param user The address of the sender -/// @param from The initiating object -/// @param to The receiving object -/// @param state The state controller contract -/// @param data Additional data with no specified format -struct Action { - bytes4 selector; - address user; - Object from; - Object to; - address state; - bytes data; -} - -/// @title EIP-5050 Interactive NFTs with Modular Environments -interface IERC5050Sender { - /// @notice Send an action to the target address - /// @dev The action's `fromContract` is automatically set to `address(this)`, - /// and the `from` parameter is set to `msg.sender`. - /// @param action The action to send - function sendAction(Action memory action) external payable; - - /// @notice Check if an action is valid based on its hash and nonce - /// @dev When an action passes through all three possible contracts - /// (`fromContract`, `to`, and `state`) the `state` contract validates the - /// action with the initiating `fromContract` using a nonced action hash. - /// This hash is calculated and saved to storage on the `fromContract` before - /// action handling is initiated. The `state` contract calculates the hash - /// and verifies it and nonce with the `fromContract`. - /// @param _hash The hash to validate - /// @param _nonce The nonce to validate - function isValid(bytes32 _hash, uint256 _nonce) external returns (bool); - - /// @notice Retrieve list of actions that can be sent. - /// @dev Intended for use by off-chain applications to query compatible contracts, - /// and to advertise functionality in human-readable form. - function sendableActions() external view returns (string[] memory); - - /// @notice Change or reaffirm the approved address for an action - /// @dev The zero address indicates there is no approved address. - /// Throws unless `msg.sender` is the `_account`, or an authorized - /// operator of the `_account`. - /// @param _account The account of the account-action pair to approve - /// @param _action The action of the account-action pair to approve - /// @param _approved The new approved account-action controller - function approveForAction( - address _account, - bytes4 _action, - address _approved - ) external returns (bool); - - /// @notice Enable or disable approval for a third party ("operator") to conduct - /// all actions on behalf of `msg.sender` - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// an unbounded number of operators per owner. - /// @param _operator Address to add to the set of authorized operators - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAllActions(address _operator, bool _approved) - external; - - /// @notice Get the approved address for an account-action pair - /// @dev Throws if `_tokenId` is not a valid NFT. - /// @param _account The account of the account-action to find the approved address for - /// @param _action The action of the account-action to find the approved address for - /// @return The approved address for this account-action, or the zero address if - /// there is none - function getApprovedForAction(address _account, bytes4 _action) - external - view - returns (address); - - /// @notice Query if an address is an authorized operator for another address - /// @param _account The address on whose behalf actions are performed - /// @param _operator The address that acts on behalf of the account - /// @return True if `_operator` is an approved operator for `_account`, false otherwise - function isApprovedForAllActions(address _account, address _operator) - external - view - returns (bool); - - /// @dev This emits when an action is sent (`sendAction()`) - event SendAction( - bytes4 indexed name, - address _from, - address indexed _fromContract, - uint256 _tokenId, - address indexed _to, - uint256 _toTokenId, - address _state, - bytes _data - ); - - /// @dev This emits when the approved address for an account-action pair - /// is changed or reaffirmed. The zero address indicates there is no - /// approved address. - event ApprovalForAction( - address indexed _account, - bytes4 indexed _action, - address indexed _approved - ); - - /// @dev This emits when an operator is enabled or disabled for an account. - /// The operator can conduct all actions on behalf of the account. - event ApprovalForAllActions( - address indexed _account, - address indexed _operator, - bool _approved - ); -} - -interface IERC5050Receiver { - /// @notice Handle an action - /// @dev Both the `to` contract and `state` contract are called via - /// `onActionReceived()`. - /// @param action The action to handle - function onActionReceived(Action calldata action, uint256 _nonce) - external - payable; - - /// @notice Retrieve list of actions that can be received. - /// @dev Intended for use by off-chain applications to query compatible contracts, - /// and to advertise functionality in human-readable form. - function receivableActions() external view returns (string[] memory); - - /// @dev This emits when a valid action is received. - event ActionReceived( - bytes4 indexed name, - address _from, - address indexed _fromContract, - uint256 _tokenId, - address indexed _to, - uint256 _toTokenId, - address _state, - bytes _data - ); -} -``` - -### Action Naming - -Actions SHOULD use dot-separation for namespacing (e.g. `"spells.cast"` specifies the `"cast"` action with namespace `"spells"`), and arrow-separation for sequence specification (e.g. `"settle>build"` indicating `"settle"` must be received before `"build"`). - -### How State Contracts Work - -Actions do not require that a state contract be used. Actions can be transmitted from one token contract (`Object`) to another, or from a user to a single token contract. In these cases, the sending and receiving contracts each control their own state. - -State contracts allow arbitrary senders and receivers to share a user-specified state environment. Each `Object` MAY define its own action handling, which MAY include reading from the state contract during, but the action MUST be finalized by the state contract. This means the state contract serves as ground truth. - -The intended workflow is for state contracts to define stateful game environments, typically with a custom `IState` interface for use by other contracts. `Objects` register with state contracts to initialize their state. Then, users commit actions using a specific state contract to make things happen in the game. - -The modularity of state contracts allows multiple copies of the same or similar "game environment" to be created and swapped in or out by the client. There are many ways this modularity can be used: - -- Aggregator services can analyze action events to determine likely state contracts for a given sender/receiver -- Sender/receiver contracts can require a specific state contract -- Sender/receiver contracts can allow any state contract, but set a default. This is important for NFTs that change their render based on state. This default can also be configurable by the token holder. -- State contracts can be bridges to state contracts on another chain, allowing for L1-verification, L2-storage usage pattern (validate action with layer-1 assets, save on l2 where storage is cheaper). - -#### Example - -State Contract `FightGame` defines a fighting game environment. Token holders call `FightGame.register(contract, tokenId)` to randomly initialize their stats (strength/hp/etc.). An account which holds a registered token A of contract `Fighters`, calls `Fighters.sendAction(AttackAction)`, specifying token A from `Fighters` as the sender, token B from `Pacifists` contract as the receiver, and `FightGame` as the state contract. - -The action is passed to token B, which may handle the action in whatever way it wants before passing the action to the `FightGame` state contract. The state contract can verify the stored action hash with the `Fighters` contract to validate the action is authentic before updating the stats if the tokens, dealing damage to token B. - -Tokens A and B may update their metadata based on stats in the `FightGame` state contract, or based on their own stored data updated in response to sending/receiving actions. - -### Extensions - -#### Interactive - -Some contracts may have custom user interfaces that facilitate interaction. - -```solidity -pragma solidity ^0.8.0; - -/// @title EIP-5050 Interactive NFTs with Modular Environments -interface IERC5050Interactive { - function interfaceURI(bytes4 _action) external view returns (string); -} -``` - -#### Action Proxies - -Action proxies can be used to support backwards compatibility with non-upgradeable contracts, and potentially for cross-chain action bridging. - -They can be implemented using a modified version of [EIP-1820](./eip-1820.md#erc-1820-registry-smart-contract) that allows [EIP-173](./eip-173.md) contract owners to call `setManager()`. - -#### Controllable - -Users of this standard may want to allow trusted contracts to control the action process to provide security guarantees, and support action bridging. Controllers step through the action chain, calling each contract individually in sequence. - -Contracts that support Controllers SHOULD ignore require/revert statements related to action verification, and MUST NOT pass the action to the next contract in the chain. - -```solidity -pragma solidity ^0.8.0; - -/// @title EIP-5050 Action Controller -interface IControllable { - - /// @notice Enable or disable approval for a third party ("controller") to force - /// handling of a given action without performing EIP-5050 validity checks. - /// @dev Emits the ControllerApproval event. The contract MUST allow - /// an unbounded number of controllers per action. - /// @param _controller Address to add to the set of authorized controllers - /// @param _action Selector of the action for which the controller is approved / disapproved - /// @param _approved True if the controller is approved, false to revoke approval - function setControllerApproval(address _controller, bytes4 _action, bool _approved) - external; - - /// @notice Enable or disable approval for a third party ("controller") to force - /// action handling without performing EIP-5050 validity checks. - /// @dev Emits the ControllerApproval event. The contract MUST allow - /// an unbounded number of controllers per action. - /// @param _controller Address to add to the set of authorized controllers - /// @param _approved True if the controller is approved, false to revoke approval - function setControllerApprovalForAll(address _controller, bool _approved) - external; - - /// @notice Query if an address is an authorized controller for a given action. - /// @param _controller The trusted third party address that can force action handling - /// @param _action The action selector to query against - /// @return True if `_controller` is an approved operator for `_account`, false otherwise - function isApprovedController(address _controller, bytes4 _action) - external - view - returns (bool); - - /// @dev This emits when a controller is enabled or disabled for the given - /// action. The controller can force `action` handling on the emitting contract, - /// bypassing the standard EIP-5050 validity checks. - event ControllerApproval( - address indexed _controller, - bytes4 indexed _action, - bool _approved - ); - - /// @dev This emits when a controller is enabled or disabled for all actions. - /// Disabling all action approval for a controller does not override explicit action - /// action approvals. Controller's approved for all actions can force action handling - /// on the emitting contract for any action. - event ControllerApprovalForAll( - address indexed _controller, - bool _approved - ); -} -``` - -#### Metadata Update - -Interactive NFTs are likely to update their metadata in response to certain actions and developers MAY want to implement [EIP-4906](./eip-4906.md) event emitters. - -## Rationale - -The critical features of this interactive token standard are that it 1) creates a common way to define, advertise, and conduct object interaction, 2) enables optional, brokered statefulness with *useful* validity assurances at minimum gas overhead, 3) is easy for developers to implement, and 4) is easy for end-users to use. - -### Action Names & Selectors - -Actions are advertised using human-readable strings, and processed using function selectors (`bytes4(keccack256(action_key))`). Human-readable strings allow end-users to easily interpret functionality, while function selectors allow efficient comparison operations on arbitrarily long action keys. This scheme also allows for simple namespacing and sequence specification. - -Off-chain services can easily convert the strings to `bytes4` selector encoding when interacting with contracts implementing this EIP or parsing `SendAction` and `ActionReceived` event logs. - -### Validation - -Validation of the initiating contract via a hash of the action data was satisfactory to nearly everyone surveyed and was the most gas efficient verification solution explored. We recognize that this solution does not allow the receiving and state contracts to validate the initiating `user` account beyond using `tx.origin`, which is vulnerable to phishing attacks. - -We considered using a signed message to validate user-intiation, but this approach had two major drawbacks: - -1. **UX** users would be required to perform two steps to commit each action (sign the message, and send the transaction) -2. **Gas** performing signature verification is computationally expensive - -Most importantly, the consensus among the developers surveyed is that strict user validation is not necessary because the concern is only that malicious initiating contracts will phish users to commit actions *with* the malicious contract's assets. **This protocol treats the initiating contract's token as the prime mover, not the user.** Anyone can tweet at Bill Gates. Any token can send an action to another token. Which actions are accepted, and how they are handled is left up to the contracts. High-value actions can be reputation-gated via state contracts, or access-gated with allow/disallow-lists. [`Controllable`](#controllable) contracts can also be used via trusted controllers as an alternative to action chaining. - -*Alternatives considered: action transmitted as a signed message, action saved to reusable storage slot on initiating contract* - -### State Contracts - -Moving state logic into dedicated, parameterized contracts makes state an action primitive and prevents state management from being obscured within the contracts. Specifically, it allows users to decide which "environment" to commit the action in, and allows the initiating and receiving contracts to share state data without requiring them to communicate. - -The specifics of state contract interfaces are outside the scope of this standard, and are intended to be purpose-built for unique interactive environments. - -### Gas and Complexity (regarding action chaining) - -Action handling within each contract can be arbitrarily complex, and there is no way to eliminate the possibility that certain contract interactions will run out of gas. However, developers SHOULD make every effort to minimize gas usage in their action handler methods, and avoid the use of for-loops. - -*Alternatives considered: multi-request action chains that push-pull from one contract to the next.* - -## Backwards Compatibility - -Non-upgradeable, already deployed token contracts will not be compatible with this standard unless a proxy registry extension is used. - -## Reference Implementation - -A reference implementation is included in `../assets/eip-5050` with a simple stateless example [`ExampleToken2Token.sol`](../assets/eip-5050/ExampleToken2Token.sol), and a stateful example [`ExampleStateContract.sol`](../assets/eip-5050/ExampleStateContract.sol) - -## Security Considerations - -The core security consideration of this protocol is action validation. Actions are passed from one contract to another, meaning it is not possible for the receiving contract to natively verify that the caller of the initiating contract matches the `action.from` address. One of the most important contributions of this protocol is that it provides an alternative to using signed messages, which require users to perform two operations for every action committed. - -As discussed in [Validation](#validation), this is viable because the initiating contract / token is treated as the prime mover, not the user. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5050.md diff --git a/EIPS/eip-5058.md b/EIPS/eip-5058.md index bac05416a7aeaf..a5f695b24cff60 100644 --- a/EIPS/eip-5058.md +++ b/EIPS/eip-5058.md @@ -1,206 +1 @@ ---- -eip: 5058 -title: Lockable Non-Fungible Tokens -description: Lockable EIP-721 tokens -author: Tyler (@radiocaca), Alex (@gojazdev), John (@sfumato00) -discussions-to: https://ethereum-magicians.org/t/eip-5058-erc-721-lockable-standard/9201 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-04-30 -requires: 20, 165, 721 ---- - -## Abstract - -We propose to extend the [EIP-721](./eip-721.md) standard with a secure locking mechanism. The NFT owners approve the operator to lock the NFT through `setLockApprovalForAll()` or `lockApprove()`. The approved operator locks the NFT through `lock()`. The locked NFTs cannot be transferred until the end of the locking period. An immediate use case is to allow NFTs to participate in smart contracts without leaving the wallets of their owners. - -## Motivation - -NFTs, enabled by [EIP-721](./eip-721.md), have exploded in demand. The total market value and the ecosystem continue to grow with more and more blue chip NFTs, which are approximately equivalent to popular intellectual properties in a conventional sense. Despite the vast success, something is left to be desired. Liquidity has always been one of the biggest challenges for NFTs. Several attempts have been made to tackle the liquidity challenge: NFTFi and BendDAO, to name a few. Utilizing the currently prevalent EIP-721 standard, these projects require participating NFTs to be transferred to the projects' contracts, which poses inconveniences and risks to the owners: - -1. Smart contract risks: NFTs can be lost or stolen due to bugs or vulnerabilities in the contracts. -2. Loss of utility: NFTs have utility values, such as profile pictures and bragging rights, which are lost when the NFTs are no longer seen under the owners' custody. -3. Missing Airdrops: The owners can no longer directly receive airdrops entitled to the NFTs. Considering the values and price fluctuation of some of the airdrops, either missing or not getting the airdrop on time can financially impact the owners. - -All of the above are bad UX, and we believe the EIP-721 standard can be improved by adopting a native locking mechanism: - -1. Instead of being transferred to a smart contract, an NFT remains in self-custody but locked. -2. While an NFT is locked, its transfer is prohibited. Other properties remain unaffected. -3. The owners can receive or claim airdrops themselves. - -The value of an NFT can be reflected in two aspects: collection value and utility value. Collection value needs to ensure that the holder's wallet retains ownership of the NFT forever. Utility value requires ensuring that the holder can verify their NFT ownership in other projects. Both of these aspects require that the NFT remain in its owner's wallet. - -The proposed standard allows the underlying NFT assets to be managed securely and conveniently by extending the EIP-721 standard to natively support common NFTFi use cases including locking, staking, lending, and crowdfunding. We believe the proposed standard will encourage NFT owners to participate more actively in NFTFi projects and, hence, improve the livelihood of the whole NFT ecosystem. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Lockable EIP-721 **MUST** implement the `IERC5058` interfaces: - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.8; - -/** - * @dev EIP-721 Non-Fungible Token Standard, optional lockable extension - * ERC721 Token that can be locked for a certain period and cannot be transferred. - * This is designed for a non-escrow staking contract that comes later to lock a user's NFT - * while still letting them keep it in their wallet. - * This extension can ensure the security of user tokens during the staking period. - * If the nft lending protocol is compatible with this extension, the trouble caused by the NFT - * airdrop can be avoided, because the airdrop is still in the user's wallet - */ -interface IERC5058 { - /** - * @dev Emitted when `tokenId` token is locked by `operator` from `from`. - */ - event Locked(address indexed operator, address indexed from, uint256 indexed tokenId, uint256 expired); - - /** - * @dev Emitted when `tokenId` token is unlocked by `operator` from `from`. - */ - event Unlocked(address indexed operator, address indexed from, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables `approved` to lock the `tokenId` token. - */ - event LockApproval(address indexed owner, address indexed approved, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables or disables (`approved`) `operator` to lock all of its tokens. - */ - event LockApprovalForAll(address indexed owner, address indexed operator, bool approved); - - /** - * @dev Returns the locker who is locking the `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function lockerOf(uint256 tokenId) external view returns (address locker); - - /** - * @dev Lock `tokenId` token until the block number is greater than `expired` to be unlocked. - * - * Requirements: - * - * - `tokenId` token must be owned by `owner`. - * - `expired` must be greater than block.number - * - If the caller is not `owner`, it must be approved to lock this token - * by either {lockApprove} or {setLockApprovalForAll}. - * - * Emits a {Locked} event. - */ - function lock(uint256 tokenId, uint256 expired) external; - - /** - * @dev Unlock `tokenId` token. - * - * Requirements: - * - * - `tokenId` token must be owned by `owner`. - * - the caller must be the operator who locks the token by {lock} - * - * Emits a {Unlocked} event. - */ - function unlock(uint256 tokenId) external; - - /** - * @dev Gives permission to `to` to lock `tokenId` token. - * - * Requirements: - * - * - The caller must own the token or be an approved lock operator. - * - `tokenId` must exist. - * - * Emits an {LockApproval} event. - */ - function lockApprove(address to, uint256 tokenId) external; - - /** - * @dev Approve or remove `operator` as an lock operator for the caller. - * Operators can call {lock} for any token owned by the caller. - * - * Requirements: - * - * - The `operator` cannot be the caller. - * - * Emits an {LockApprovalForAll} event. - */ - function setLockApprovalForAll(address operator, bool approved) external; - - /** - * @dev Returns the account lock approved for `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function getLockApproved(uint256 tokenId) external view returns (address operator); - - /** - * @dev Returns if the `operator` is allowed to lock all of the assets of `owner`. - * - * See {setLockApprovalForAll} - */ - function isLockApprovedForAll(address owner, address operator) external view returns (bool); - - /** - * @dev Returns if the `tokenId` token is locked. - */ - function isLocked(uint256 tokenId) external view returns (bool); - - /** - * @dev Returns the `tokenId` token lock expired time. - */ - function lockExpiredTime(uint256 tokenId) external view returns (uint256); -} -``` - -## Rationale - -### NFT lock approvals - -An NFT owner can give another trusted operator the right to lock his NFT through the approve functions. The `lockApprove()` function only approves for the specified NFT, whereas `setLockApprovalForAll()` approves for all NFTs of the collection under the wallet. When a user participates in an NFTFi project, the project contract calls `lock()` to lock the user's NFT. Locked NFTs cannot be transferred, but the NFTFi project contract can use the unlock function `unlock()` to unlock the NFT. - -### NFT lock/unlock - -Authorized project contracts have permission to lock NFT with the `lock` method. Locked NFTs cannot be transferred until the lock time expires. The project contract also has permission to unlock NFT in advance through the `unlock` function. Note that only the address of the locked NFT has permission to unlock that NFT. - -### NFT lock period - -When locking an NFT, one must specify the lock expiration block number, which must be greater than the current block number. When the current block number exceeds the expiration block number, the NFT is automatically released and can be transferred. - -### Bound NFT - -Bound NFT is an extension of this EIP, which implements the ability to mint a boundNFT during the NFT locking period. The boundNFT is identical to the locked NFT metadata and can be transferred. However, a boundNFT only exists during the NFT locking period and will be destroyed after the NFT is unlocked. -BoundNFT can be used to lend, as a staking credential for the contract. The credential can be locked in the contract, but also to the user. In NFT leasing, boundNFT can be rented to users because boundNFT is essentially equivalent to NFT. This consensus, if accepted by all projects, boundNFT will bring more creativity to NFT. - -### Bound NFT Factory - -Bound NFT Factory is a common boundNFT factory, similar to Uniswap's [EIP-20](./eip-20.md) pairs factory. It uses the create2 method to create a boundNFT contract address for any NFT deterministic. BoundNFT contract that has been created can only be controlled by the original NFT contract. - - -## Backwards Compatibility - -This standard is compatible with EIP-721. - -## Test Cases - -Test cases written using hardhat can be found [here](../assets/eip-5058/test/test.ts) - -## Reference Implementation - -You can find an implementation of this standard in the [assets](../assets/eip-5058/ERC5058.sol) folder. - -## Security Considerations - -After being locked, the NFT can not be transferred, so before authorizing locking rights to other project contracts, you must confirm that the project contract can unlock NFT. Otherwise there is a risk of NFT being permanently locked. It is recommended to give a reasonable locking period in use for projects. NFT can be automatically unlocked, which can reduce the risk to a certain extent. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5058.md diff --git a/EIPS/eip-5094.md b/EIPS/eip-5094.md index ed4fa85d83ad45..2c9af33c52d5fd 100644 --- a/EIPS/eip-5094.md +++ b/EIPS/eip-5094.md @@ -1,95 +1 @@ ---- -eip: 5094 -title: URL Format for Ethereum Network Switching -description: A way of representing various network configurations as URLs. -author: Luc van Kampen (@lucemans), Jakob Helgesson (@svemat01), Joshua Hendrix (@thejoshuahendrix) -discussions-to: https://ethereum-magicians.org/t/5094-uri-format-for-ethereum-network-switching/9277 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-05-13 -requires: 681, 831 ---- - -## Abstract - -This standard includes all needed information for adding a network to a wallet via URL, by including parameters such as `chainId`, `rpc_url`, `chain_name` and others, such that the network configuration is provided through the URL itself. - -## Motivation - -As observed with the use of [EIP-681](./eip-681.md) and its implementation in current mobile wallets, transactions can be made, approved, viewed, and used. However, if the wallet is instructed to perform a transaction on a chain they have not yet been configured before, the operation tends to fail. - -This is understandable, as the `chain_id` provided makes up only one part of what is required to connect to a network. This EIP aims to introduce a new type of URL for usage with deep-linking, QR, and more, to allow users to seamlessly add new networks to their (for ex. mobile) wallet to then be able to more easily partake in `pay-`, `tx-`, or other Ethereum URL interactions. - -As an extension to [EIP-831](./eip-831.md) and neighboring [EIP-681](./eip-681.md) and [EIP-2400](./eip-2400.md), this document aims to standardize the addition of new networks and switching thereof through the means of URLs. User convenience in this case is primary. - -Introduction of this EIP is meant to bridge to a safer RPC listing system to be introduced in the near future. - -## Specification - -### Syntax - -Network Switching URLs contain "ethereum" in their schema (protocol) part and are constructed as follows: - - network_add = erc831_part "add" "@" chain_id [ "/" ] "?" parameters - erc831_part = "ethereum:network-" - chain_id = 1*DIGIT - parameters = parameter *( "&" parameter ) - parameter = key "=" value - key = required_keys / optional_keys - required_keys = "rpc_url" / "chain_name" - optional_keys = "name" / "symbol" / "decimals" / "explorer_url" / "icon_url" - value = STRING / number - number = 1*DIGIT - -`STRING` is a URL-encoded Unicode string of arbitrary length, where delimiters and the -percentage symbol (`%`) are mandatorily hex-encoded with a `%` prefix. - -If the *key* in the parameter is `decimals` the *value* MUST be a `number`. - -### Semantics - -`chain_id` is mandatory and denotes the decimal chain ID, such that we have the identifier of the network we would like to add. - -`rpc_url` is represented as an array of RPC URLs. A minimum of 1 `rpc_url` MUST be present, in the format of `rpc_url=https%3A%2F%2Fpolygon-rpc.com`, or when multiple present `rpc_url=https%3A%2F%2Fpolygon-rpc.com&rpc_url=https%3A%2F%2Frpc-mainnet.matic.network`. - -`chain_name` is required to specify the name of the network to be added. - -`name` and `symbol` if provided, SHOULD be a human-readable string representing the native token. - -`decimals` if provided, MUST be a non-negative integer representing the decimal precision of the native token. - -`explorer_url` if provided, MUST specify one or more URLs pointing to block explorer web sites for the chain. - -`icon_url` if provided, MUST specify one or more URLs pointing to reasonably sized images that can be used to visually identify the chain. - -An example of adding a network with RPC endpoints `https://rpc-polygon.com` and `https://rpc-mainnet.matic.network`, the name `Polygon Mainnet`, token `Matic`, symbol `MATIC`, decimals `18`, explorer at `https://polygonscan.com/`, and Chain ID `137` would look as follows: - -```URL -ethereum:network-add@137/?chain_name=Polygon%20Mainnet&rpc_url=https%3A%2F%2Frpc-polygon.com&rpc_url=https%3A%2F%2Frpc-mainnet.matic.network&name=Matic&symbol=MATIC&decimals=18&explorer_url=https%3A%2F%2Fpolygonscan.com -``` - -## Rationale - -In furtherance of the Ethereum URL saga, network configuration is a needed addition to the possibility of Ethereum URLs. This would improve functionality for URLs, and offer non-mainnet users a way to connect without needing to configure their wallet by hand. - -The URL follows [EIP-831](./eip-831.md) with the `PREFIX` being `network` and the `PAYLOAD` being a composite of `add` and [EIP-681](./eip-681.md)-like `chain_id` and parameters. - -The choice for `PREFIX` being `network` is to allow further expansion and allow variants following the pattern `network-x`. - -An example URL for adding the Optimism Network - -```URL -ethereum:network-add@10/?chain_name=Optimistic%20Ethereum -&rpc_url=https%3A%2F%2Fmainnet.optimism.io&name=Ethereum&symbol=ETH&decimals=18&explorer_url=https%3A%2F%2Foptimistic.etherscan.io -``` - -The specification allows for a multitude of `rpc_url` and `explorer_url` to be specified. This is done such to overlap with parsing of the `TYPE` mentioned in [EIP-681](./eip-681.md). - -## Security Considerations - -URLs can be malformed to deceive users. Users SHOULD confirm source of URL before using any links. As well as checking source and transaction details before confirming any transactions. Applications SHOULD display network config, prior to network addition, such that users can confirm the validity of the network configuration being added. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5094.md diff --git a/EIPS/eip-5095.md b/EIPS/eip-5095.md index a2f9899399d970..c4676755059b46 100644 --- a/EIPS/eip-5095.md +++ b/EIPS/eip-5095.md @@ -1,551 +1 @@ ---- -eip: 5095 -title: Principal Token -description: Principal tokens (zero-coupon tokens) are redeemable for a single underlying EIP-20 token at a future timestamp. -author: Julian Traversa (@JTraversa), Robert Robbins (@robrobbins), Alberto Cuesta Cañada (@alcueca) -discussions-to: https://ethereum-magicians.org/t/eip-5095-principal-token-standard/9259 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-05-01 -requires: 20, 2612 ---- - -## Abstract - -Principal tokens represent ownership of an underlying [EIP-20](./eip-20.md) token at a future timestamp. - -This specification is an extension on the [EIP-20](./eip-20.md) token that provides basic functionality for depositing -and withdrawing tokens and reading balances and the [EIP-2612](./eip-2612.md) specification that provides -[EIP-712](./eip-712.md) signature based approvals. - -## Motivation - -Principal tokens lack standardization which has led to a difficult to navigate development space and diverse implementation -schemes. - -The primary examples include yield tokenization platforms which strip future yield leaving a principal -token behind, as well as fixed-rate money-markets which utilize principal tokens as a medium -to lend/borrow. - -This inconsistency in implementation makes integration difficult at the application layer as well as -wallet layer which are key catalysts for the space's growth. -Developers are currently expected to implement individual adapters for each principal token, as well as adapters for -their pool contracts, and many times adapters for their custodial contracts as well, wasting significant developer resources. - -## Specification - -All Principal Tokens (PTs) MUST implement [EIP-20](./eip-20.md) to represent ownership of future underlying redemption. -If a PT is to be non-transferrable, it MAY revert on calls to `transfer` or `transferFrom`. -The [EIP-20](./eip-20.md) operations `balanceOf`, `transfer`, `totalSupply`, etc. operate on the Principal Token balance. - -All Principal Tokens MUST implement [EIP-20](./eip-20.md)'s optional metadata extensions. -The `name` and `symbol` functions SHOULD reflect the underlying token's `name` and `symbol` in some way, as well as the origination protocol, and in the case of yield tokenization protocols, the origination money-market. - -All Principal Tokens MAY implement [EIP-2612](./eip-2612.md) to improve the UX of approving PTs on various integrations. - -### Definitions: - -- underlying: The token that Principal Tokens are redeemable for at maturity. - Has units defined by the corresponding [EIP-20](./eip-20.md) contract. -- maturity: The timestamp (unix) at which a Principal Token matures. Principal Tokens become redeemable for underlying at or after this timestamp. -- fee: An amount of underlying or Principal Token charged to the user by the Principal Token. Fees can exist on redemption or post-maturity yield. -- slippage: Any difference between advertised redemption value and economic realities of PT redemption, which is not accounted by fees. - -### Methods - -#### `underlying` - -The address of the underlying token used by the Principal Token for accounting, and redeeming. - -MUST be an EIP-20 token contract. - -MUST _NOT_ revert. - -```yaml -- name: underlying - type: function - stateMutability: view - - inputs: [] - - outputs: - - name: underlyingAddress - type: address -``` - -#### `maturity` - -The unix timestamp (uint256) at or after which Principal Tokens can be redeemed for their underlying deposit. - -MUST _NOT_ revert. - -```yaml -- name: maturity - type: function - stateMutability: view - - inputs: [] - - outputs: - - name: timestamp - type: uint256 -``` - -#### `convertToUnderlying` - -The amount of underlying that would be exchanged for the amount of PTs provided, in an ideal scenario where all the conditions are met. - -Before maturity, the amount of underlying returned is as if the PTs would be at maturity. - -MUST NOT be inclusive of any fees that are charged against redemptions. - -MUST NOT show any variations depending on the caller. - -MUST NOT reflect slippage or other on-chain conditions, when performing the actual redemption. - -MUST NOT revert unless due to integer overflow caused by an unreasonably large input. - -MUST round down towards 0. - -This calculation MAY NOT reflect the "per-user" price-per-principal-token, and instead should reflect the "average-user's" price-per-principal-token, meaning what the average user should expect to see when exchanging to and from. - -```yaml -- name: convertToUnderlying - type: function - stateMutability: view - - inputs: - - name: principalAmount - type: uint256 - - outputs: - - name: underlyingAmount - type: uint256 -``` - -#### `convertToPrincipal` - -The amount of principal tokens that the principal token contract would request for redemption in order to provide the amount of underlying specified, in an ideal scenario where all the conditions are met. - -MUST NOT be inclusive of any fees. - -MUST NOT show any variations depending on the caller. - -MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - -MUST NOT revert unless due to integer overflow caused by an unreasonably large input. - -MUST round down towards 0. - -This calculation MAY NOT reflect the "per-user" price-per-principal-token, and instead should reflect the "average-user's" price-per-principal-token, meaning what the average user should expect to see when redeeming. - -```yaml -- name: convertToPrincipal - type: function - stateMutability: view - - inputs: - - name: underlyingAmount - type: uint256 - - outputs: - - name: principalAmount - type: uint256 -``` - -#### `maxRedeem` - -Maximum amount of principal tokens that can be redeemed from the `holder` balance, through a `redeem` call. - -MUST return the maximum amount of principal tokens that could be transferred from `holder` through `redeem` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). - -MUST factor in both global and user-specific limits, like if redemption is entirely disabled (even temporarily) it MUST return 0. - -MUST NOT revert. - -```yaml -- name: maxRedeem - type: function - stateMutability: view - - inputs: - - name: holder - type: address - - outputs: - - name: maxPrincipalAmount - type: uint256 -``` - -#### `previewRedeem` - -Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions. - -MUST return as close to and no more than the exact amount of underliyng that would be obtained in a `redeem` call in the same transaction. I.e. `redeem` should return the same or more `underlyingAmount` as `previewRedeem` if called in the same transaction. - -MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the redemption would be accepted, regardless if the user has enough principal tokens, etc. - -MUST be inclusive of redemption fees. Integrators should be aware of the existence of redemption fees. - -MUST NOT revert due to principal token contract specific user/global limits. MAY revert due to other conditions that would also cause `redeem` to revert. - -Note that any unfavorable discrepancy between `convertToUnderlying` and `previewRedeem` SHOULD be considered slippage in price-per-principal-token or some other type of condition. - -```yaml -- name: previewRedeem - type: function - stateMutability: view - - inputs: - - name: principalAmount - type: uint256 - - outputs: - - name: underlyingAmount - type: uint256 -``` - -#### `redeem` - -At or after maturity, burns exactly `principalAmount` of Principal Tokens from `from` and sends `underlyingAmount` of underlying tokens to `to`. - -Interfaces and other contracts MUST NOT expect fund custody to be present. While custodial redemption of Principal Tokens through the Principal Token contract is extremely useful for integrators, some protocols may find giving the Principal Token itself custody breaks their backwards compatibility. - -MUST emit the `Redeem` event. - -MUST support a redeem flow where the Principal Tokens are burned from `holder` directly where `holder` is `msg.sender` or `msg.sender` has EIP-20 approval over the principal tokens of `holder`. -MAY support an additional flow in which the principal tokens are transferred to the Principal Token contract before the `redeem` execution, and are accounted for during `redeem`. - -MUST revert if all of `principalAmount` cannot be redeemed (due to withdrawal limit being reached, slippage, the holder not having enough Principal Tokens, etc). - -Note that some implementations will require pre-requesting to the Principal Token before a withdrawal may be performed. Those methods should be performed separately. - -```yaml -- name: redeem - type: function - stateMutability: nonpayable - - inputs: - - name: principalAmount - type: uint256 - - name: to - type: address - - name: from - type: address - - outputs: - - name: underlyingAmount - type: uint256 -``` - -#### `maxWithdraw` - -Maximum amount of the underlying asset that can be redeemed from the `holder` principal token balance, through a `withdraw` call. - -MUST return the maximum amount of underlying tokens that could be redeemed from `holder` through `withdraw` and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). - -MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0. - -MUST NOT revert. - -```yaml -- name: maxWithdraw - type: function - stateMutability: view - - inputs: - - name: holder - type: address - - outputs: - - name: maxUnderlyingAmount - type: uint256 -``` - -#### `previewWithdraw` - -Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions. - -MUST return as close to and no fewer than the exact amount of principal tokens that would be burned in a `withdraw` call in the same transaction. I.e. `withdraw` should return the same or fewer `principalAmount` as `previewWithdraw` if called in the same transaction. - -MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though the withdrawal would be accepted, regardless if the user has enough principal tokens, etc. - -MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - -MUST NOT revert due to principal token contract specific user/global limits. MAY revert due to other conditions that would also cause `withdraw` to revert. - -Note that any unfavorable discrepancy between `convertToPrincipal` and `previewWithdraw` SHOULD be considered slippage in price-per-principal-token or some other type of condition. - -```yaml -- name: previewWithdraw - type: function - stateMutability: view - - inputs: - - name: underlyingAmount - type: uint256 - - outputs: - - name: principalAmount - type: uint256 -``` - -#### `withdraw` - -Burns `principalAmount` from `holder` and sends exactly `underlyingAmount` of underlying tokens to `receiver`. - -MUST emit the `Redeem` event. - -MUST support a withdraw flow where the principal tokens are burned from `holder` directly where `holder` is `msg.sender` or `msg.sender` has [EIP-20](./eip-20.md) approval over the principal tokens of `holder`. - MAY support an additional flow in which the principal tokens are transferred to the principal token contract before the `withdraw` execution, and are accounted for during `withdraw`. - -MUST revert if all of `underlyingAmount` cannot be withdrawn (due to withdrawal limit being reached, slippage, the holder not having enough principal tokens, etc). - -Note that some implementations will require pre-requesting to the principal token contract before a withdrawal may be performed. Those methods should be performed separately. - -```yaml -- name: withdraw - type: function - stateMutability: nonpayable - - inputs: - - name: underlyingAmount - type: uint256 - - name: receiver - type: address - - name: holder - type: address - - outputs: - - name: principalAmount - type: uint256 -``` - -### Events - -#### Redeem - -`from` has exchanged `principalAmount` of Principal Tokens for `underlyingAmount` of underlying, and transferred that underlying to `to`. - -MUST be emitted when Principal Tokens are burnt and underlying is withdrawn from the contract in the `EIP5095.redeem` method. - -```yaml -- name: Redeem - type: event - - inputs: - - name: from - indexed: true - type: address - - name: to - indexed: true - type: address - - name: amount - indexed: false - type: uint256 -``` - -## Rationale - -The Principal Token interface is designed to be optimized for integrators with a core minimal interface alongside optional interfaces to enable backwards compatibility. Details such as accounting and management of underlying are intentionally not specified, as Principal Tokens are expected to be treated as black boxes on-chain and inspected off-chain before use. - -[EIP-20](./eip-20.md) is enforced as implementation details such as token approval and balance calculation directly carry over. This standardization makes Principal Tokens immediately compatible with all [EIP-20](./eip-20.md) use cases in addition to EIP-5095. - -All principal tokens are redeemable upon maturity, with the only variance being whether further yield is generated post-maturity. Given the ubiquity of redemption, the presence of `redeem` allows integrators to purchase Principal Tokens on an open market, and them later redeem them for a fixed-yield solely knowing the address of the Principal Token itself. - -This EIP draws heavily on the design of [EIP-4626](./eip-4626.md) because technically Principal Tokens could be described as a subset of Yield Bearing Vaults, extended with a `maturity` variable and restrictions on the implementation. However, extending [EIP-4626](./eip-4626.md) would force PT implementations to include methods (namely, `mint` and `deposit`) that are not necessary to the business case that PTs solve. It can also be argued that partial redemptions (implemented via `withdraw`) are rare for PTs. - -PTs mature at a precise second, but given the reactive nature of smart contracts, there can't be an event marking maturity, because there is no guarantee of any activity at or after maturity. Emitting an event to notify of maturity in the first transaction after maturity would be imprecise and expensive. Instead, integrators are recommended to either use the first `Redeem` event, or to track themselves when each PT is expected to have matured. - -## Backwards Compatibility - -This EIP is fully backward compatible with the [EIP-20](./eip-20.md) specification and has no known compatibility issues with other standards. -For production implementations of Principal Tokens which do not use EIP-5095, wrapper adapters can be developed and used, or wrapped tokens can be implemented. - -## Reference Implementation - -``` -// SPDX-License-Identifier: MIT -pragma solidity 0.8.14; - -import {ERC20} from "yield-utils-v2/contracts/token/ERC20.sol"; -import {MinimalTransferHelper} from "yield-utils-v2/contracts/token/MinimalTransferHelper.sol"; - -contract ERC5095 is ERC20 { - using MinimalTransferHelper for ERC20; - - /* EVENTS - *****************************************************************************************************************/ - - event Redeem(address indexed from, address indexed to, uint256 underlyingAmount); - - /* MODIFIERS - *****************************************************************************************************************/ - - /// @notice A modifier that ensures the current block timestamp is at or after maturity. - modifier afterMaturity() virtual { - require(block.timestamp >= maturity, "BEFORE_MATURITY"); - _; - } - - /* IMMUTABLES - *****************************************************************************************************************/ - - ERC20 public immutable underlying; - uint256 public immutable maturity; - - /* CONSTRUCTOR - *****************************************************************************************************************/ - - constructor( - string memory name_, - string memory symbol_, - uint8 decimals_, - ERC20 underlying_, - uint256 maturity_ - ) ERC20(name_, symbol_, decimals_) { - underlying = underlying_; - maturity = maturity_; - } - - /* CORE FUNCTIONS - *****************************************************************************************************************/ - - /// @notice Burns an exact amount of principal tokens in exchange for an amount of underlying. - /// @dev This reverts if before maturity. - /// @param principalAmount The exact amount of principal tokens to be burned. - /// @param from The owner of the principal tokens to be redeemed. If not msg.sender then must have prior approval. - /// @param to The address to send the underlying tokens. - /// @return underlyingAmount The total amount of underlying tokens sent. - function redeem( - uint256 principalAmount, - address from, - address to - ) public virtual afterMaturity returns (uint256 underlyingAmount) { - _decreaseAllowance(from, principalAmount); - - // Check for rounding error since we round down in previewRedeem. - require((underlyingAmount = _previewRedeem(principalAmount)) != 0, "ZERO_ASSETS"); - - _burn(from, principalAmount); - - emit Redeem(from, to, principalAmount); - - _transferOut(to, underlyingAmount); - } - - /// @notice Burns a calculated amount of principal tokens in exchange for an exact amount of underlying. - /// @dev This reverts if before maturity. - /// @param underlyingAmount The exact amount of underlying tokens to be received. - /// @param from The owner of the principal tokens to be redeemed. If not msg.sender then must have prior approval. - /// @param to The address to send the underlying tokens. - /// @return principalAmount The total amount of underlying tokens redeemed. - function withdraw( - uint256 underlyingAmount, - address from, - address to - ) public virtual afterMaturity returns (uint256 principalAmount) { - principalAmount = _previewWithdraw(underlyingAmount); // No need to check for rounding error, previewWithdraw rounds up. - - _decreaseAllowance(from, principalAmount); - - _burn(from, principalAmount); - - emit Redeem(from, to, principalAmount); - - _transferOut(to, underlyingAmount); - } - - /// @notice An internal, overridable transfer function. - /// @dev Reverts on failed transfer. - /// @param to The recipient of the transfer. - /// @param amount The amount of the transfer. - function _transferOut(address to, uint256 amount) internal virtual { - underlying.safeTransfer(to, amount); - } - - /* ACCOUNTING FUNCTIONS - *****************************************************************************************************************/ - - /// @notice Calculates the amount of underlying tokens that would be exchanged for a given amount of principal tokens. - /// @dev Before maturity, it converts to underlying as if at maturity. - /// @param principalAmount The amount principal on which to calculate conversion. - /// @return underlyingAmount The total amount of underlying that would be received for the given principal amount.. - function convertToUnderlying(uint256 principalAmount) external view returns (uint256 underlyingAmount) { - return _convertToUnderlying(principalAmount); - } - - function _convertToUnderlying(uint256 principalAmount) internal view virtual returns (uint256 underlyingAmount) { - return principalAmount; - } - - /// @notice Converts a given amount of underlying tokens to principal exclusive of fees. - /// @dev Before maturity, it converts to principal as if at maturity. - /// @param underlyingAmount The total amount of underlying on which to calculate the conversion. - /// @return principalAmount The amount principal tokens required to provide the given amount of underlying. - function convertToPrincipal(uint256 underlyingAmount) external view returns (uint256 principalAmount) { - return _convertToPrincipal(underlyingAmount); - } - - function _convertToPrincipal(uint256 underlyingAmount) internal view virtual returns (uint256 principalAmount) { - return underlyingAmount; - } - - /// @notice Allows user to simulate redemption of a given amount of principal tokens, inclusive of fees and other - /// current block conditions. - /// @dev This reverts if before maturity. - /// @param principalAmount The amount of principal that would be redeemed. - /// @return underlyingAmount The amount of underlying that would be received. - function previewRedeem(uint256 principalAmount) external view afterMaturity returns (uint256 underlyingAmount) { - return _previewRedeem(principalAmount); - } - - function _previewRedeem(uint256 principalAmount) internal view virtual returns (uint256 underlyingAmount) { - return _convertToUnderlying(principalAmount); // should include fees/slippage - } - - /// @notice Calculates the maximum amount of principal tokens that an owner could redeem. - /// @dev This returns 0 if before maturity. - /// @param owner The address for which the redemption is being calculated. - /// @return maxPrincipalAmount The maximum amount of principal tokens that can be redeemed by the given owner. - function maxRedeem(address owner) public view returns (uint256 maxPrincipalAmount) { - return block.timestamp >= maturity ? _balanceOf[owner] : 0; - } - - /// @notice Allows user to simulate withdraw of a given amount of underlying tokens. - /// @dev This reverts if before maturity. - /// @param underlyingAmount The amount of underlying tokens that would be withdrawn. - /// @return principalAmount The amount of principal tokens that would be redeemed. - function previewWithdraw(uint256 underlyingAmount) external view afterMaturity returns (uint256 principalAmount) { - return _previewWithdraw(underlyingAmount); - } - - function _previewWithdraw(uint256 underlyingAmount) internal view virtual returns (uint256 principalAmount) { - return _convertToPrincipal(underlyingAmount); // should include fees/slippage - } - - /// @notice Calculates the maximum amount of underlying tokens that can be withdrawn by a given owner. - /// @dev This returns 0 if before maturity. - /// @param owner The address for which the withdraw is being calculated. - /// @return maxUnderlyingAmount The maximum amount of underlying tokens that can be withdrawn by a given owner. - function maxWithdraw(address owner) public view returns (uint256 maxUnderlyingAmount) { - return _previewWithdraw(maxRedeem(owner)); - } -} - -``` - -## Security Considerations - -Fully permissionless use cases could fall prey to malicious implementations which only conform to the interface in this EIP but not the specification, failing to implement proper custodial functionality but offering the ability to purchase Principal Tokens through secondary markets. - -It is recommended that all integrators review each implementation for potential ways of losing user deposits before integrating. - -The `convertToUnderlying` method is an estimate useful for display purposes, -and do _not_ have to confer the _exact_ amount of underlying assets their context suggests. - -As is common across many standards, it is strongly recommended to mirror the underlying token's `decimals` if at all possible, to eliminate possible sources of confusion and simplify integration across front-ends and for other off-chain users. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5095.md diff --git a/EIPS/eip-5114.md b/EIPS/eip-5114.md index 35cbbd5e41520c..2f004de4631dae 100644 --- a/EIPS/eip-5114.md +++ b/EIPS/eip-5114.md @@ -1,98 +1 @@ ---- -eip: 5114 -title: Soulbound Badge -description: A token that is attached to a "soul" at mint time and cannot be transferred after that. -author: Micah Zoltu (@MicahZoltu) -discussions-to: https://ethereum-magicians.org/t/eip-5114-soulbound-token/9417 -status: Last Call -last-call-deadline: 2023-09-19 -type: Standards Track -category: ERC -created: 2022-05-30 ---- - - -## Abstract - -A soulbound badge is a token that, when minted, is bound to another Non-Fungible Token (NFT), and cannot be transferred/moved after that. - - -## Specification - -```solidity -interface IERC5114 { - // fired anytime a new instance of this badge is minted - // this event **MUST NOT** be fired twice for the same `badgeId` - event Mint(uint256 indexed badgeId, address indexed nftAddress, uint256 indexed nftTokenId); - - // returns the NFT that this badge is bound to. - // this function **MUST** throw if the badge hasn't been minted yet - // this function **MUST** always return the same result every time it is called after it has been minted - // this function **MUST** return the same value as found in the original `Mint` event for the badge - function ownerOf(uint256 badgeId) external view returns (address nftAddress, uint256 nftTokenId); - - // returns a URI with details about this badge collection - // the metadata returned by this is merged with the metadata return by `badgeUri(uint256)` - // the collectionUri **MUST** be immutable (e.g., ipfs:// and not http://) - // the collectionUri **MUST** be content addressable (e.g., ipfs:// and not http://) - // data from `badgeUri` takes precedence over data returned by this method - // any external links referenced by the content at `collectionUri` also **MUST** follow all of the above rules - function collectionUri() external pure returns (string collectionUri); - - // returns a censorship resistant URI with details about this badge instance - // the collectionUri **MUST** be immutable (e.g., ipfs:// and not http://) - // the collectionUri **MUST** be content addressable (e.g., ipfs:// and not http://) - // data from this takes precedence over data returned by `collectionUri` - // any external links referenced by the content at `badgeUri` also **MUST** follow all of the above rules - function badgeUri(uint256 badgeId) external view returns (string badgeUri); - - // returns a string that indicates the format of the `badgeUri` and `collectionUri` results (e.g., 'EIP-ABCD' or 'soulbound-schema-version-4') - function metadataFormat() external pure returns (string format); -} -``` - -Implementers of this standard **SHOULD** also depend on a standard for interface detection so callers can easily find out if a given contract implements this interface. - - -## Rationale - -### Immutability - -By requiring that badges can never move, we both guarantee non-separability and non-mergeability among collections of soulbound badges that are bound to a single NFT while simultaneously allowing users to aggressively cache results. - -### Content Addressable URIs Required - -Soulbound badges are meant to be permanent badges/indicators attached to a persona. -This means that not only can the user not transfer ownership, but the minter also cannot withdraw/transfer/change ownership as well. -This includes mutating or removing any remote content as a means of censoring or manipulating specific users. - -### No Specification for `badgeUri` Data Format - -The format of the data pointed to by `collectionUri()` and `badgeUri(uint256)`, and how to merge them, is intentionally left out of this standard in favor of separate standards that can be iterated on in the future. -The immutability constraints are the only thing defined by this to ensure that the spirit of this badge is maintained, regardless of the specifics of the data format. -The `metadataFormat` function can be used to inform a caller what type/format/version of data they should expect at the URIs, so the caller can parse the data directly without first having to deduce its format via inspection. - - -## Backwards Compatibility - -This is a new token type and is not meant to be backward compatible with any existing tokens other than existing viable souls (any asset that can be identified by `[address,id]`). - - -## Security Considerations - -Users of badges that claim to implement this EIP must be diligent in verifying they actually do. -A badge author can create a badge that, upon initial probing of the API surface, may appear to follow the rules when in reality it doesn't. -For example, the contract could allow transfers via some mechanism and simply not utilize them initially. - -It should also be made clear that soulbound badges are not bound to a human, they are bound to a persona. -A persona is any actor (which could be a group of humans) that collects multiple soulbound badges over time to build up a collection of badges. -This persona may transfer to another human, or to another group of humans, and anyone interacting with a persona should not assume that there is a single permanent human behind that persona. - -It is possible for a soulbound badge to be bound to another soulbound badge. -In theory, if all badges in the chain are created at the same time they could form a loop. -Software that tries to walk such a chain should take care to have an exit strategy if a loop is detected. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5114.md diff --git a/EIPS/eip-5115.md b/EIPS/eip-5115.md index 1930983248f55e..39802b3a7f29d9 100644 --- a/EIPS/eip-5115.md +++ b/EIPS/eip-5115.md @@ -1,349 +1 @@ ---- -eip: 5115 -title: SY Token -description: Interface for wrapped yield-bearing tokens. -author: Vu Nguyen (@mrenoon), Long Vuong (@UncleGrandpa925), Anton Buenavista (@ayobuenavista) -discussions-to: https://ethereum-magicians.org/t/eip-5115-super-composable-yield-token-standard/9423 -status: Draft -type: Standards Track -category: ERC -created: 2022-05-30 -requires: 20 ---- - -## Abstract - -This standard proposes an API for wrapped yield-bearing tokens within smart contracts. It is an extension on the [ERC-20](./eip-20.md) token that provides basic functionality for transferring, depositing, withdrawing tokens, as well as reading balances. - -## Motivation - -Yield generating mechanisms are built in all shapes and sizes, necessitating a manual integration every time a protocol builds on top of another protocol’s yield generating mechanism. - -[ERC-4626](./eip-4626.md) tackled a significant part of this fragmentation by standardizing the interfaces for vaults, a major category among various yield generating mechanisms. - -In this ERC, we’re extending the coverage to include assets beyond ERC-4626’s reach, namely: - -- yield-bearing assets that have different input tokens used for minting vs accounting for the pool value. - - This category includes AMM liquidity tokens (which are yield-bearing assets that yield swap fees) since the value of the pool is measured in “liquidity units” (for example, $\sqrt k$ in UniswapV2, as defined in UniswapV2 whitepaper) which can’t be deposited in (as they are not tokens). - - This extends the flexibility in minting the yield-bearing assets. For example, there could be an ETH vault that wants to allow users to deposit cETH directly instead of ETH, for gas efficiency or UX reasons. -- Assets with reward tokens by default (e.g. COMP rewards for supplying in Compound). The reward tokens are expected to be sold to compound into the same asset. -- This ERC can be extended further to include the handling of rewards, such as the claiming of accrued multiple rewards tokens. - -While ERC-4626 is a well-designed and suitable standard for most vaults, there will inevitably be some yield generating mechanisms that do not fit into their category (LP tokens for instance). A more flexible standard is required to standardize the interaction with all types of yield generating mechanisms. - -Therefore, we are proposing Standardized Yield (SY), a flexible standard for wrapped yield-bearing tokens that could cover most mechanisms in DeFi. We foresee that: - -- ERC-4626 will still be a popular vault standard, that most vaults should adopt. -- SY tokens can wrap over most yield generating mechanisms in DeFi, including ERC-4626 vaults for projects built on top of yield-bearing tokens. -- Whoever needs the functionalities of SY could integrate with the existing SY tokens or write a new SY (to wrap over the target yield-bearing token). -- Reward handling can be extended from the SY token. - -### Use Cases - -This ERC is designed for flexibility, aiming to accommodate as many yield generating mechanisms as possible. Particularly, this standard aims to be generalized enough that it supports the following use cases and more: - -- Money market supply positions - - Lending DAI in Compound, getting DAI interests and COMP rewards - - Lending ETH in BenQi, getting ETH interests and QI + AVAX rewards - - Lending USDC in Aave, getting USDC interests and stkAAVE rewards -- AMM liquidity provision - - Provide ETH + USDC to ETHUSDC pool in SushiSwap, getting swap fees in more ETH+USDC - - Provide ETH + USDC to ETHUSDC pool in SushiSwap and stake it in Sushi Onsen, getting swap fees and SUSHI rewards - - Provide USDC+DAI+USDT to 3crv pool and stake it in Convex, getting 3crv swap fees and CRV + CVX rewards -- Vault positions - - Provide ETH into Yearn ERC-4626 vault, where the vault accrues yield from Yearn’s ETH strategy - - Provide DAI into Harvest and staking it, getting DAI interests and FARM rewards -- Liquid staking positions - - Holding stETH (in Lido), getting yields in more stETH -- Liquidity mining programs - - Provide USDC in Stargate, getting STG rewards - - Provide LOOKS in LooksRare, getting LOOKS yield and WETH rewards -- Rebasing tokens - - Stake OHM into sOHM/gOHM, getting OHM rebase yield - -The ERC hopes to minimize, if not possibly eliminate, the use of customized adapters in order to interact with many different forms of yield-bearing token mechanisms. - -## Specification - -### Generic Yield Generating Pool - -We will first introduce Generic Yield Generating Pool (GYGP), a model to describe most yield generating mechanisms in DeFi. In every yield generating mechanism, there is a pool of funds, whose value is measured in **assets**. There are a number of users who contribute liquidity to the pool, in exchange for **shares** of the pool, which represents units of ownership of the pool. Over time, the value (measured in **assets**) of the pool grows, such that each **share** is worth more **assets** over time. The pool could earn a number of **reward tokens** over time, which are distributed to the users according to some logic (for example, proportionally the number of **shares**). - -Here are the more concrete definitions of the terms: - -#### GYGP Definitions: - -- **asset**: Is a unit to measure the value of the pool. At time *t*, the pool has a total value of *TotalAsset(t)* **assets**. -- **shares**: Is a unit that represents ownership of the pool. At time *t*, there are *TotalShares(t)* **shares** in total. -- **reward tokens**: Over time, the pool earns $n_{rewards}$ types of reward tokens $(n_{rewards} \ge 0)$. At time *t*, $TotalRewards_i(t)$ is the amount of **reward token *i*** that has accumulated for the pool up until time *t*. -- **exchange rate**: At time *t*, the **exchange rate** *ExchangeRate(t)* is simply how many **assets** each **shares** is worth $ExchangeRate(t) = \frac{TotalAsset(t)}{TotalShares(t)}$ -- **users**: At time *t*, each user *u* has $shares_u(t)$ **shares** in the pool, which is worth $asset_u(t) = shares_u(t) \cdot ExchangeRate(t)$ **assets**. Until time *t*, user *u* is entitled to receive a total of $rewards_{u_i}(t)$ **reward token *i***. The sum of all users’ shares, assets and rewards should be the same as the total shares, assets and rewards of the whole pool. - -#### State changes: - -1. A user deposits $d_a$ **assets** into the pool at time $t$ ($d_a$ could be negative, which means a withdraw from the pool). $d_s = d_a / ExchangeRate(t)$ new **shares** will be created and given -to user (or removed and burned from the user when $d_a$ is negative). -2. The pool earns $d_a$ (or loses $−d_a$ if $d_a$ is negative) **assets** at time $t$. The **exchange rate** simply increases (or decreases if $d_a$ is negative) due to the additional assets. -3. The pool earns $d_r$ **reward token** $i$. Every user will receive a certain amount of **reward token** $i$. - -#### Examples of GYGPs in DeFi: - -| Yield generating mechanism | Asset | Shares | Reward tokens | Exchange rate | -| --- | --- | --- | --- | --- | -| Supply USDC in Compound | USDC | cUSDC | COMP | USDC value per cUSDC, increases with USDC supply interests | -| ETH liquid staking in Lido | stETH | wstETH | None | stETH value per wstETH, increases with ETH staking rewards | -| Stake LOOKS in LooksRare Compounder | LOOKS | shares (in contract) | WETH | LOOKS value per shares, increases with LOOKS rewards | -| Stake APE in $APE Compounder | sAPE | shares (in contract) | APE | sAPE value per shares, increases with APE rewards | -| Provide ETH+USDC liquidity on Sushiswap | ETHUSDC liquidity (a pool of x ETH + y USDC has sqrt(xy) ETHUSDC liquidity) | ETHUSDC Sushiswap LP (SLP) token | None | ETHUSDC liquidity value per ETHUSDC SLP, increases due to swap fees | -| Provide ETH+USDC liquidity on Sushiswap and stake into Onsen | ETHUSDC liquidity (a pool of x ETH + y USDC has sqrt(xy) ETHUSDC liquidity) | ETHUSDC Sushiswap LP (SLP) token | SUSHI | ETHUSDC liquidity value per ETHUSDC SLP, increases due to swap fees | -| Provide BAL+WETH liquidity in Balancer (80% BAL, 20% WETH) | BALWETH liquidity (a pool of x BAL + y WETH has x^0.8*y^0.2 BALWETH liquidity) | BALWETH Balancer LP token | None | BALWETH liquidity per BALWETH Balancer LP token, increases due to swap fees | -| Provide USDC+USDT+DAI liquidity in Curve | 3crv pool’s liquidity (amount of D per 3crv token) | 3crv token | CRV | 3crv pool’s liquidity per 3crv token, increases due to swap fees | -| Provide FRAX+USDC liquidity in Curve then stake LP in Convex | BALWETH liquidity (a pool of x BAL + y WETH has x^0.8*y^0.2 BALWETH liquidity) | BALWETH Balancer LP token | None | BALWETH liquidity per BALWETH Balancer LP token, increases due to swap fees | - - -### Standardized Yield Token Standard - -#### Overview: - -Standardized Yield (SY) is a token standard for any yield generating mechanism that conforms to the GYGP model. Each SY token represents **shares** in a GYGP and allows for interacting with the GYGP via a standard interface. - -All SY tokens: - -- **MUST** implement **`ERC-20`** to represent shares in the underlying GYGP. -- **MUST** implement ERC-20’s optional metadata extensions `name`, `symbol`, and `decimals`, which **SHOULD** reflect the underlying GYGP’s accounting asset’s `name`, `symbol`, and `decimals`. -- **MAY** implement [ERC-2612](./eip-2612.md) to improve the UX of approving SY tokens on various integrations. -- **MAY** revert on calls to `transfer` and `transferFrom` if a SY token is to be non-transferable. -- The ERC-20 operations `balanceOf`, `transfer`, `totalSupply`, etc. **SHOULD** operate on the GYGP “shares”, which represent a claim to ownership on a fraction of the GYGP’s underlying holdings. - -#### SY Definitions: - -On top of the definitions above for GYGPs, we need to define 2 more concepts: - -- **input tokens**: Are tokens that can be converted into assets to enter the pool. Each SY can accept several possible input tokens $tokens_{in_{i}}$ - -- **output tokens**: Are tokens that can be redeemed from assets when exiting the pool. Each SY can have several possible output tokens $tokens_{out_{i}}$ - -#### Interface - -```solidity -interface IStandardizedYield { - event Deposit( - address indexed caller, - address indexed receiver, - address indexed tokenIn, - uint256 amountDeposited, - uint256 amountSyOut - ); - - event Redeem( - address indexed caller, - address indexed receiver, - address indexed tokenOut, - uint256 amountSyToRedeem, - uint256 amountTokenOut - ); - - function deposit( - address receiver, - address tokenIn, - uint256 amountTokenToDeposit, - uint256 minSharesOut, - bool depositFromInternalBalance - ) external returns (uint256 amountSharesOut); - - function redeem( - address receiver, - uint256 amountSharesToRedeem, - address tokenOut, - uint256 minTokenOut, - bool burnFromInternalBalance - ) external returns (uint256 amountTokenOut); - - function exchangeRate() external view returns (uint256 res); - - function getTokensIn() external view returns (address[] memory res); - - function getTokensOut() external view returns (address[] memory res); - - function yieldToken() external view returns (address); - - function previewDeposit(address tokenIn, uint256 amountTokenToDeposit) - external - view - returns (uint256 amountSharesOut); - - function previewRedeem(address tokenOut, uint256 amountSharesToRedeem) - external - view - returns (uint256 amountTokenOut); - - function name() external view returns (string memory); - - function symbol() external view returns (string memory); - - function decimals() external view returns (uint8); -} -``` - -#### Methods - -```solidity -function deposit( - address receiver, - address tokenIn, - uint256 amountTokenToDeposit, - uint256 minSharesOut, - bool depositFromInternalBalance -) external returns (uint256 amountSharesOut); -``` - -This function will deposit *amountTokenToDeposit* of input token $i$ (*tokenIn*) to mint new SY shares. - -If *depositFromInternalBalance* is set to *false*, msg.sender will need to initially deposit *amountTokenToDeposit* of input token $i$ (*tokenIn*) into the SY contract, then this function will convert the *amountTokenToDeposit* of input token $i$ into $d_a$ worth of **asset** and deposit this amount into the pool for the *receiver*, who will receive *amountSharesOut* of SY tokens (**shares**). If *depositFromInternalBalance* is set to *true*, then *amountTokenToDeposit* of input token $i$ (*tokenIn*) will be taken from receiver directly (as msg.sender), and will be converted and shares returned to the receiver similarly to the first case. - -This function should revert if $amountSharesOut \lt minSharesOut$. - -- **MUST** emit the `Deposit` event. -- **MUST** support ERC-20’s `approve` / `transferFrom` flow where `tokenIn` are taken from receiver directly (as msg.sender) or if the msg.sender has ERC-20 approved allowance over the input token of the receiver. -- **MUST** revert if $amountSharesOut \lt minSharesOut$ (due to deposit limit being reached, slippage, or the user not approving enough `tokenIn` **to the SY contract, etc). -- **MAY** be payable if the `tokenIn` depositing asset is the chain's native currency (e.g. ETH). - -```solidity -function redeem( - address receiver, - uint256 amountSharesToRedeem, - address tokenOut, - uint256 minTokenOut, - bool burnFromInternalBalance -) external returns (uint256 amountTokenOut); -``` - -This function will redeem the $d_s$ shares, which is equivalent to $d_a = d_s \times ExchangeRate(t)$ assets, from the pool. The $d_a$ assets is converted into exactly *amountTokenOut* of output token $i$ (*tokenOut*). - -If *burnFromInternalBalance* is set to *false*, the user will need to initially deposit *amountSharesToRedeem* into the SY contract, then this function will burn the floating amount $d_s$ of SY tokens (**shares**) in the SY contract to redeem to output token $i$ (*tokenOut*). This pattern is similar to UniswapV2 which allows for more gas efficient ways to interact with the contract. If *burnFromInternalBalance* is set to *true*, then this function will burn *amountSharesToRedeem* $d_s$ of SY tokens directly from the user to redeem to output token $i$ (*tokenOut*). - -This function should revert if $amountTokenOut \lt minTokenOut$. - -- **MUST** emit the `Redeem` event. -- **MUST** support ERC-20’s `approve` / `transferFrom` flow where the shares are burned from receiver directly (as msg.sender) or if the msg.sender has ERC-20 approved allowance over the shares of the receiver. -- **MUST** revert if $amountTokenOut \lt minTokenOut$ (due to redeem limit being reached, slippage, or the user not approving enough `amountSharesToRedeem` to the SY contract, etc). - -```solidity -function exchangeRate() external view returns (uint256 res); -``` - -This method updates and returns the latest **exchange rate**, which is the **exchange rate** from SY token amount into asset amount, scaled by a fixed scaling factor of 1e18. - -- **MUST** return $ExchangeRate(t_{now})$ such that $ExchangeRate(t_{now}) \times syBalance / 1e18 = assetBalance$. -- **MUST NOT** include fees that are charged against the underlying yield token in the SY contract. - -```solidity -function getTokensIn() external view returns (address[] memory res); -``` - -This read-only method returns the list of all input tokens that can be used to deposit into the SY contract. - -- **MUST** return ERC-20 token addresses. -- **MUST** return at least one address. -- **MUST NOT** revert. - -```solidity -function getTokensOut() external view returns (address[] memory res); -``` - -This read-only method returns the list of all output tokens that can be converted into when exiting the SY contract. - -- **MUST** return ERC-20 token addresses. -- **MUST** return at least one address. -- **MUST NOT** revert. - -```solidity -function yieldToken() external view returns (address); -``` - -This read-only method returns the underlying yield-bearing token (representing a GYGP) address. - -- **MUST** return a token address that conforms to the ERC-20 interface, or zero address -- **MUST NOT** revert. -- **MUST** reflect the exact underlying yield-bearing token address if the SY token is a wrapped token. -- **MAY** return 0x or zero address if the SY token is natively implemented, and not from wrapping. - -```solidity -function previewDeposit(address tokenIn, uint256 amountTokenToDeposit) - external - view - returns (uint256 amountSharesOut); -``` - -This read-only method returns the amount of shares that a user would have received if they deposit *amountTokenToDeposit* of *tokenIn*. - -- **MUST** return less than or equal of *amountSharesOut* to the actual return value of the `deposit` method, and **SHOULD NOT** return greater than the actual return value of the `deposit` method. -- **MUST NOT** revert. - -```solidity -function previewRedeem(address tokenOut, uint256 amountSharesToRedeem) - external - view - returns (uint256 amountTokenOut); -``` - -This read-only method returns the amount of *tokenOut* that a user would have received if they redeem *amountSharesToRedeem* of *tokenOut*. - -- **MUST** return less than or equal of *amountTokenOut* to the actual return value of the `redeem` method, and **SHOULD NOT** return greater than the actual return value of the `redeem` method. -- **MUST NOT** revert. - -#### Events - -```solidity -event Deposit( - address indexed caller, - address indexed receiver, - address indexed tokenIn, - uint256 amountDeposited, - uint256 amountSyOut -); -``` - -`caller` has converted exact *tokenIn* tokens into SY (shares) and transferred those SY to `receiver`. - -- **MUST** be emitted when input tokens are deposited into the SY contract via `deposit` method. - -```solidity -event Redeem( - address indexed caller, - address indexed receiver, - address indexed tokenOut, - uint256 amountSyToRedeem, - uint256 amountTokenOut -); -``` - -`caller` has converted exact SY (shares) into input tokens and transferred those input tokens to `receiver`. - -- **MUST** be emitted when input tokens are redeemed from the SY contract via `redeem` method. - -**"SY" Word Choice:** - -"SY" (pronunciation: */sʌɪ/*), an abbreviation of Standardized Yield, was found to be appropriate to describe a broad universe of standardized composable yield-bearing digital assets. - -## Rationale - -[ERC-20](./eip-20.md) is enforced because implementation details such as transfer, token approvals, and balance calculation directly carry over to the SY tokens. This standardization makes the SY tokens immediately compatible with all ERC-20 use cases. - -[ERC-165](./eip-165.md) can optionally be implemented should you want integrations to detect the IStandardizedYield interface implementation. - -[ERC-2612](./eip-2612.md) can optionally be implemented in order to improve the UX of approving SY tokens on various integrations. - -## Backwards Compatibility - -This ERC is fully backwards compatible as its implementation extends the functionality of [ERC-20](./eip-20.md), however the optional metadata extensions, namely `name`, `decimals`, and `symbol` semantics MUST be implemented for all SY token implementations. - -## Security Considerations - -Malicious implementations which conform to the interface can put users at risk. It is recommended that all integrators (such as wallets, aggregators, or other smart contract protocols) review the implementation to avoid possible exploits and users losing funds. - -`yieldToken` must strongly reflect the address of the underlying wrapped yield-bearing token. For a native implementation wherein the SY token does not wrap a yield-bearing token, but natively represents a GYGP share, then the address returned MAY be a zero address. Otherwise, for wrapped tokens, you may introduce confusion on what the SY token represents, or may be deemed malicious. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5115.md diff --git a/EIPS/eip-5131.md b/EIPS/eip-5131.md index bb2ea1ae235339..a77dd4d831ddeb 100644 --- a/EIPS/eip-5131.md +++ b/EIPS/eip-5131.md @@ -1,343 +1 @@ ---- -eip: 5131 -title: SAFE Authentication For ENS -description: Using ENS Text Records to facilitate safer and more convenient signing operations. -author: Wilkins Chung (@wwhchung) , Jalil Wahdatehagh (@jwahdatehagh), Cry (@crydoteth), Sillytuna (@sillytuna), Cyberpnk (@CyberpnkWin) -discussions-to: https://ethereum-magicians.org/t/eip-5131-ens-subdomain-authentication/9458 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-06-03 -requires: 137, 181, 634 ---- - -## Abstract -This EIP links one or more signing wallets via Ethereum Name Service Specification ([EIP-137](./eip-137.md)) to prove control and asset ownership of a main wallet. - -## Motivation -Proving ownership of an asset to a third party application in the Ethereum ecosystem is common. Users frequently sign payloads of data to authenticate themselves before gaining access to perform some operation. However, this method--akin to giving the third party root access to one's main wallet--is both insecure and inconvenient. - -***Examples:*** - 1. In order for you to edit your profile on OpenSea, you must sign a message with your wallet. - 2. In order to access NFT gated content, you must sign a message with the wallet containing the NFT in order to prove ownership. - 3. In order to gain access to an event, you must sign a message with the wallet containing a required NFT in order to prove ownership. - 4. In order to claim an airdrop, you must interact with the smart contract with the qualifying wallet. - 5. In order to prove ownership of an NFT, you must sign a payload with the wallet that owns that NFT. - -In all the above examples, one interacts with the dApp or smart contract using the wallet itself, which may be - - inconvenient (if it is controlled via a hardware wallet or a multi-sig) - - insecure (since the above operations are read-only, but you are signing/interacting via a wallet that has write access) - -Instead, one should be able to approve multiple wallets to authenticate on behalf of a given wallet. - -### Problems with existing methods and solutions -Unfortunately, we've seen many cases where users have accidentally signed a malicious payload. The result is almost always a significant loss of assets associated with the signing address. - -In addition to this, many users keep significant portions of their assets in 'cold storage'. With the increased security from 'cold storage' solutions, we usually see decreased accessibility because users naturally increase the barriers required to access these wallets. - -Some solutions propose dedicated registry smart contracts to create this link, or new protocols to be supported. This is problematic from an adoption standpoint, and there have not been any standards created for them. - -### Proposal: Use the Ethereum Name Service (EIP-137) -Rather than 're-invent the wheel', this proposal aims to use the widely adopted Ethereum Name Service in conjunction with the ENS Text Records feature ([EIP-634](./eip-634.md)) in order to achieve a safer and more convenient way to sign and authenticate, and provide 'read only' access to a main wallet via one or more secondary wallets. - -From there, the benefits are twofold. This EIP gives users increased security via outsourcing potentially malicious signing operations to wallets that are more accessible (hot wallets), while being able to maintain the intended security assumptions of wallets that are not frequently used for signing operations. - -#### Improving dApp Interaction Security -Many dApps requires one to prove control of a wallet to gain access. At the moment, this means that you must interact with the dApp using the wallet itself. This is a security issue, as malicious dApps or phishing sites can lead to the assets of the wallet being compromised by having them sign malicious payloads. - -However, this risk would be mitigated if one were to use a secondary wallet for these interactions. Malicious interactions would be isolated to the assets held in the secondary wallet, which can be set up to contain little to nothing of value. - -#### Improving Multiple Device Access Security -In order for a non-hardware wallet to be used on multiple devices, you must import the seed phrase to each device. Each time a seed phrase is entered on a new device, the risk of the wallet being compromised increases as you are increasing the surface area of devices that have knowledge of the seed phrase. - -Instead, each device can have its own unique wallet that is an authorized secondary wallet of the main wallet. If a device specific wallet was ever compromised or lost, you could simply remove the authorization to authenticate. - -Further, wallet authentication can be chained so that a secondary wallet could itself authorize one or many tertiary wallets, which then have signing rights for both the secondary address as well as the root main address. This, can allow teams to each have their own signer while the main wallet can easily invalidate an entire tree, just by revoking rights from the root stem. - -#### Improving Convenience -Many invididuals use hardware wallets for maximum security. However, this is often inconvenient, since many do not want to carry their hardware wallet with them at all times. - -Instead, if you approve a non-hardware wallet for authentication activities (such as a mobile device), you would be able to use most dApps without the need to have your hardware wallet on hand. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Let: - - `mainAddress` represent the wallet address we are trying to authenticate or prove asset ownership for. - - `mainENS` represent the reverse lookup ENS string for `mainAddress`. - - `authAddress` represent the address we want to use for signing in lieu of `mainAddress`. - - `authENS` represent the reverse lookup ENS string for `authAddress`. - - `authKey` represents a string in the format `[0-9A-Za-z]+`. - -Control of `mainAddress` and ownership of `mainAddress` assets by `authAddress` is proven if all the following conditions are met: - - `mainAddress` has an ENS resolver record and a reverse record set to `mainENS`. - - `authAddress` has an ENS resolver record and a reverse record set to `authENS`. - - `authENS` has an ENS TEXT record `eip5131:vault` in the format `:`. - - `mainENS` has an ENS TEXT record `eip5131:`. - -### Setting up one or many `authAddress` records on a single ENS domain -The `mainAddress` MUST have an ENS resolver record and reverse record configured. -In order to automatically discover the linked account, the `authAddress` SHOULD have an ENS resolver record and reverse record configured. - -1. Choose an unused ``. This can be any string in the format `[0-0A-Za-z]+`. -2. Set a TEXT record `eip5131:` on `mainENS`, with the value set to the desired `authAddress`. -3. Set a TEXT record `eip5131:vault` on `authENS`, with the value set to the `:mainAddress`. - -Currently this EIP does not enforce an upper-bound on the number of `authAddress` entries you can include. Users can repeat this process with as many address as they like. - -### Authenticating `mainAddress` via `authAddress` -Control of `mainAddress` and ownership of `mainAddress` assets is proven if any associated `authAddress` is the `msg.sender` or has signed the message. - -Practically, this would work by performing the following operations: -1. Get the resolver for `authENS` -2. Get the `eip5131:vault` TEXT record of `authENS` -3. Parse `:` to determine the `authKey` and `mainAddress`. -4. MUST get the reverse ENS record for `mainAddress` and verify that it matches ``. - - Otherwise one could set up other ENS nodes (with auths) that point to `mainAddress` and authenticate via those. -5. Get the `eip5131:` TEXT record of `mainENS` and ensure it matches `authAddress`. - -Note that this specification allows for both contract level and client/server side validation of signatures. It is not limited to smart contracts, which is why there is no proposed external interface definition. - -### Revocation of `authAddress` -To revoke permission of `authAddress`, delete the `eip5131:` TEXT record of `mainENS` or update it to point to a new `authAddress`. - -## Rationale - -### Usage of EIP-137 -The proposed specification makes use of EIP-137 rather than introduce another registry paradigm. The reason for this is due to the existing wide adoption of EIP-137 and ENS. - -However, the drawback to EIP-137 is that any linked `authAddress` must contain some ETH in order to set the `authENS` reverse record as well as the `eip5131:vault` TEXT record. This can be solved by a separate reverse lookup registry that enables `mainAddress` to set the reverse record and TEXT record with a message signed by `authAddress`. - -With the advent of L2s and ENS Layer 2 functionalities, off chain verification of linked addresses is possible even with domains managed across different chains. - -### One-to-Many Authentication Relationship -This proposed specification allows for a one (`mainAddress`) to many (`authAddress`) authentication relationship. i.e. one `mainAddress` can authorize many `authAddress` to authenticate, but an `authAddress` can only authenticate itself or a single `mainAddress`. - -The reason for this design choice is to allow for simplicity of authentication via client and smart contract code. You can determine which `mainAddress` the `authAddress` is signing for without any additional user input. - -Further, you can design UX without any user interaction necessary to 'pick' the interacting address by display assets owned by `authAddress` and `mainAddress` and use the appropriate address dependent on the asset the user is attempting to authenticate with. - -## Reference Implementation - -### Client/Server Side -In typescript, the validation function, using ethers.js would be as follows: -``` -export interface LinkedAddress { - ens: string, - address: string, -} - -export async function getLinkedAddress( - provider: ethers.providers.EnsProvider, address: string -): Promise { - const addressENS = await provider.lookupAddress(address); - if (!addressENS) return null; - - const vaultInfo = await (await provider.getResolver(addressENS))?.getText('eip5131:vault'); - if (!vaultInfo) return null; - - const vaultInfoArray = vaultInfo.split(':'); - if (vaultInfoArray.length !== 2) { - throw new Error('EIP5131: Authkey and vault address not configured correctly.'); - } - - const [ authKey, vaultAddress ] = vaultInfoArray; - - const vaultENS = await provider.lookupAddress(vaultAddress); - if (!vaultENS) { - throw new Error(`EIP5131: No ENS domain with reverse record set for vault.`); - }; - - const expectedSigningAddress = await ( - await provider.getResolver(vaultENS) - )?.getText(`eip5131:${authKey}`); - - if (expectedSigningAddress?.toLowerCase() !== address.toLowerCase()) { - throw new Error(`EIP5131: Authentication mismatch.`); - }; - - return { - ens: vaultENS, - address: vaultAddress - }; -} -``` - -### Contract side - -#### With a backend -If your application operates a secure backend server, you could run the client/server code above, then use the result in conjunction with specs like [EIP-1271](./eip-1271.md) : `Standard Signature Validation Method for Contracts` for a cheap and secure way to validate that the the message signer is indeed authenticated for the main address. - -#### Without a backend (JavaScript only) -Provided is a reference implementation for an internal function to verify that the message sender has an authentication link to the main address. - -``` -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -/// @author: manifold.xyz - -/** - * ENS Registry Interface - */ -interface ENS { - function resolver(bytes32 node) external view returns (address); -} - -/** - * ENS Resolver Interface - */ -interface Resolver { - function addr(bytes32 node) external view returns (address); - function name(bytes32 node) external view returns (string memory); - function text(bytes32 node, string calldata key) external view returns (string memory); -} - -/** - * Validate a signing address is associtaed with a linked address - */ -library LinkedAddress { - /** - * Validate that the message sender is an authentication address for mainAddress - * - * @param ensRegistry Address of ENS registry - * @param mainAddress The main address we want to authenticate for. - * @param mainENSNodeHash The main ENS Node Hash - * @param authKey The TEXT record of the authKey we are using for validation - * @param authENSNodeHash The auth ENS Node Hash - */ - function validateSender( - address ensRegistry, - address mainAddress, - bytes32 mainENSNodeHash, - string calldata authKey, - bytes32 authENSNodeHash - ) internal view returns (bool) { - return validate(ensRegistry, mainAddress, mainENSNodeHash, authKey, msg.sender, authENSNodeHash); - } - - /** - * Validate that the authAddress is an authentication address for mainAddress - * - * @param ensRegistry Address of ENS registry - * @param mainAddress The main address we want to authenticate for. - * @param mainENSNodeHash The main ENS Node Hash - * @param authAddress The address of the authentication wallet - * @param authENSNodeHash The auth ENS Node Hash - */ - function validate( - address ensRegistry, - address mainAddress, - bytes32 mainENSNodeHash, - string calldata authKey, - address authAddress, - bytes32 authENSNodeHash - ) internal view returns (bool) { - _verifyMainENS(ensRegistry, mainAddress, mainENSNodeHash, authKey, authAddress); - _verifyAuthENS(ensRegistry, mainAddress, authKey, authAddress, authENSNodeHash); - - return true; - } - - // ********************* - // Helper Functions - // ********************* - function _verifyMainENS( - address ensRegistry, - address mainAddress, - bytes32 mainENSNodeHash, - string calldata authKey, - address authAddress - ) private view { - // Check if the ENS nodes resolve correctly to the provided addresses - address mainResolver = ENS(ensRegistry).resolver(mainENSNodeHash); - require(mainResolver != address(0), "Main ENS not registered"); - require(mainAddress == Resolver(mainResolver).addr(mainENSNodeHash), "Main address is wrong"); - - // Verify the authKey TEXT record is set to authAddress by mainENS - string memory authText = Resolver(mainResolver).text(mainENSNodeHash, string(abi.encodePacked("eip5131:", authKey))); - require( - keccak256(bytes(authText)) == keccak256(bytes(_addressToString(authAddress))), - "Invalid auth address" - ); - } - - function _verifyAuthENS( - address ensRegistry, - address mainAddress, - string memory authKey, - address authAddress, - bytes32 authENSNodeHash - ) private view { - // Check if the ENS nodes resolve correctly to the provided addresses - address authResolver = ENS(ensRegistry).resolver(authENSNodeHash); - require(authResolver != address(0), "Auth ENS not registered"); - require(authAddress == Resolver(authResolver).addr(authENSNodeHash), "Auth address is wrong"); - - // Verify the TEXT record is appropriately set by authENS - string memory vaultText = Resolver(authResolver).text(authENSNodeHash, "eip5131:vault"); - require( - keccak256(abi.encodePacked(authKey, ":", _addressToString(mainAddress))) == - keccak256(bytes(vaultText)), - "Invalid auth text record" - ); - } - - bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; - - function sha3HexAddress(address addr) private pure returns (bytes32 ret) { - uint256 value = uint256(uint160(addr)); - bytes memory buffer = new bytes(40); - for (uint256 i = 39; i > 1; --i) { - buffer[i] = _HEX_SYMBOLS[value & 0xf]; - value >>= 4; - } - return keccak256(buffer); - } - - function _addressToString(address addr) private pure returns (string memory ptr) { - // solhint-disable-next-line no-inline-assembly - assembly { - ptr := mload(0x40) - - // Adjust mem ptr and keep 32 byte aligned - // 32 bytes to store string length; address is 42 bytes long - mstore(0x40, add(ptr, 96)) - - // Store (string length, '0', 'x') (42, 48, 120) - // Single write by offsetting across 32 byte boundary - ptr := add(ptr, 2) - mstore(ptr, 0x2a3078) - - // Write string backwards - for { - // end is at 'x', ptr is at lsb char - let end := add(ptr, 31) - ptr := add(ptr, 71) - } gt(ptr, end) { - ptr := sub(ptr, 1) - addr := shr(4, addr) - } { - let v := and(addr, 0xf) - // if > 9, use ascii 'a-f' (no conditional required) - v := add(v, mul(gt(v, 9), 39)) - // Add ascii for '0' - v := add(v, 48) - mstore8(ptr, v) - } - - // return ptr to point to length (32 + 2 for '0x' - 1) - ptr := sub(ptr, 33) - } - - return string(ptr); - } -} -``` - -## Security Considerations -The core purpose of this EIP is to enhance security and promote a safer way to authenticate wallet control and asset ownership when the main wallet is not needed and assets held by the main wallet do not need to be moved. Consider it a way to do 'read only' authentication. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5131.md diff --git a/EIPS/eip-5139.md b/EIPS/eip-5139.md index c5c4a0d68a1b6d..1a90704898219a 100644 --- a/EIPS/eip-5139.md +++ b/EIPS/eip-5139.md @@ -1,602 +1 @@ ---- -eip: 5139 -title: Remote Procedure Call Provider Lists -description: Format for lists of RPC providers for Ethereum-like chains. -author: Sam Wilson (@SamWilsn) -discussions-to: https://ethereum-magicians.org/t/eip-5139-remote-procedure-call-provider-lists/9517 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-06-06 -requires: 155, 1577 ---- - -## Abstract -This proposal specifies a JSON schema for describing lists of remote procedure call (RPC) providers for Ethereum-like chains, including their supported [EIP-155](./eip-155.md) `CHAIN_ID`. - -## Motivation -The recent explosion of alternate chains, scaling solutions, and other mostly Ethereum-compatible ledgers has brought with it many risks for users. It has become commonplace to blindly add new RPC providers using [EIP-3085](./eip-3085.md) without evaluating their trustworthiness. At best, these RPC providers may be accurate, but track requests; and at worst, they may provide misleading information and frontrun transactions. - -If users instead are provided with a comprehensive provider list built directly by their wallet, with the option of switching to whatever list they so choose, the risk of these malicious providers is mitigated significantly, without sacrificing functionality for advanced users. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### List Validation & Schema - -List consumers (like wallets) MUST validate lists against the provided schema. List consumers MUST NOT connect to RPC providers present only in an invalid list. - -Lists MUST conform to the following JSON Schema: - -```json -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - - "title": "Ethereum RPC Provider List", - "description": "Schema for lists of RPC providers compatible with Ethereum wallets.", - - "$defs": { - "VersionBase": { - "type": "object", - "description": "Version of a list, used to communicate changes.", - - "required": [ - "major", - "minor", - "patch" - ], - - "properties": { - "major": { - "type": "integer", - "description": "Major version of a list. Incremented when providers are removed from the list or when their chain ids change.", - "minimum": 0 - }, - - "minor": { - "type": "integer", - "description": "Minor version of a list. Incremented when providers are added to the list.", - "minimum": 0 - }, - - "patch": { - "type": "integer", - "description": "Patch version of a list. Incremented for any change not covered by major or minor versions, like bug fixes.", - "minimum": 0 - }, - - "preRelease": { - "type": "string", - "description": "Pre-release version of a list. Indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its major, minor, and patch versions.", - "pattern": "^[1-9A-Za-z][0-9A-Za-z]*(\\.[1-9A-Za-z][0-9A-Za-z]*)*$" - } - } - }, - - "Version": { - "type": "object", - "additionalProperties": false, - - "allOf": [ - { - "$ref": "#/$defs/VersionBase" - } - ], - - "properties": { - "major": true, - "minor": true, - "patch": true, - "preRelease": true, - "build": { - "type": "string", - "description": "Build metadata associated with a list.", - "pattern": "^[0-9A-Za-z-]+(\\.[0-9A-Za-z-])*$" - } - } - }, - - "VersionRange": { - "type": "object", - "additionalProperties": false, - - "properties": { - "major": true, - "minor": true, - "patch": true, - "preRelease": true, - "mode": true - }, - - "allOf": [ - { - "$ref": "#/$defs/VersionBase" - } - ], - - "oneOf": [ - { - "properties": { - "mode": { - "type": "string", - "enum": ["^", "="] - }, - "preRelease": false - } - }, - { - "required": [ - "preRelease", - "mode" - ], - - "properties": { - "mode": { - "type": "string", - "enum": ["="] - } - } - } - ] - }, - - "Logo": { - "type": "string", - "description": "A URI to a logo; suggest SVG or PNG of size 64x64", - "format": "uri" - }, - - "ProviderChain": { - "type": "object", - "description": "A single chain supported by a provider", - "additionalProperties": false, - "required": [ - "chainId", - "endpoints" - ], - "properties": { - "chainId": { - "type": "integer", - "description": "Chain ID of an Ethereum-compatible network", - "minimum": 1 - }, - "endpoints": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "format": "uri" - } - } - } - }, - - "Provider": { - "type": "object", - "description": "Description of an RPC provider.", - "additionalProperties": false, - - "required": [ - "chains", - "name" - ], - - "properties": { - "name": { - "type": "string", - "description": "Name of the provider.", - "minLength": 1, - "maxLength": 40, - "pattern": "^[ \\w.'+\\-%/À-ÖØ-öø-ÿ:&\\[\\]\\(\\)]+$" - }, - "logo": { - "$ref": "#/$defs/Logo" - }, - "priority": { - "type": "integer", - "description": "Priority of this provider (where zero is the highest priority.)", - "minimum": 0 - }, - "chains": { - "type": "array", - "items": { - "$ref": "#/$defs/ProviderChain" - } - } - } - }, - - "Path": { - "description": "A JSON Pointer path.", - "type": "string" - }, - - "Patch": { - "items": { - "oneOf": [ - { - "additionalProperties": false, - "required": ["value", "op", "path"], - "properties": { - "path": { - "$ref": "#/$defs/Path" - }, - "op": { - "description": "The operation to perform.", - "type": "string", - "enum": ["add", "replace", "test"] - }, - "value": { - "description": "The value to add, replace or test." - } - } - }, - { - "additionalProperties": false, - "required": ["op", "path"], - "properties": { - "path": { - "$ref": "#/$defs/Path" - }, - "op": { - "description": "The operation to perform.", - "type": "string", - "enum": ["remove"] - } - } - }, - { - "additionalProperties": false, - "required": ["from", "op", "path"], - "properties": { - "path": { - "$ref": "#/$defs/Path" - }, - - "op": { - "description": "The operation to perform.", - "type": "string", - "enum": ["move", "copy"] - }, - "from": { - "$ref": "#/$defs/Path", - "description": "A JSON Pointer path pointing to the location to move/copy from." - } - } - } - ] - }, - "type": "array" - } - }, - - "type": "object", - "additionalProperties": false, - - "required": [ - "name", - "version", - "timestamp" - ], - - "properties": { - "name": { - "type": "string", - "description": "Name of the provider list", - "minLength": 1, - "maxLength": 40, - "pattern": "^[\\w ]+$" - }, - "logo": { - "$ref": "#/$defs/Logo" - }, - "version": { - "$ref": "#/$defs/Version" - }, - "timestamp": { - "type": "string", - "format": "date-time", - "description": "The timestamp of this list version; i.e. when this immutable version of the list was created" - }, - "extends": true, - "changes": true, - "providers": true - }, - - "oneOf": [ - { - "type": "object", - - "required": [ - "extends", - "changes" - ], - - "properties": { - "providers": false, - - "extends": { - "type": "object", - "additionalProperties": false, - - "required": [ - "version" - ], - - "properties": { - "uri": { - "type": "string", - "format": "uri", - "description": "Location of the list to extend, as a URI." - }, - "ens": { - "type": "string", - "description": "Location of the list to extend using EIP-1577." - }, - "version": { - "$ref": "#/$defs/VersionRange" - } - }, - - "oneOf": [ - { - "properties": { - "uri": false, - "ens": true - } - }, - { - "properties": { - "ens": false, - "uri": true - } - } - ] - }, - "changes": { - "$ref": "#/$defs/Patch" - } - } - }, - { - "type": "object", - - "required": [ - "providers" - ], - - "properties": { - "changes": false, - "extends": false, - "providers": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/Provider" - } - } - } - } - ] -} -``` - -For illustrative purposes, the following is an example list following the schema: - -```json -{ - "name": "Example Provider List", - "version": { - "major": 0, - "minor": 1, - "patch": 0, - "build": "XPSr.p.I.g.l" - }, - "timestamp": "2004-08-08T00:00:00.0Z", - "logo": "https://mylist.invalid/logo.png", - "providers": { - "some-key": { - "name": "Frustrata", - "chains": [ - { - "chainId": 1, - "endpoints": [ - "https://mainnet1.frustrata.invalid/", - "https://mainnet2.frustrana.invalid/" - ] - }, - { - "chainId": 3, - "endpoints": [ - "https://ropsten.frustrana.invalid/" - ] - } - ] - }, - "other-key": { - "name": "Sourceri", - "priority": 3, - "chains": [ - { - "chainId": 1, - "endpoints": [ - "https://mainnet.sourceri.invalid/" - ] - }, - { - "chainId": 42, - "endpoints": [ - "https://kovan.sourceri.invalid" - ] - } - ] - } - } -} -``` - -### Versioning - -List versioning MUST follow the [Semantic Versioning 2.0.0](../assets/eip-5139/semver.md) (SemVer) specification. - -The major version MUST be incremented for the following modifications: - - - Removing a provider. - - Changing a provider's key in the `providers` object. - - Removing the last `ProviderChain` for a chain id. - -The major version MAY be incremented for other modifications, as permitted by SemVer. - -If the major version is not incremented, the minor version MUST be incremented if any of the following modifications are made: - - - Adding a provider. - - Adding the first `ProviderChain` of a chain id. - -The minor version MAY be incremented for other modifications, as permitted by SemVer. - -If the major and minor versions are unchanged, the patch version MUST be incremented for any change. - -### Publishing - -Provider lists SHOULD be published to an Ethereum Name Service (ENS) name using [EIP-1577](./eip-1577.md)'s `contenthash` mechanism on mainnet. - -Provider lists MAY instead be published using HTTPS. Provider lists published in this way MUST allow reasonable access from other origins (generally by setting the header `Access-Control-Allow-Origin: *`.) - -### Priority - -Provider entries MAY contain a `priority` field. A `priority` value of zero SHALL indicate the highest priority, with increasing `priority` values indicating decreasing priority. Multiple providers MAY be assigned the same priority. All providers without a `priority` field SHALL have equal priority. Providers without a `priority` field SHALL always have a lower priority than any provider with a `priority` field. - -List consumers MAY use `priority` fields to choose when to connect to a provider, but MAY ignore it entirely. List consumers SHOULD explain to users how their implementation interprets `priority`. - -### List Subtypes - -Provider lists are subdivided into two categories: root lists, and extension lists. A root list contains a list of providers, while an extension list contains a set of modifications to apply to another list. - -#### Root Lists - -A root list has a top-level `providers` key. - -#### Extension Lists - -An extension list has top-level `extends` and `changes` keys. - -##### Specifying a Parent (`extends`) - -The `uri` and `ens` fields SHALL point to a source for the parent list. - -If present, the `uri` field MUST use a scheme specified in [Publishing](#publishing). - -If present, the `ens` field MUST specify an ENS name to be resolved using EIP-1577. - -The `version` field SHALL specify a range of compatible versions. List consumers MUST reject extension lists specifying an incompatible parent version. - -In the event of an incompatible version, list consumers MAY continue to use a previously saved parent list, but list consumers choosing to do so MUST display a prominent warning that the provider list is out of date. - -###### Default Mode - -If the `mode` field is omitted, a parent version SHALL be compatible if and only if the parent's version number matches the left-most non-zero portion in the major, minor, patch grouping. - -For example: - -```javascript -{ - "major": "1", - "minor": "2", - "patch": "3" -} -``` - -Is equivalent to: - -``` ->=1.2.3, <2.0.0 -``` - -And: - -```javascript -{ - "major": "0", - "minor": "2", - "patch": "3" -} -``` - -Is equivalent to: - -``` ->=0.2.3, <0.3.0 -``` - -###### Caret Mode (`^`) - -The `^` mode SHALL behave exactly as the default mode above. - -###### Exact Mode (`=`) - -In `=` mode, a parent version SHALL be compatible if and only if the parent's version number exactly matches the specified version. - -##### Specifying Changes (`changes`) - -The `changes` field SHALL be a JavaScript Object Notation (JSON) Patch document as specified in RFC 6902. - -JSON pointers within the `changes` field MUST be resolved relative to the `providers` field of the parent list. For example, see the following lists for a correctly formatted extension. - -###### Root List - -```json -TODO -``` - -###### Extension List - -```json -TODO -``` - -##### Applying Extension Lists - -List consumers MUST follow this algorithm to apply extension lists: - - 1. Is the current list an extension list? - * Yes: - 1. Ensure that this `from` has not been seen before. - 1. Retrieve the parent list. - 1. Verify that the parent list is valid according to the JSON schema. - 1. Ensure that the parent list is version compatible. - 1. Set the current list to the parent list and go to step 1. - * No: - 1. Go to step 2. - 1. Copy the current list into a variable `$output`. - 1. Does the current list have a child: - * Yes: - 1. Apply the child's `changes` to `providers` in `$output`. - 1. Verify that `$output` is valid according to the JSON schema. - 1. Set the current list to the child. - 1. Go to step 3. - * No: - 1. Replace the current list's `providers` with `providers` from `$output`. - 1. The current list is now the resolved list; return it. - - -List consumers SHOULD limit the number of extension lists to a reasonable number. - -## Rationale - -This specification has two layers (provider, then chain id) instead of a flatter structure so that wallets can choose to query multiple independent providers for the same query and compare the results. - -Each provider may specify multiple endpoints to implement load balancing or redundancy. - -List version identifiers conform to SemVer to roughly communicate the kinds of changes that each new version brings. If a new version adds functionality (eg. a new chain id), then users can expect the minor version to be incremented. Similarly, if the major version is not incremented, list subscribers can assume dapps that work in the current version will continue to work in the next one. - -## Security Considerations - -Ultimately it is up to the end user to decide on what list to subscribe to. Most users will not change from the default list maintained by their wallet. Since wallets already have access to private keys, giving them additional control over RPC providers seems like a small increase in risk. - -While list maintainers may be incentivized (possibly financially) to include or exclude particular providers, actually doing so may jeopardize the legitimacy of their lists. This standard facilitates swapping lists, so if such manipulation is revealed, users are free to swap to a new list with little effort. - -If the list chosen by the user is published using EIP-1577, the list consumer has to have access to ENS in some way. This creates a paradox: how do you query Ethereum without an RPC provider? This paradox creates an attack vector: whatever method the list consumer uses to fetch the list can track the user, and even more seriously, **can lie about the contents of the list**. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5139.md diff --git a/EIPS/eip-5143.md b/EIPS/eip-5143.md index 72afb4bc0d02d3..b9cc2dce4e2e18 100644 --- a/EIPS/eip-5143.md +++ b/EIPS/eip-5143.md @@ -1,224 +1 @@ ---- -eip: 5143 -title: Slippage Protection for Tokenized Vault -description: An extension of EIP-4626 supporting improved EOA interactions. -author: Hadrien Croubois (@amxx) -discussions-to: https://ethereum-magicians.org/t/eip-5143-slippage-protection-for-tokenized-vaults/9554 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-06-09 -requires: 20, 4626 ---- - -## Abstract - -The following standard extends the [EIP-4626](./eip-4626.md) Tokenized Vault standard with functions dedicated to the safe interaction between EOAs and the vault when price is subject to slippage. - -## Motivation - -[EIP-4626](./eip-4626.md) security considerations section states that: -> "If implementors intend to support EOA account access directly, they should consider adding an additional function call for deposit/mint/withdraw/redeem with the means to accommodate slippage loss or unexpected deposit/withdrawal limits, since they have no other means to revert the transaction if the exact output amount is not achieved." - -Yet, EIP-4626 does not standardize the corresponding function signatures and behaviors. For improved interroperability, and better support by wallets, it is essential that this optional functions are also standardized. - -## Specification - -This ERC is an extension of EIP-4626. Any contract implementing it MUST also implement EIP-4626. - -### Methods - -#### deposit - -Overloaded version of ERC-4626's `deposit`. - -Mints `shares` Vault shares to `receiver` by depositing exactly `assets` of underlying tokens. - -MUST emit the `Deposit` event. - -MUST support [EIP-20](./eip-20.md) `approve` / `transferFrom` on `asset` as a deposit flow. -MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the `deposit` execution, and are accounted for during `deposit`. - -MUST revert if all of `assets` cannot be deposited (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). -MUST revert if depositing `assets` underlying asset mints less then `minShares` shares. - -Note that most implementations will require pre-approval of the Vault with the Vault's underlying `asset` token. - -```yaml -- name: deposit - type: function - stateMutability: nonpayable - - inputs: - - name: assets - type: uint256 - - name: receiver - type: address - - name: minShares - type: uint256 - - outputs: - - name: shares - type: uint256 -``` - -#### mint - -Overloaded version of ERC-4626's `mint`. - -Mints exactly `shares` Vault shares to `receiver` by depositing `assets` of underlying tokens. - -MUST emit the `Deposit` event. - -MUST support ERC-20 `approve` / `transferFrom` on `asset` as a mint flow. -MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the `mint` execution, and are accounted for during `mint`. - -MUST revert if all of `shares` cannot be minted (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). -MUST revert if minting `shares` shares cost more then `maxAssets` underlying tokens. - -Note that most implementations will require pre-approval of the Vault with the Vault's underlying `asset` token. - -```yaml -- name: mint - type: function - stateMutability: nonpayable - - inputs: - - name: shares - type: uint256 - - name: receiver - type: address - - name: maxAssets - type: uint256 - - outputs: - - name: assets - type: uint256 -``` - -#### withdraw - -Overloaded version of ERC-4626's `withdraw`. - -Burns `shares` from `owner` and sends exactly `assets` of underlying tokens to `receiver`. - -MUST emit the `Withdraw` event. - -MUST support a withdraw flow where the shares are burned from `owner` directly where `owner` is `msg.sender` or `msg.sender` has ERC-20 approval over the shares of `owner`. -MAY support an additional flow in which the shares are transferred to the Vault contract before the `withdraw` execution, and are accounted for during `withdraw`. - -MUST revert if all of `assets` cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). -MUST revert if withdrawing `assets` underlying tokens requires burning more then `maxShares` shares. - -Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. - -```yaml -- name: withdraw - type: function - stateMutability: nonpayable - - inputs: - - name: assets - type: uint256 - - name: receiver - type: address - - name: owner - type: address - - name: maxShares - type: uint256 - - outputs: - - name: shares - type: uint256 -``` - -#### redeem - -Overloaded version of ERC-4626's `redeem`. - -Burns exactly `shares` from `owner` and sends `assets` of underlying tokens to `receiver`. - -MUST emit the `Withdraw` event. - -MUST support a redeem flow where the shares are burned from `owner` directly where `owner` is `msg.sender` or `msg.sender` has ERC-20 approval over the shares of `owner`. -MAY support an additional flow in which the shares are transferred to the Vault contract before the `redeem` execution, and are accounted for during `redeem`. - -MUST revert if all of `shares` cannot be redeemed (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). -MUST revert if redeeming `shares` shares sends less than `minAssets` underlying tokens to `receiver`. - -Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. - -```yaml -- name: redeem - type: function - stateMutability: nonpayable - - inputs: - - name: shares - type: uint256 - - name: receiver - type: address - - name: owner - type: address - - name: minAssets - type: uint256 - - outputs: - - name: assets - type: uint256 -``` - -## Rationale - -This ERC's functions do not replace ERC-4626 equivalent mechanisms. They are additional (overloaded) methods designed to protect EOAs interacting with the vault. - -When smart contracts interact with an ERC-4626 vault, they can preview any operation using the dedicated functions before executing the operation. This can be done -atomically, with no risk of price change. This is not true of EOA, which will preview their operations on a UI, sign a transaction, and have it mined later. -Between the preview and the transaction being executed, the blockchain state might change, resulting in unexpected outcomes. In particular, frontrunning -make EOA's interractons with an ERC-4626 vault possibly risky. - -Other projects in the DeFi spaces, such as decentralized exchanges, already include similar mechanisms so a user can request its transaction reverts if the -resulting exchange rate is not considered good enough. - -Implementing This ERC on top of an ERC-4626 contract can be done very easily. It just requires calling the corresponding ERC-4626 function and adding a revert -check on the returned value. - -### Alternative approaches - -This ERC aims at solving the security concerns (describes in the motivation section) at the vault level. For completeness, we have to mention that these issues can also be addressed using a generic ERC-4626 router, similar to how Uniswap V2 & V3 use a router to provide good user workflows on top of the Uniswap pairs. The router approach is possibly more versatile and leaves more room for evolutions (the router can be replaced at any point) but it also leads to more expensive operations because the router needs to take temporary custody of the tokens going into the vault. - -## Reference Implementation - -Given an existing ERC-4626 implementation - -``` solidity -contract ERC5143 is ERC4626 { - function deposit(uint256 assets, address receiver, uint256 minShares) public virtual returns (uint256) { - uint256 shares = deposit(assets, receiver); - require(shares >= minShares, "ERC5143: deposit slippage protection"); - return shares; - } - function mint(uint256 shares, address receiver, uint256 maxAssets) public virtual returns (uint256) { - uint256 assets = mint(shares, receiver); - require(assets <= maxAssets, "ERC5143: mint slippage protection"); - return assets; - } - function withdraw(uint256 assets, address receiver, address owner, uint256 maxShares) public virtual returns (uint256) { - uint256 shares = withdraw(assets, receiver, owner); - require(shares <= maxShares, "ERC5143: withdraw slippage protection"); - return shares; - } - function redeem(uint256 shares, address receiver, address owner, uint256 minAssets) public virtual returns (uint256) { - uint256 assets = redeem(shares, receiver, owner); - require(assets >= minAssets, "ERC5143: redeem slippage protection"); - return assets; - } -} -``` -## Security Considerations - -This ERC addresses one of the security consideration raised by ERC-4626. Other considerations still apply. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5143.md diff --git a/EIPS/eip-5164.md b/EIPS/eip-5164.md index 2180c02f4bf564..6982f58194884a 100644 --- a/EIPS/eip-5164.md +++ b/EIPS/eip-5164.md @@ -1,223 +1 @@ ---- -eip: 5164 -title: Cross-Chain Execution -description: Defines an interface that supports execution across EVM networks. -author: Brendan Asselstine (@asselstine), Pierrick Turelier (@PierrickGT), Chris Whinfrey (@cwhinfrey) -discussions-to: https://ethereum-magicians.org/t/eip-5164-cross-chain-execution/9658 -status: Review -type: Standards Track -category: ERC -created: 2022-06-14 ---- - -## Abstract - -This specification defines a cross-chain execution interface for EVM-based blockchains. Implementations of this specification will allow contracts on one chain to call contracts on another by sending a cross-chain message. - -The specification defines two components: the "Message Dispatcher" and the "Message Executor". The Message Dispatcher lives on the calling side, and the executor lives on the receiving side. When a message is sent, a Message Dispatcher will move the message through a transport layer to a Message Executor, where they are executed. Implementations of this specification must implement both components. - -## Motivation - -Many Ethereum protocols need to coordinate state changes across multiple EVM-based blockchains. These chains often have native or third-party bridges that allow Ethereum contracts to execute code. However, bridges have different APIs so bridge integrations are custom. Each one affords different properties; with varying degrees of security, speed, and control. Defining a simple, common specification will increase code re-use and allow us to use common bridge implementations. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -This specification allows contracts on one chain to send messages to contracts on another chain. There are two key interfaces that needs to be implemented: - -- `MessageDispatcher` -- `MessageExecutor` - -The `MessageDispatcher` lives on the origin chain and dispatches messages to the `MessageExecutor` for execution. The `MessageExecutor` lives on the destination chain and executes dispatched messages. - -### MessageDispatcher - -The `MessageDispatcher` lives on the chain from which messages are sent. The Dispatcher's job is to broadcast messages through a transport layer to one or more `MessageExecutor` contracts. - -A unique `messageId` MUST be generated for each message or message batch. The message identifier MUST be unique across chains and dispatchers. This can be achieved by hashing a tuple of `chainId, dispatcherAddress, messageNonce` where messageNonce is a monotonically increasing integer per message. - -#### MessageDispatcher Methods - -**dispatchMessage** - -Will dispatch a message to be executed by the `MessageExecutor` on the destination chain specified by `toChainId`. - -`MessageDispatcher`s MUST emit the `MessageDispatched` event when a message is dispatched. - -`MessageDispatcher`s MUST revert if `toChainId` is not supported. - -`MessageDispatcher`s MUST forward the message to a `MessageExecutor` on the `toChainId`. - -`MessageDispatcher`s MUST use a unique `messageId` for each message. - -`MessageDispatcher`s MUST return the `messageId` to allow the message sender to track the message. - -`MessageDispatcher`s MAY require payment. - -```solidity -interface MessageDispatcher { - function dispatchMessage(uint256 toChainId, address to, bytes calldata data) external payable returns (bytes32 messageId); -} -``` - -```yaml -- name: dispatchMessage - type: function - stateMutability: payable - inputs: - - name: toChainId - type: uint256 - - name: to - type: address - - name: data - type: bytes - outputs: - - name: messageId - type: bytes32 -``` - -#### MessageDispatcher Events - -**MessageDispatched** - -The `MessageDispatched` event MUST be emitted by the `MessageDispatcher` when an individual message is dispatched. - -```solidity -interface MessageDispatcher { - event MessageDispatched( - bytes32 indexed messageId, - address indexed from, - uint256 indexed toChainId, - address to, - bytes data, - ); -} -``` - -```yaml -- name: MessageDispatched - type: event - inputs: - - name: messageId - indexed: true - type: bytes32 - - name: from - indexed: true - type: address - - name: toChainId - indexed: true - type: uint256 - - name: to - type: address - - name: data - type: bytes -``` - -### MessageExecutor - -The `MessageExecutor` executes dispatched messages and message batches. Developers must implement a `MessageExecutor` in order to execute messages on the receiving chain. - -The `MessageExecutor` will execute a messageId only once, but may execute messageIds in any order. This specification makes no ordering guarantees, because messages and message batches may travel non-sequentially through the transport layer. - -#### Execution - -`MessageExecutor`s SHOULD verify all message data with the bridge transport layer. - -`MessageExecutor`s MUST NOT successfully execute a message more than once. - -`MessageExecutor`s MUST revert the transaction when a message fails to be executed allowing the message to be retried at a later time. - -**Calldata** - -`MessageExecutor`s MUST append the ABI-packed (`messageId`, `fromChainId`, `from`) to the calldata for each message being executed. This allows the receiver of the message to verify the cross-chain sender and the chain that the message is coming from. - -```solidity -to.call(abi.encodePacked(data, messageId, fromChainId, from)); -``` - -```yaml -- name: calldata - type: bytes - inputs: - - name: data - type: bytes - - name: messageId - type: bytes32 - - name: fromChainId - type: uint256 - - name: from - type: address -``` - -#### MessageExecutor Events - -**MessageIdExecuted** - -`MessageIdExecuted` MUST be emitted once a message or message batch has been executed. - -```solidity -interface MessageExecutor { - event MessageIdExecuted( - uint256 indexed fromChainId, - bytes32 indexed messageId - ); -} -``` - -```yaml -- name: MessageIdExecuted - type: event - inputs: - - name: fromChainId - indexed: true - type: uint256 - - name: messageId - indexed: true - type: bytes32 -``` - -#### MessageExecutor Errors - -**MessageAlreadyExecuted** - -`MessageExecutor`s MUST revert if a messageId has already been executed and SHOULD emit a `MessageIdAlreadyExecuted` custom error. - -```solidity -interface MessageExecutor { - error MessageIdAlreadyExecuted( - bytes32 messageId - ); -} -``` - -**MessageFailure** - -`MessageExecutor`s MUST revert if an individual message fails and SHOULD emit a `MessageFailure` custom error. - -```solidity -interface MessageExecutor { - error MessageFailure( - bytes32 messageId, - bytes errorData - ); -} -``` - -## Rationale - -The `MessageDispatcher` can be coupled to one or more `MessageExecutor`. It is up to bridges to decide how to couple the two. Users can easily bridge a message by calling `dispatchMessage` without being aware of the `MessageExecutor` address. Messages can also be traced by a client using the data logged by the `MessageIdExecuted` event. - -Some bridges may require payment in the native currency, so the `dispatchMessage` function is payable. - -## Backwards Compatibility - -This specification is compatible with existing governance systems as it offers simple cross-chain execution. - -## Security Considerations - -Bridge trust profiles are variable, so users must understand that bridge security depends on the implementation. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5164.md diff --git a/EIPS/eip-5169.md b/EIPS/eip-5169.md index f9023b301807ba..bae0ee344fa8b4 100644 --- a/EIPS/eip-5169.md +++ b/EIPS/eip-5169.md @@ -1,211 +1 @@ ---- -eip: 5169 -title: Client Script URI for Token Contracts -description: Add a scriptURI to point to an executable script associated with the functionality of the token. -author: James (@JamesSmartCell), Weiwu (@weiwu-zhang) -discussions-to: https://ethereum-magicians.org/t/eip-5169-client-script-uri-for-token-contracts/9674 -status: Final -type: Standards Track -category: ERC -created: 2022-05-03 -requires: 20, 165, 721, 777, 1155 ---- - -## Abstract - -This EIP provides a contract interface adding a `scriptURI()` function for locating executable scripts associated with the token. - -## Motivation - -Often, smart contract authors want to provide some user functionality to their tokens through client scripts. The idea is made popular with function-rich NFTs. It's important that a token's contract is linked to its client script, since the client script may carry out trusted tasks such as creating transactions for the user. - -This EIP allows users to be sure they are using the correct script through the contract by providing a URI to an official script, made available with a call to the token contract itself (`scriptURI`). This URI can be any RFC 3986-compliant URI, such as a link to an IPFS multihash, GitHub gist, or a cloud storage provider. Each contract implementing this EIP implements a `scriptURI` function which returns the download URI to a client script. The script provides a client-side executable to the hosting token. Examples of such a script could be: - -- A 'miniDapp', which is a cut-down DApp tailored for a single token. -- A 'TokenScript' which provides TIPS from a browser wallet. -- A 'TokenScript' that allows users to interact with contract functions not normally provided by a wallet, eg 'mint' function. -- An extension that is downloadable to the hardware wallet with an extension framework, such as Ledger. -- JavaScript instructions to operate a smartlock, after owner receives authorization token in their wallet. - -### Overview - -With the discussion above in mind, we outline the solution proposed by this EIP. For this purpose, we consider the following variables: - -- `SCPrivKey`: The private signing key to administrate a smart contract implementing this EIP. Note that this doesn't have to be a new key especially added for this EIP. Most smart contracts made today already have an administration key to manage the tokens issued. It can be used to update the `scriptURI`. - -- `newScriptURI`: an array of URIs for different ways to find the client script. - -We can describe the life cycle of the `scriptURI` functionality: - -- Issuance - -1. The token issuer issues the tokens and a smart contract implementing this EIP, with the admin key for the smart contract being `SCPrivKey`. -2. The token issuer calls `setScriptURI` with the `scriptURI`. - -- Update `scriptURI` - -1. The token issuer stores the desired `script` at all the new URI locations and constructs a new `scriptURI` structure based on this. -2. The token issuer calls `setScriptURI` with the new `scriptURI` structure. - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -We define a scriptURI element using the `string[]`. -Based on this, we define the smart contract interface below: - -```solidity -interface IERC5169 { - /// @dev This event emits when the scriptURI is updated, - /// so wallets implementing this interface can update a cached script - event ScriptUpdate(string[] newScriptURI); - - /// @notice Get the scriptURI for the contract - /// @return The scriptURI - function scriptURI() external view returns(string[] memory); - - /// @notice Update the scriptURI - /// emits event ScriptUpdate(scriptURI memory newScriptURI); - function setScriptURI(string[] memory newScriptURI) external; -} -``` - -The interface MUST be implemented under the following constraints: - -- The smart contract implementing `IERC5169` MUST store variables `address owner` in its state. - -- The smart contract implementing `IERC5169` MUST set `owner=msg.sender` in its constructor. - -- The `ScriptUpdate(...)` event MUST be emitted when the ```setScriptURI``` function updates the `scriptURI`. - -- The `setScriptURI(...)` function MUST validate that `owner == msg.sender` *before* executing its logic and updating any state. - -- The `setScriptURI(...)` function MUST update its internal state such that `currentScriptURI = newScriptURI`. - -- The `scriptURI()` function MUST return the `currentScriptURI` state. - -- The `scriptURI()` function MAY be implemented as pure or view. - -- Any user of the script learned from `scriptURI` MUST validate the script is either at an immutable location, its URI contains its hash digest, or it implements the separate `Authenticity for Client Script` EIP, which asserts authenticity using signatures instead of a digest. - -## Rationale - -This method avoids the need for building secure and certified centralized hosting and allows scripts to be hosted anywhere: IPFS, GitHub or cloud storage. - -## Backwards Compatibility - -This standard is backwards-compatible with most existing token standards, including the following commonly-used ones: - -- [ERC-20](./eip-20.md) -- [ERC-721](./eip-721.md) -- [ERC-777](./eip-777.md) -- [ERC-1155](./eip-1155.md) - -## Test Cases - -### Test Contract - -```solidity - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./IERC5169.sol"; -contract ERC5169 is IERC5169, Ownable { - string[] private _scriptURI; - function scriptURI() external view override returns(string[] memory) { - return _scriptURI; - } - - function setScriptURI(string[] memory newScriptURI) external onlyOwner override { - _scriptURI = newScriptURI; - - emit ScriptUpdate(newScriptURI); - } -} - -``` - -### Test Cases - -```ts - -const { expect } = require('chai'); -const { BigNumber, Wallet } = require('ethers'); -const { ethers, network, getChainId } = require('hardhat'); - -describe('ERC5169', function () { - before(async function () { - this.ERC5169 = await ethers.getContractFactory('ERC5169'); - }); - - beforeEach(async function () { - // targetNFT - this.erc5169 = await this.ERC5169.deploy(); - }); - - it('Should set script URI', async function () { - const scriptURI = [ - 'uri1', 'uri2', 'uri3' - ]; - - await expect(this.erc5169.setScriptURI(scriptURI)) - .emit(this.erc5169, 'ScriptUpdate') - .withArgs(scriptURI); - - const currentScriptURI = await this.erc5169.scriptURI(); - - expect(currentScriptURI.toString()).to.be.equal(scriptURI.toString()); - }); - -``` - -## Reference Implementation - -An intuitive implementation is the STL office door token. This NFT is minted and transferred to STL employees. The TokenScript attached to the token contract via the `scriptURI()` function contains instructions on how to operate the door interface. This takes the form of: - -1. Query for challenge string (random message from IoT interface eg 'Apples-5E3FA1'). - -2. Receive and display challenge string on Token View, and request 'Sign Personal'. - -3. On obtaining the signature of the challenge string, send back to IoT device. - -4. IoT device will unlock door if ec-recovered address holds the NFT. - -With `scriptURI()` the experience is greatly enhanced as the flow for the user is: - -1. Receive NFT. - -2. Use authenticated NFT functionality in the wallet immediately. - -The project with contract, TokenScript and IoT firmware is in use by Smart Token Labs office door and numerous other installations. An example implementation contract: [ERC-5169 Contract Example](../assets/eip-5169/contract/ExampleContract.sol) and TokenScript: [ERC-5169 TokenScript Example](../assets/eip-5169/tokenscript/ExampleScript.xml). Links to the firmware and full sample can be found in the associated discussion linked in the header. -The associated TokenScript can be read from the contract using `scriptURI()`. - -### Script location - -While the most straightforward solution to facilitate specific script usage associated with NFTs, is clearly to store such a script on the smart contract. However, this has several disadvantages: - -1. The smart contract signing key is needed to make updates, causing the key to become more exposed, as it is used more often. - -2. Updates require smart contract interaction. If frequent updates are needed, smart contract calls can become an expensive hurdle. - -3. Storage fee. If the script is large, updates to the script will be costly. A client script is typically much larger than a smart contract. - -For these reasons, storing volatile data, such as token enhancing functionality, on an external resource makes sense. Such an external resource can be either be hosted centrally, such as through a cloud provider, or privately hosted through a private server, or decentralized hosted, such as the interplanetary filesystem. - -While centralized storage for a decentralized functionality goes against the ethos of web3, fully decentralized solutions may come with speed, price or space penalties. This EIP handles this by allowing the function `ScriptURI` to return multiple URIs, which could be a mix of centralized, individually hosted and decentralized locations. - -While this EIP does not dictate the format of the stored script, the script itself could contain pointers to multiple other scripts and data sources, allowing for advanced ways to expand token scripts, such as lazy loading. -The handling of integrity of such secondary data sources is left dependent on the format of the script. - -## Security Considerations - -**When a server is involved** - -When the client script does not purely rely on connection to a blockchain node, but also calls server APIs, the trustworthiness of the server API is called into question. This EIP does not provide any mechanism to assert the authenticity of the API access point. Instead, as long as the client script is trusted, it's assumed that it can call any server API in order to carry out token functions. This means the client script can mistrust a server API access point. - -**When the scriptURI doesn't contain integrity (hash) information** - -We separately authored `Authenticity for Client Script` EIP to guide on how to use digital signatures efficiently and concisely to ensure authenticity and integrity of scripts not stored at a URI which is a digest of the script itself. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5169.md diff --git a/EIPS/eip-5173.md b/EIPS/eip-5173.md index cbe7a4bce776b8..add35324b0b974 100644 --- a/EIPS/eip-5173.md +++ b/EIPS/eip-5173.md @@ -1,398 +1 @@ ---- -eip: 5173 -title: NFT Future Rewards (nFR) -description: A multigenerational reward mechanism that rewards‌ all ‌owners of non-fungible tokens (NFT). -author: Yale ReiSoleil (@longnshort), dRadiant (@dRadiant), D Wang, PhD -discussions-to: https://ethereum-magicians.org/t/non-fungible-future-rewards-token-standard/9203 -status: Draft -type: Standards Track -category: ERC -created: 2022-05-08 -requires: 165, 721 ---- - -## Abstract - -In the proposed EIP, we introduce the Future Rewards (FR) extension for [ERC-721](./eip-721.md) tokens (NFTs), allowing owners to benefit from future price increases even after selling their tokens. With [ERC-5173](./eip-5173.md), we establish a Participatory Value Amplification framework where creators, buyers, and sellers collaborate to amplify value and build wealth together. This innovative approach tackles the challenges faced by traders, creating a fairer and more profitable system that goes beyond zero-sum thinking. Aligned interests between the service provider and users create a sustainable and beneficial trading environment. ERC-5173 compliant token owners enjoy price increases during holding and continue to receive Future Rewards (FRs) even after selling. By eliminating division and promoting collective prosperity, ERC-5173 fosters strong bonds among participants. All sellers, including the original Minter, receive equal FR distributions through the NFT Future Rewards (nFR) framework, ensuring fair sharing of profits across historical ownership. - -## Motivation - -The current trading landscape is rife with unfair practices such as spoofing, insider trading, and wash trading. These techniques often lead to losses for average traders who are caught in the cycle of greed and fear. However, with the emergence of non-fungible tokens (NFTs) and the potential to track every transaction, we have the opportunity to disrupt this unequal value distribution. - -The implementation of ERC-5173 sets a standard for profit sharing throughout the token's ownership history, benefiting all market participants. It introduces a value-amplification system where buyers and owners are rewarded for their contributions to the price discovery process. This model aligns interests and creates a mutually beneficial economic rule for both buyers and sellers. - -NFTs, unlike physical art and collectibles, can accurately reflect the contributions of their owners to their value. By recording every price change of each [ERC-721](./eip-721.md) token, we can establish a Future Rewards program that fairly compensates owners. This program aims to level the playing field and provide average traders with a better chance at success. - -In addition to promoting a new gift economic model, our framework discourages any illicit deals that bypass the rules set by artists and marketplaces. It promotes transparency and integrity within the trading ecosystem. - -When applied to wrapped [ERC-20](./eip-20.md) token trading, this value-amplification framework revolutionizes the asset transaction industry by incorporating identities into the time and sales (T&S) data. This inclusive feature provides a comprehensive view of each transaction, adding a new dimension to trading. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -The following is an extension of the [ERC-721](./eip-721.md) standard. - -[ERC-721](./eip-721.md)-compliant contracts MAY implement this EIP for rewards to provide a standard method of rewarding future buyers and previous owners with realized profits in the future. - -Implementers of this standard MUST have all of the following functions: - -```solidity - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/* - * - * @dev Interface for the Future Rewards Token Standard. - * - * A standardized way to receive future rewards for non-fungible tokens (NFTs.) - * - */ -interface IERC5173 is IERC165 { - - event FRClaimed(address indexed account, uint256 indexed amount); - - event FRDistributed(uint256 indexed tokenId, uint256 indexed soldPrice, uint256 indexed allocatedFR); - - event Listed(uint256 indexed tokenId, uint256 indexed salePrice); - - event Unlisted(uint256 indexed tokenId); - - event Bought(uint256 indexed tokenId, uint256 indexed salePrice); - - function list(uint256 tokenId, uint256 salePrice) external; - - function unlist(uint256 tokenId) external; - - function buy(uint256 tokenId) payable external; - - function releaseFR(address payable account) external; - - function retrieveFRInfo(uint256 tokenId) external returns(uint8, uint256, uint256, uint256, uint256, address[] memory); - - function retrieveAllottedFR(address account) external returns(uint256); - - function retrieveListInfo(uint256 tokenId) external returns(uint256, address, bool); - -} - -``` - -An nFR contract MUST implement and update for each Token ID. The data in the `FRInfo` struct MAY either be stored wholly in a single mapping, or MAY be broken down into several mappings. The struct MUST either be exposed in a public mapping or mappings, or MUST have public functions that access the private data. This is for client-side data fetching and verification. - -```solidity - -struct FRInfo { - uint8 numGenerations; // Number of generations corresponding to that Token ID - uint256 percentOfProfit; // Percent of profit allocated for FR, scaled by 1e18 - uint256 successiveRatio; // The common ratio of successive in the geometric sequence, used for distribution calculation - uint256 lastSoldPrice; // Last sale price in ETH mantissa - uint256 ownerAmount; // Amount of owners the Token ID has seen - address[] addressesInFR; // The addresses currently in the FR cycle -} - -struct ListInfo { - uint256 salePrice; // ETH mantissa of the listed selling price - address lister; // Owner/Lister of the Token - bool isListed; // Boolean indicating whether the Token is listed or not -} - -``` - -Additionally, an nFR smart contract MUST store the corresponding `ListInfo` for each Token ID in a mapping. A method to retrieve a Token ID’s corresponding `ListInfo` MUST also be accessible publicly. - -An nFR smart contract MUST also store and update the amount of Ether allocated to a specific address using the `_allotedFR` mapping. The `_allottedFR` mapping MUST either be public or have a function to fetch the FR payment allotted to a specific address. - -### Percent Fixed Point - -The `allocatedFR` MUST be calculated using a percentage fixed point with a scaling factor of 1e18 (X/1e18) - such as "5e16" - for 5%. This is REQUIRED to maintain uniformity across the standard. The max and min values would be - 1e18 - 1. - -### Default FR Info - -A default `FRInfo` MUST be stored in order to be backward compatible with [ERC-721](./eip-721.md) mint functions. It MAY also have a function to update the `FRInfo`, assuming it has not been hard-coded. - -### ERC-721 Overrides - -An nFR-compliant smart contract MUST override the [ERC-721](./eip-721.md) `_mint`, `_transfer`, and `_burn` functions. When overriding the `_mint` function, a default FR model is REQUIRED to be established if the mint is to succeed when calling the [ERC-721](./eip-721.md) `_mint` function and not the nFR `_mint` function. It is also to update the owner amount and directly add the recipient address to the FR cycle. When overriding the `_transfer` function, the smart contract SHALL consider the NFT as sold for 0 ETH, and update the state accordingly after a successful transfer. This is to prevent FR circumvention. Additionally, the `_transfer` function SHALL prevent the caller from transferring the token to themselves or an address that is already in the FR sliding window, this can be done through a require statement that ensures that the sender or an address in the FR sliding window is not the recipient, otherwise, it’d be possible to fill up the FR sequence with one’s own address or duplicate addresses. Finally, when overriding the `_burn` function, the smart contract SHALL delete the `FRInfo` and `ListInfo` corresponding to that Token ID after a successful burn. - -Additionally, the [ERC-721](./eip-721.md) `_checkOnERC721Received` function MAY be explicitly called after mints and transfers if the smart contract aims to have safe transfers and mints. - -### Safe Transfers - -If the wallet/broker/auction application will accept safe transfers, then it MUST implement the [ERC-721](./eip-721.md) wallet interface. - -### Listing, Unlisting, and Buying - -The `list`, `unlist`, and `buy` functions MUST be implemented, as they provide the capability to sell a token. - -```solidity -function list(uint256 tokenId, uint256 salePrice) public virtual override { - //... -} - - -function unlist(uint256 tokenId) public virtual override { - //... -} - -function buy(uint256 tokenId) public virtual override payable { - //... -} - -``` - -The `list` function accepts a `tokenId` and a `salePrice` and updates the corresponding `ListInfo` for that given `tokenId` after ensuring that the `msg.sender` is either approved or the owner of the token. The `list` function SHOULD emit the `Listed` event. The function signifies that the token is listed and at what price it is listed for. - -The `unlist` function accepts a `tokenId` and it deletes the corresponding `ListInfo` after the owner verifications have been met. The `unlist` function SHOULD emit the `Unlisted` event. - -The `buy` function accepts a `tokenId` and MUST be payable. It MUST verify that the `msg.value` matches the token’s `salePrice` and that the token is listed, before proceeding and calling the FR `_transferFrom` function. The function MUST also verify that the buyer is not already in the FR sliding window. This is to ensure the values are valid and will also allow for the necessary FR to be held in the contract. The `buy` function SHOULD emit the `Bought` event. - - -### Future Rewards `_transferFrom` Function - -The FR `_transferFrom` function MUST be called by all nFR-supporting smart contracts, though the accommodations for non-nFR-supporting contracts MAY also be implemented to ensure backwards compatibility. - -```solidity - -function transferFrom(address from, address to, uint256 tokenId, uint256 soldPrice) public virtual override payable { - //... -} - -``` - -Based on the stored `lastSoldPrice`, the smart contract will determine whether the sale was profitable after calling the [ERC-721](./eip-721.md) transfer function and transferring the NFT. If it was not profitable, the smart contract SHALL update the last sold price for the corresponding Token ID, increment the owner amount, shift the generations, and transfer all of the `msg.value` to the `lister` depending on the implementation. Otherwise, if the transaction was profitable, the smart contract SHALL call the `_distributeFR` function, then update the `lastSoldPrice`, increment the owner amount, and finally shift generations. The `_distributeFR` function or the FR `_transferFrom` MUST return the difference between the allocated FR that is to be distributed amongst the `_addressesInFR` and the `msg.value` to the `lister`. Once the operations have completed, the function MUST clear the corresponding `ListInfo`. Similarly to the `_transfer` override, the FR `_transferFrom` SHALL ensure that the recipient is not the sender of the token or an address in the FR sliding window. - -### Future Rewards Calculation - -Marketplaces that support this standard MAY implement various methods of calculating or transferring Future Rewards to the previous owners. - -```solidity - -function _calculateFR(uint256 totalProfit, uint256 buyerReward, uint256 successiveRatio, uint256 ownerAmount, uint256 windowSize) pure internal virtual returns(uint256[] memory) { - //... -} - -``` - -In this example (*Figure 1*), a seller is REQUIRED to share a portion of their net profit with 10 previous holders of the token. Future Rewards will also be paid to the same seller as the value of the token increases from up to 10 subsequent owners. - -When an owner loses money during their holding period, they MUST NOT be obligated to share Future Rewards distributions, since there is no profit to share. However, he SHALL still receive a share of Future Rewards distributions from future generations of owners, if they are profitable. - -![Figure 1: Geometric sequence distribution](../assets/eip-5173/Total_FR_Payout_Distribution-geo.png) - -*Figure 1: Geometric sequence distribution* - -The buyers/owners receive a portion ( r ) of the realized profit (P ) from an NFT transaction. The remaining proceeds go to the seller. - -As a result of defining a sliding window mechanism ( n ), we can determine which previous owners will receive distributions. The owners are arranged in a queue, starting with the earliest owner and ending with the owner immediately before the current owner (the Last Generation). The First Generation is the last of the next n generations. There is a fixed-size profit distribution window from the First Generation to the Last Generation. - -The profit distribution SHALL be only available to previous owners who fall within the window. - -In this example, there SHALL be a portion of the proceeds awarded to the Last Generation owner (the owner immediately prior to the current seller) based on the geometric sequence in which profits are distributed. The larger portion of the proceeds SHALL go to the Mid-Gen owners, the earlier the greater, until the last eligible owner is determined by the sliding window, the First Generation. Owners who purchase earlier SHALL receive a greater reward, with first-generation owners receiving the greatest reward. - -### Future Rewards Distribution - -![Figure 2: NFT Owners' Future Rewards (nFR)](../assets/eip-5173/nFR_Standard_Outline.jpeg) - -*Figure 2: NFT Owners' Future Rewards (nFR)* - -*Figure 2* illustrates an example of a five-generation Future Rewards Distribution program based on an owner's realized profit. - -```solidity - -function _distributeFR(uint256 tokenId, uint256 soldPrice) internal virtual { - //... - - emit FRDistributed(tokenId, soldPrice, allocatedFR); - } - -``` - -The `_distributeFR` function MUST be called in the FR `_transferFrom` function if there is a profitable sale. The function SHALL determine the addresses eligible for FR, which would essentially be, excluding the last address in `addressesInFR` in order to prevent any address from paying itself. If the function determines there are no addresses eligible, i.e., it is the first sale, then it SHALL either `return 0` if `_transferFrom` is handling FR payment or send `msg.value` to the `lister`. The function SHALL calculate the difference between the current sale price and the `lastSoldPrice`, then it SHALL call the `_calculateFR` function to receive the proper distribution of FR. Then it SHALL distribute the FR accordingly, making order adjustments as necessary. Then, the contract SHALL calculate the total amount of FR that was distributed (`allocatedFR`), in order to return the difference of the `soldPrice` and `allocatedFR` to the `lister`. Finally, it SHALL emit the `FRDistributed` event. Additionally, the function MAY return the allocated FR, which would be received by the FR `_transferFrom` function, if the `_transferFrom` function is sending the `allocatedFR` to the `lister`. - -### Future Rewards Claiming - -The future Rewards payments SHOULD utilize a pull-payment model, similar to that demonstrated by OpenZeppelin with their PaymentSplitter contract. The event FRClaimed would be triggered after a successful claim has been made. - -```solidity - -function releaseFR(address payable account) public virtual override { - //... -} - -``` - -### Owner Generation Shifting - -The `_shiftGenerations` function MUST be called regardless of whether the sale was profitable or not. As a result, it will be called in the `_transfer` [ERC-721](./eip-721.md) override function and the FR `transferFrom` function. The function SHALL remove the oldest account from the corresponding `_addressesInFR` array. This calculation will take into account the current length of the array versus the total number of generations for a given token ID. - -## Rationale - -### Fixed Percentage to 10^18 - -Considering Fixed-Point Arithmetic is to be enforced, it is logical to have 1e18 represent 100% and 1e16 represent 1% for Fixed-Point operations. This method of handling percents is also commonly seen in many Solidity libraries for Fixed-Point operations. - -### Emitting Event for Payment - -Since each NFT contract is independent, and while a marketplace contract can emit events when an item is sold, choosing to emit an event for payment is important. As the royalty and FR recipient may not be aware of/watching for a secondary sale of their NFT, they would never know that they received a payment except that their ETH wallet has been increased randomly. - -The recipient of the secondary sale will therefore be able to verify that the payment has been received by calling the parent contract of the NFT being sold, as implemented in [ERC-2981](./eip-2981.md). - -### Number of Generations of All Owners ( n ) vs Number of Generations of Only Profitable Owners - -It is the number of generations of all owners, not just those who are profitable, that determines the number of owners from which the subsequent owners' profits will be shared, see *Figure 3*. As part of the effort to discourage "ownership hoarding," Future Rewards distributions will not be made to the current owner/purchaser if all the owners lose money holding the NFT. Further information can be found under Security Considerations. - -![Figure 3: Losing owners](../assets/eip-5173/Losing_owners.jpeg) - -*Figure 3: Losing owners* - -### Single vs Multigenerations - -In a single generation reward, the new buyer/owner receives a share of the next single generation's realized profit only. In a multigenerational reward system, buyers will have future rewards years after their purchase. The NFT should have a long-term growth potential and a substantial dividend payout would be possible in this case. - -We propose that the marketplace operator can choose between a single generational distribution system and a multigenerational distribution system. - -### Direct FR Payout by the Seller vs Smart Contract-managed Payout - -FR payouts directly derived from the sale proceeds are immediate and final. As part of the fraud detection detailed later in the Security Considerations section, we selected a method in which the smart contract calculates all the FR amounts for each generation of previous owners, and handles payout according to other criteria set by the marketplace, such as reduced or delayed payments for wallet addresses with low scores, or a series of consecutive orders detected using a time-heuristic analysis. - -### Equal vs Linear Reward Distributions - -#### Equal FR Payout - -![Figure 4: Equal, linear reward distribution](../assets/eip-5173/Total_FR_Payout_Distribution-flat.png?raw=true) - -*Figure 4: Equal, linear reward distribution* - -FR distributions from the realization of profits by later owners are distributed equally to all eligible owners (*Figure 4*). The exponential reward curve, however, may be more desirable, as it gives a slightly larger share to the newest buyer. Additionally, this distribution gives the earliest generations the largest portions as their FR distributions near the end, so they receive higher rewards for their early involvement, but the distribution is not nearly as extreme as one based on arithmetic sequences (*Figure 5*). - -This system does not discriminate against any buyer because each buyer will go through the same distribution curve. - -#### Straight line arithmetic sequence FR payout - -![Figure 5: Arithmetic sequence distribution](../assets/eip-5173/Arithmetic_Sequence_FR_Payout_Distribution.png?raw=true) - -*Figure 5: Arithmetic sequence distribution* - -The profit is distributed according to the arithmetic sequence, which is 1, 2, 3, ... and so on. The first owner will receive 1 portion, the second owner will receive 2 portions, the third owner will receive 3 portions, etc. - -## Backwards Compatibility - -This proposal is fully compatible with current [ERC-721](./eip-721.md) standards and [ERC-2981](./eip-2981.md). It can also be easily adapted to work with [ERC-1155](./eip-1155.md). - -## Test Cases - -[This contract](../assets/eip-5173/Implementation/nFRImplementation.sol) contains the reference implementation for this proposal. - -[Here is a visualization of the test case](../assets/eip-5173/animate-1920x1080-1750-frames.gif?raw=true). - -As a result of implementing ERC-5173, a new project has been launched called untrading.org. - -## Reference Implementation - -This implementation uses OpenZeppelin contracts and the PRB Math library created by Paul R Berg for fixed-point arithmetic. It demonstrates the interface for the nFR standard, an nFR standard-compliant extension, and an [ERC-721](./eip-721.md) implementation using the extension. - -The code for the reference implementation is [here](../assets/eip-5173/Implementation/nFRImplementation.sol). - -### Distribution of NFT Royalties to Artists and Creators - -We agree that artists’ royalties should be uniform and on-chain. We support [ERC-2981](./eip-2981.md) NFT royalty Standard proposal. - -All platforms can support royalty rewards for the same NFT based on on-chain parameters and functions: - -- No profit, no profit sharing, no cost; -- The question of "who owned it" is often crucial to the provenance and value of a collectible; -- The previous owner should be re-compensated for their ownership; -- And the buyer/owner incentive in FR eliminates any motive to circumvent the royalty payout schemes; - -### Distribution of NFT Owners’ Future Rewards (FRs) - -#### Future Rewards calculation - -Any realized profits (P) when an NFT is sold are distributed among the buyers/owners. The previous owners will take a fixed portion of the profit (P), and this portion is called Future Rewards (FRs). The seller takes the rest of the profits. - -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) - -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 - -#### Converting into Code - -```solidity - -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) { - uint256 n = Math.min(m, w); - uint256[] memory FR = new uint256[](n); - - for (uint256 i = 1; i < n + 1; i++) { - uint256 pi = 0; - - 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 v4 = v3.mul(1e18 - int256(g)); - pi = uint256(v4 * v2 / v1); - } else { - pi = P.mul(r).div(n); - } - - FR[i - 1] = pi; - } - - return FR; -} - -``` - -The complete implementation code can be found [here](../assets/eip-5173/Implementation/nFRImplementation.sol). - -## Security Considerations - -### Payment Attacks - -As this ERC introduces royalty and realized profit rewards collection, distribution, and payouts to the ERC-721 standard, the attack vectors increase. As discussed by Andreas Freund regarding mitigations to phishing attacks, we recommend reentrancy protection for all payment functions to reduce the most significant attack vectors for payments and payouts. - -### Royalty Circumventing - -Many methods are being used to avoid paying royalties to creators under the current [ERC-721](./eip-721.md) standard. Through an under-the-table transaction, the new buyer's cost basis will be reduced to zero, increasing their FR liability to the full selling price. Everyone, either the buyer or seller, would pay a portion of the previous owner's net realized profits ( P x r ). Acting in his or her own interests, the buyer rejects any loyalty circumventing proposal. - -### FR Hoarding through Wash Sales - -Quantexa blog and beincrypto articles have reported widespread wash trading on all unregulated cryptocurrency trading platforms and NFT marketplaces. The use of wash trading by dishonest actors can lead to an unfair advantage, as well as inflated prices and money laundering. When a single entity becomes multiple generations of owners to accumulate more rewards in the future, the validity of the system is undermined. - -#### Wash trading by users - -Using a different wallet address, an attacker can "sell" the NFT to themselves at a loss. It is possible to repeat this process n times in order to maximize their share of the subsequent FR distributions (*Figure 6*). A wallet ranking score can partially alleviate this problem. It is evident that a brand new wallet is a red flag, and the marketplace may withhold FR distribution from it if it has a short transaction history (i.e. fewer than a certain number of transactions). - -We do not want a large portion of future rewards to go to a small number of wash traders. Making such practices less profitable is one way to discourage wash trading and award hoarding. It can be partially mitigated, for example, by implementing a wallet-score and holding period-based incentive system. The rewards for both parties are reduced if a new wallet is used or if a holding period is less than a certain period. - -![Figure 6: Same owner using different wallets](../assets/eip-5173/Same_owner_using_different_wallets.jpeg) - -*Figure 6: Same owner using different wallets* - -#### Wash trading by the marketplace operator - -However, the biggest offender appears to be the marketplace, which engages heavily in wash trading, or simply does not care about it, according to Decrypt. The authors have personally experienced this phenomenon. A senior executive of a top-5 cryptocurrency exchange boasted during a mid-night drinking session in 2018, that they had "brushed" (wash-traded) certain newly listed tokens, which they called "marketmaking." The exchange is still ranked among the top five crypto exchanges today. - -Many of these companies engage in wash trading on their own or collude with certain users, and royalties and FR payments are reimbursed under the table. It is crucial that all exchanges have robust features to prevent self-trading. Users should be able to observe watchers transparently. Marketplaces should provide their customers with free access to an on-chain transaction monitoring service like Chainalysis Reactor. - -### Long/Cyclical FR-Entitled Owner Generations - -In most cases, malicious actors will create excessively long or cyclical Future Rewards Owner Generations that will result in applications that attempt to distribute FR or shift generations running out of gas and not functioning. Therefore, clients are responsible for verifying that the contract with which they interact has an appropriate number of generations, so that looping over will not deplete the gas. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5173.md diff --git a/EIPS/eip-5185.md b/EIPS/eip-5185.md index 000784dbf2696a..2bfe4608f37f5a 100644 --- a/EIPS/eip-5185.md +++ b/EIPS/eip-5185.md @@ -1,232 +1 @@ ---- -eip: 5185 -title: NFT Updatable Metadata Extension -description: An interface extension for ERC-721/ERC-1155 controlled metadata updates -author: Christophe Le Bars (@clbrge) -discussions-to: https://ethereum-magicians.org/t/erc-721-erc-1155-updatable-metadata-extension/9077 -status: Stagnant -type: Standards Track -category: ERC -requires: 721, 1155 -created: 2022-06-27 ---- - -## Abstract - -This specification defines a standard way to allow controlled NFTs' metadata updates along predefined formulas. Updates of the original metadata are restricted and defined by a set of recipes and the sequence and results of these recipes are deterministic and fully verifiable with on-chain metadata updates event. The proposal depends on and extends the [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md). - -## Motivation - -Storing voluminous NFT metadata on-chain is often neither practical nor cost-efficient. - -Storing NFT metadata off-chain on distributed file systems like IPFS can answer some needs of verifiable correlation and permanence between an NFT tokenId and its metadata but updates come at the cost of being all or nothing (aka changing the `tokenURI`). Bespoke solutions can be easily developed for a specific NFT smart contract but a common specification is necessary for NFT marketplaces and third parties tools to understand and verify these metadata updates. - -This ERC allows the original JSON metadata to be modified step by step along a set of predefined JSON transformation formulas. Depending on NFT use-cases, the transformation formulas can be more or less restrictive. - -As examples, an NFT representing a house could only allow append-only updates to the list of successive owners, and a game using NFT characters could let some attributes change from time to time (e.g. health, experience, level, etc) while some other would be guaranteed to never change (e.g. physicals traits etc). - -This standard extension is compatible with NFTs bridged between Ethereum and L2 networks and allows efficient caching solutions. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -The **metadata updates extension** is OPTIONAL for [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) contracts. - -```solidity -/// @title ERC-721/ERC-1155 Updatable Metadata Extension -interface IERC5185UpdatableMetadata { - /// @notice A distinct Uniform Resource Identifier (URI) for a set of updates - /// @dev This event emits an URI (defined in RFC 3986) of a set of metadata updates. - /// The URI should point to a JSON file that conforms to the "NFT Metadata Updates JSON Schema" - /// Third-party platforms such as NFT marketplace can deterministically calculate the latest - /// metadata for all tokens using these events by applying them in sequence for each token. - event MetadataUpdates(string URI); -} -``` - -The original metadata SHOULD conform to the "ERC-5185 Updatable Metadata JSON Schema" which is a compatible extension of the "ERC-721 Metadata JSON Schema" defined in ERC-721. - -"ERC-5185 Updatable Metadata JSON Schema" : - -```json -{ - "title": "Asset Updatable Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "updatable": { - "type": "object", - "required": ["engine", "recipes"], - "properties": { - "engine": { - "type": "string", - "description": "Non ambiguous transformation method/language (with version) to process updates along recipes defined below" - }, - "schema": { - "type": "object", - "description": "if present, a JSON Schema that all sequential post transformation updated metadata need to conform. If a transformed JSON does not conform, the update should be considered voided." - }, - "recipes": { - "type": "object", - "description": "A catalog of all possibles recipes identified by their keys", - "patternProperties": { - ".*": { - "type": "object", - "description": "The key of this object is used to select which recipe to apply for each update", - "required": ["eval"], - "properties": { - "eval": { - "type": "string", - "description": "The evaluation formula to transform the last JSON metadata using the engine above (can take arguments)" - } - } - } - } - } - } - } - } -} -``` - -"NFT Metadata Updates JSON Schema" : - -```json -{ - "title": "Metadata Updates JSON Schema", - "type": "object", - "properties": { - "updates": { - "type": "array", - "description": "A list of updates to apply sequentially to calculate updated metadata", - "items": { "$ref": "#/$defs/update" }, - "$defs": { - "update": { - "type": "object", - "required": ["tokenId", "recipeKey"], - "properties": { - "tokenId": { - "type": "string", - "description": "The tokenId for which the update recipe should apply" - }, - "recipeKey": { - "type": "string", - "description": "recipeKey to use to get the JSON transformation expression in current metadata" - }, - "args": { - "type": "string", - "description": "arguments to pass to the JSON transformation" - } - } - } - } - } - } -} -``` - -### Engines - -Only one engine is currently defined in this extension proposal. - -If the engine in the original metadata is "jsonata@1.8.*", updated metadata is calculated starting from original metadata and applying each update sequentially (all updates which are present in all the URIs emitted by the event `MetadataUpdates` for which tokenId matches). - -For each step, the next metadata is obtained by the javascript calculation (or compatible jsonata implementation in other language) : - -```js -const nextMetadata = jsonata(evalString).evaluate(previousMetadata, args) -``` - -With `evalString` is found with `recipeKey` in the original metadata recipes list. - -If the key is not present in the original metadata list, `previousMetadata` is kept as the valid updated metadata. - -If the evaluation throws any errors, `previousMetadata` is kept as the valid updated metadata. - -If a validation Schema JSON has been defined and the result JSON `nextMetadata` does not conform, that update is not valid and `previousMetadata` is kept as the valid updated metadata. - -## Rationale - -There have been numerous interesting uses of [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) smart contracts that associate for each token essential and significant metadata. While some projects (e.g. EtherOrcs) have experimented successfully to manage these metadata on-chain, that experimental solution will always be limited by the cost and speed of generating and storing JSON on-chain. Symmetrically, while storing the JSON metadata at URI endpoint controlled by traditional servers permit limitless updates the the metadata for each NFT, it is somehow defeating in many uses cases, the whole purpose of using a trustless blockchain to manage NFT: indeed users may want or demand more permanence and immutability from the metadata associated with their NFT. - -Most use cases have chosen intermediate solutions like IPFS or arweave to provide some permanence or partial/full immutability of metadata. This is a good solution when an NFT represents a static asset whose characteristics are by nature permanent and immutable (like in the art world) but less so with other use cases like gaming or NFT representing a deed or title. Distinguishable assets in a game often should be allowed to evolve and change over time in a controlled way and titles need to record real life changes. - -The advantages of this standard is precisely to allow these types of controlled transformations over time of each NFT metadata by applying sequential transformations starting with the original metadata and using formulas themselves defined in the original metadata. - -The original metadata for a given NFT is always defined as the JSON pointed by the result of `tokenURI` for [EIP-721](./eip-721.md) and function `uri` for [EIP-1155](./eip-1155.md). - -The on-chain log trace of updates guarantee that anyone can recalculate and verify independently the current updated metadata starting from the original metadata. The fact that the calculation is deterministic allows easy caching of intermediate transformations and the efficient processing of new updates using these caches. - -The number of updates defined by each event is to be determined by the smart contract logic and use case, but it can easily scale to thousands or millions of updates per event. The function(s) that should emit `MetadataUpdates` and the frequency of these on-chain updates is left at the discretion of this standard implementation. - -The proposal is extremely gas efficient, since gas costs are only proportional to the frequency of committing changes. Many changes for many tokens can be batched in one transaction for the cost of only one `emit`. - -## Reference Implementation - -### Transformation engines - -We have been experimenting with this generic Metadata update proposal using the JSONata transformation language. - -Here is a very simple example of a NFT metadata for an imaginary 'little monster' game : - -```json -{ - "name": "Monster 1", - "description": "Little monsters you can play with.", - "attributes": [ - { "trait_type": "Level", "value": 0 }, - { "trait_type": "Stamina", "value": 100 } - ], - "updatable": { - "engine": "jsonata@1.8.*", - "recipes": { - "levelUp": { - "eval": "$ ~> | attributes[trait_type='Level'] | {'value': value + 1} |" - }, - "updateDescription": { - "eval": "$ ~> | $ | {'description': $newDescription} |" - } - } - } -} - ``` - -This updatable metadata can only be updated to increment by one the trait attribute "Level". - -An example JSON updates metadata would be : -```json -{ - "updates": [ - {"tokenId":"1","action":"levelUp"}, - {"tokenId":"2","action":"levelUp"}, - {"tokenId":"1","action":"updateDescription","args":{"newDescription":"Now I'm a big monster"}}, - {"tokenId":"1","action":"levelUp"}, - {"tokenId":"3","action":"levelUp"} - ] -} - ``` - -## Security Considerations - -A malicious recipe in the original metadata might be constructed as a DDoS vector for third parties marketplaces and tools that calculate NFT updated JSON metadata. They are encouraged to properly encapsulate software in charge of these calculations and put limits for the engine updates processing. - -Smart contracts should be careful and conscious of using this extension and still allow the metadata URI to be updated in some contexts (by not having the same URI returned by `tokenURI` or `uri` for a given tokenId over time). They need to take into account if previous changes could have been already broadcasted for that NFT by the contract, if these changes are compatible with the new "original metadata" and what semantic they decide to associate by combining these two kinds of "updates". - -## Backwards Compatibility - -The proposal is fully compatible with both [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md). Third-party applications that don't support this EIP will still be able to use the original metadata for each NFT. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5185.md diff --git a/EIPS/eip-5187.md b/EIPS/eip-5187.md index efe9a0738d61b4..11ca48cee9410f 100644 --- a/EIPS/eip-5187.md +++ b/EIPS/eip-5187.md @@ -1,148 +1 @@ ---- -eip: 5187 -title: Extend EIP-1155 with rentable usage rights -description: Separate ownership and usage rights of EIP-1155 to allow users to use NFTs for an allotted time and return them to owners after expiration. -author: DerivStudio (@DerivStudio) -discussions-to: https://ethereum-magicians.org/t/eip-draft-extending-erc1155-with-rentable-usage-rights/9553/4 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-04-17 -requires: 165, 1155 ---- - -## Abstract - -This standard is an extension of [EIP-1155](./eip-1155.md). It proposes to introduce separable, rentable, and transferable usage rights (in the form of NFT-IDs), enabling the property owner (the only NFT holder) to rent out the NFT to multiple users (ID holders) at the same time for different terms, and be withdrawn by smart contract upon expiration. - -The property owner always retains ownership and is able to transfer the NFT to others during the lease. - -The proposal also supports the sublease and renewal of the rental so that users can freely transfer the usage rights among each other and extend the lease term. Early return of NFTs can also be achieved by subletting the usage rights back to the property owners. - -## Motivation - -The well-accepted [EIP-721](./eip-721.md) and EIP-1155 standards focused on the ownership of unique assets, quite sensible in the time of NFTs being used primarily as arts and collectibles, or, you can say, as private property rights. -### First Step: "Expirable" NFTs -The advent of private ownership in the real world has promoted the vigorous development of the modern economy, and we believe that the usage right will be the first detachable right widely applied in the blockchain ecosystem. As NFTs are increasingly applied in rights, finance, games, and the Metaverse, the value of NFT is no longer simply the proof of ownership, but with limitless practice use scenarios. For example, artists may wish to rent out their artworks to media or audiences within specific periods, and game guilds may wish to rent out game items to new players to reduce their entry costs. - -The lease/rental of NFTs in the crypto space is not a new topic, but the implementation of leasing has long relied on over-collateralization, centralized custody, or pure trust, which significantly limits the boom of the leasing market. Therefore, a new type of "expirable" NFTs that can be automatically withdrawn upon expiration through smart contract is proposed, at the technical level, to eliminate those bottlenecks. Based on that, a new leasing model that is decentralized, collateral-free, and operated purely "on-chain" may disrupt the way people trade and use NFTs. Thus, this EIP proposal is here to create "expirable" NFTs compatible with EIP-1155. -### Then, Make Everything Transferable -The way we achieve leasing is to separate ownership and usage rights, and beyond that, we focus more on making them freely priced and traded after separation, which is impossible to happen in the traditional financial field. Imagine the below scenarios: i) as a landlord, you can sell your house in rental to others without affecting the tenancy, and your tenants will then pay rent to the new landlord; ii) as a tenant, you can sublet the house to others without the consent of the landlord, and even the one sublets can continue subletting the house until the lease term is close the last tenant can apply for a renewal of the lease. All of this can happen in the blockchain world, and that's the beauty of blockchain. Without permission, without trust, code is the law. - -Making ownership and usage rights transferable may further revolutionize the game rules in NFT's field, both in capital allocation and NFT development. Buying NFT ownership is more like investing in stocks, and the price is determined by market expectations of the project; renting the usage right is less speculative, so the price is easier to determine based on supply and demand. The ownership market and the usage-right market will function to meet the needs of target participants and achieve a balance that is conducive to the long-term and stable development of NFT projects. -Based on the above, we propose this EIP standard to complement the current EIP scopes and introduce those functions as new standards. - - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -pragma solidity ^0.8.0; - -/// Note: the ERC-165 identifier for this interface is 0x6938e358. - interface IRental /* is IERC165,IERC1155 */ { - /** - * @notice This emits when user rent NFT - * - `id` The id of the current token - * - `user` The address to rent the NFT usage rights - * - `amount` The amount of usage rights - * - `expire` The specified period of time to rent - **/ - event Rented(uint256 indexed id,address indexed user,uint256 amount,uint256 expire); - - /** - * MUST trigger on any successful call to `renew(address user,uint256 id)` - * - `id` The id of the current token - * - `user` The user of the NFT - * - `expire` The new specified period of time to rent - **/ - event Renew(uint256 indexed id,address indexed user,uint256 expire); - - /** - * MUST trigger on any successful call to `renew(address user,uint256 id,uint256 expire)` - * - `id` The id of the current token - * - `from` The current user of the NFT - * - `to` The new user - **/ - event Sublet(uint256 indexed id,address indexed from,address to); - - /** - * @notice This emits when the NFT owner takes back the usage rights from the tenant (the `user`) - * - id The id of the current token - * - user The address to rent the NFT's usage rights - * - amount Amount of usage rights - **/ - event TakeBack(uint256 indexed id, address indexed user, uint256 amount); - - /** - * @notice Function to rent out usage rights - * - from The address to approve - * - to The address to rent the NFT usage rights - * - id The id of the current token - * - amount The amount of usage rights - * - expire The specified period of time to rent - **/ - function safeRent(address from,address to,uint256 id,uint256 amount,uint256 expire) external; - - /** - * @notice Function to take back usage rights after the end of the tenancy - * - user The address to rent the NFT's usage rights - * - tokenId The id of the current token - **/ - function takeBack(address user,uint256 tokenId) external; - - /** - * @notice Return the NFT to the address of the NFT property right owner. - **/ - function propertyRightOf(uint256 id) external view returns (address); - - /** - * @notice Return the total supply amount of the current token - **/ - function totalSupply(uint256 id) external view returns (uint256); - - /** - * @notice Return expire The specified period of time to rent - **/ - function expireAt(uint256 id,address user) external view returns(uint256); - - /** - * extended rental period - * - `id` The id of the current token - * - `user` The user of the NFT - * - `expire` The new specified period of time to rent - **/ - function renew(address user,uint256 id,uint256 expire) external; - - /** - * transfer of usage right - * - `id` The id of the current token - * - `user` The user of the NFT - * - `expire` The new specified period of time to rent - **/ - function sublet(address to,uint256 id) external; -} - - -``` - -## Rationale - -Implementing the proposal to create rentable NFTs has two main benefits. - -One is that NFTs with multiple usage rights allow NFT property owners to perform the safeRent function and rent out usage rights to multiple users at the same time. For each usage right leased and expires, the property owner can perform the takeBack function to retrieve the usage right. - -Another benefit is that the transfer of usage rights can be quite flexible. The user can transfer the usage rights to other users by calling the Sublet function during the lease period, and can also extend the lease period of the usage rights by asking the property owner to perform the Renewal function. It is worth mentioning that if the user sublet the NFT to the property owner, it will realize the early return of NFT before the end of the lease period. - -## Backwards Compatibility - -As mentioned at the beginning, this is an extension of EIP-1155. Therefore, it is fully backward compatible with EIP-1155. - -## Security Considerations - -Needs discussion. - -## Copyright - -Disclaimer of copyright and related rights through [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5187.md diff --git a/EIPS/eip-5189.md b/EIPS/eip-5189.md index 786275fc930984..d9e0f370733e49 100644 --- a/EIPS/eip-5189.md +++ b/EIPS/eip-5189.md @@ -1,225 +1 @@ ---- -eip: 5189 -title: Account Abstraction via Endorsed Operations -description: An account abstraction proposal that avoids protocol changes while maintaining compatibility with existing smart contract wallets. -author: Agustín Aguilar (@agusx1211), Philippe Castonguay (@phabc) -discussions-to: https://ethereum-magicians.org/t/erc-account-abstraction-via-endorsed-operations/9799 -type: Standards Track -category: ERC -status: Stagnant -created: 2022-06-29 ---- - -## Abstract -This EIP proposes a form of account abstraction that ensures compatibility with existing smart contract wallets and provides flexibility for alternative designs while avoiding introducing changes to the consensus layer. Instead of defining a strict structure for meta-transactions, this proposal introduces the figure of `endorser` contracts. These smart contract instances are tasked with determining the quality of the submitted meta-transactions, thus safely helping bundlers determine if a meta-transaction should be kept in the mempool or not. Developers that intend to make their smart contract wallet compatible with this EIP must create and deploy an instance of an `endorser`; this instance must be seeded with a small amount of ETH to be burnt that incentivizes its good behavior. - -## Motivation -This account abstraction proposal aims to implement a generalized system for executing meta-transactions while maintaining the following goals: - -* **Achieve the primary goal of account abstraction:** allow users to use smart contract wallets containing arbitrary verification and execution logic instead of EOAs as their primary account. -* **Decentralization:** -* * Allow any bundler to participate in the process of including meta-transactions. -* * Work with all activity happening over a public mempool without having to concentrate transactions on centralized relayers. -* * Define structures that help maintain a healthy mempool without risking its participants from getting flooded with invalid or malicious payloads. -* * Avoid trust assumptions between bundlers, developers, and wallets. -* **Support existing smart contract wallet implementations:** Work with all the smart contract wallets already deployed and active while avoiding forcing each wallet instance to be manually upgraded. -* **Provide an unrestrictive framework:** Smart contract wallets are very different in design, limitations, and capabilities from one another; the proposal is designed to accommodate almost all possible variations. -* **No overhead:** Smart contract wallets already have a cost overhead compared to EOA alternatives, the proposal does not worsen the current situation. -* **Support other use cases:** -* * Privacy-preserving applications. -* * Atomic multi-operations (similar to EIP-3074). -* * Payment of transaction fees using ERC-20 tokens. -* * Scheduled execution of smart contracts without any user input. -* * Applications that require a generalistic relayer. - -## Specification -To avoid Ethereum consensus changes, we do not attempt to create new transaction types for account-abstracted transactions. Instead, meta-transactions are packed up in a struct called `Operation`, operations are structs composed by the following fields: - -| Field | Type | Description | -|-------------------|---------|--------------------------------------------------------------------------------------------------------| -| entrypoint | address | contract address that must be called with `callData` to execute the `operation`. | -| callData | bytes | data that must be passed to the `entrypoint` call to execute the `operation`. | -| gasLimit | uint64 | minimum gasLimit that must be passed when executing the `operation`. | -| endorser | address | address of the endorser contract that should be used to validate the `operation`. | -| endorserGasLimit | uint64 | amount of gas that should be passed to the endorser when validating the `operation`. | -| maxFeePerGas | uint256 | max amount of basefee that the `operation` execution is expected to pay, (similar to EIP-1559 `max_fee_per_gas`) | -| priorityFeePerGas | uint256 | fixed amount of fees that the `operation` execution is expected to pay to the bundler (similar to EIP-1559 `max_priority_fee_per_gas`). | - -These `Operation` objects can be sent to a dedicated operations mempool. A specialized class of actors called bundlers (either miners running special-purpose code, or just users that can relay transactions to miners) listen for operations on the mempool and execute these transactions. - -Transactions are executed by calling the `entrypoint` with the provided `callData`. The `entrypoint` can be any contract, but most commonly it will be the wallet contract itself, alternatively it can be an intermediary utility that deploys the wallet and then performs the transaction. - -#### Endorser functionality -Mempool participants need to be able to able to filter "good operations" (operations that pay the bundler the defined fee) from "bad operations" (operations that either miss payment or revert altogether). - -This categorization is facilitated by the `endorser`; the endorser must be a deployed smart contract that implements the following interface: - -```solidity -interface Endorser { - struct Dependency { - address addr; - bool balance; - bool code; - bool nonce; - bytes32[] slots; - } - - function isOperationReady( - address _entrypoint, - bytes calldata _data, - uint256 _gasLimit, - uint256 _maxFeePerGas, - uint256 _maxPriorityFeePerGas - ) external view returns ( - bool readiness, - Dependency[] memory dependencies - ); -} -``` - -It should also be registered in the `EndorserRegistry` with a minimum amount of burned ETH (Mempool operators are free to accept operations from endorsers without any burn, but they would increase their risk exposing themselves to denial of service attacks). - -When the `isOperationReady` method is called, the endorser must return this information: - -* **readiness:** when returning`true`, it means the transaction WILL be executed correctly and the bundler WILL be paid the offered gas fees (even if the underlying intent of the operation fails). -* **dependencies:** a comprehensive list of addresses and storage slots that must be monitored; any state change in these dependencies MUST trigger a re-evaluation of the operation's readiness. - -The information provided by the endorser helps the mempool operator maintain a pool of "good" meta-transactions that behave correctly; it DOES NOT guarantee that such transactions will be able to be executed correctly. Bundlers must always simulate the result of the execution before including a transaction in a block. - -#### Dependencies -| Field | Type | Description | -| -------- | -------- | -------- | -| addr | address | Contract address of the dependencies entry *(only one entry per address should be allowed)*. | -| balance | bool | `true` if the balance of `addr` should be considered a dependency of the `operation`. | -| code | bool | `true` if the code of `addr` should be considered a dependency of the `operation`. | -| nonce | bool | `true` if the nonce of `addr` should be considered a dependency of the `operation`. | -| slots | bytes32[] | List of all storage slots of `addr` that should be considered dependencies of `operation`. | - -The `endorser` does not need to include all accessed storage slots on the dependencies list, it only needs to include storage slots that after a change may also result in a change of readiness. - -> E.g. A wallet may pay fees using funds stored as WETH. During `isValidOperation()`, the endorser contract may call the `balanceOf` method of the `WETH` contract to determine if the wallet has enough `WETH` balance. Even though the ETH balance of the WETH contract and the code of the WETH contract are being accessed, the endorser only cares about the user's WETH balance for this operation and hence does not include these as dependencies. - -### Misbehavior detection -The `endorser` contracts may behave maliciously or erratically in the following ways: - -* (1) It may consider an operation `ready`, but when the operation is executed it transfers less than the agreed-upon fees to the bundler. -* (2) It may consider an operation `ready`, but when the operation is executed the top-level call fails. -* (3) It may change the status from `ready` to `not-ready` while none of the dependencies register any change. - -The bundler must always discard and re-evaluate the readiness status after a change on any of the dependencies of the `operation`, meaning that only operations considered `ready` are candidates for constructing the next block. - -If, when simulating the final inclusion of the operation, the bundler discovers that it does not result in correct payment (either because the transaction fails, or transferred amount is below the defined fee), then it should proceed to ban the `endorser` for one of the following reasons: - -1) The `endorser` returns `isOperationReady == true` even though the `operation` is not healthy to be included in a block. -2) The `operation` changed readiness status from `true` to `false` while all dependencies remained unchanged. - -After an `endorser` is banned, the mempool operator should drop all `operations` related to such endorser. - -> Notice: The mempool operator could call one last time `isOperationReady` to determine if the `endorser` should be banned because `(1)` or `(2)`, but this step is not strictly necessary since both scenarios lead to the `endoser` being banned. - -### Client behavior upon receiving an operation -When a client receives an `operation`, it must first run some basic sanity checks, namely that: - -* The `endorserGasLimit` is sufficiently low (<= `MAX_ENDORSER_GAS`). -* The endorser (i) is registered and has enough burn (>= `MIN_ENDORSER_BURN`), and (ii) it has not been internally flagged as banned. -* The `gasLimit` is at least the cost of a `CALL` with a non-zero value. -* The `maxFeePerGas` and `priorityPerGas` are above a configurable minimum value the client is willing to accept. -* If another operation exists in the mempool with the exact same dependency set AND the same endorser address, the `maxFeePerGas` and `priorityFeePerGas` of the newly received operation MUST be 12% higher than the one on the mempool to replace it. (Similar with how EOA with same nonce work) - -If the `operation` passes these checks, then the client MUST call `isOperationReady()` on the `endorser`. If the endorser considers the operation ready, then the client MUST add the operation to the mempool. Otherwise, the operation MUST discarded. - -The `endorser` result MUST be invalidated and its readiness be re-evaluated if any of the values of the provided dependencies change. If the operation readiness changes to `false`, the operation MUST be discarded. - -Before including the operation in a block, a last simulation MUST be performed, this time without calling the `endorser`, but by constructing the block and probing the result. All transactions in the block listed **before** the operation must be simulated and the endorser must be queried again there for readiness in-case some dependencies changed. - -If the operation fails during simulation, the endorser must be banned because (i) it returned a bad readiness state or (ii) it changed the operation readiness independently from the dependencies. - -Additional events that must invalidate the readiness are: - -* A transaction or operation modifies the same storage slots (as the dependencies) is queued before the given operation. - -#### Optional rules -Mempool clients could implement additional rules to further protect against maliciously constructed transactions. -* Limit the size of accepted dependencies to `MAX_OPERATION_DEPENDENCIES`, dropping operations that cross the boundary. -* Limit the number of times an operation may trigger a re-evaluation to `MAX_OPERATION_REEVALS`, dropping operations that cross the boundary. -* Limit the number of operations in the mempool that depend on the same dependency slots. - -If these rules are widely adopted, wallet developers should keep usage of dependencies to the lowest possible levels. - -#### Evaluation -To evaluate an `operation`, the client must call the `isOperationReady` method, with a `gasLimit` above or equal to `endorserGasLimit`. - -If the call fails, or the `endorser` returns `ready == false`, then the operation must be dropped from the mempool. - -If the call succeeds and returns `ready == true`, then the operation can be kept in the mempool and used when constructing the next block. The client must keep track of all fields returned as `dependencies`. If any of these register a change, then readiness should be reevaluated. - - -#### After operation inclusion -There is no limit in-place that defines that an operation can only be executed once. - -The bundler MUST NOT drop an `operation` after successfully including such operation in a block, the `operation` must remain in the mempool and a last `isOperationReady` call must be performed. - -If the `endorser` still returns `readiness == true` (after inclusion) then the operation SHOULD be treated as any other healthy operation, and thus it COULD be kept in the mempool. - -### Endorser registry -The endorser registry serves as a place to register the burn of each endorser, anyone can increase the burn of any endorser by calling the `addBurn` function. - -All burn is effectively locked forever; slashing can't be reliably proved on-chain without protocol alterations, so it remains a virtual event on which mempool operators will ignore the deposited ETH. - -#### Implementation -(EXAMPLE) - -```solidity -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.15; - - -contract EndorserRegistry { - event Burned( - address indexed _endorser, - address indexed _sender, - uint256 _new, - uint256 _total - ); - - mapping(address => uint256) public burn; - - function addBurn(address _endorser) external payable returns (uint256) { - uint256 total = burn[_endorser] + msg.value; - burn[_endorser] = total; - - emit Burned(_endorser, msg.sender, msg.value, total); - - return total; - } -} -``` - -## Rationale -The main challenge with a purely smart contract wallet-based account abstraction system is DoS safety: how can a bundler that includes an operation make sure that it will pay fees without executing the entire operation? - -Bundlers could execute the entire operation to determine if it is healthy or not, but this operation may be expensive and complex for the following reasons: - -* The bundler does not have a way to simulate the transaction with a reduced amount of gas; it has to use the whole `gasLimit`, exposing itself to a higher level of griefing. -* The bundler does not have a way to know if a change to the state will affect the operation or not, and thus it has to re-evaluate the operation after every single change. -* The bundler does not have a way to know if a change to the state will invalidate a large portion of the mempool. - -In this proposal, we add the `endorser` as a tool for the bundlers to validate arbitrary operations in a controlled manner, without the bundler having to know any of the inner workings of such operation. - -In effect, we move the responsibility from the wallet to the wallet developer; the developer must code, deploy and burn ETH for the `endorser`; this is a nearly ideal scenario because developers know how their wallet operations work, and thus they can build tools to evaluate these operations efficiently. - -Additionally, the specification is kept as simple as possible as enforcing a highly structured behavior and schema for smart contract wallet transactions may stagnate the adoption of more innovative types of wallets and the adoption of a shared standard among them. - -#### Differences with alternative proposals -1) This proposal does not require monitoring for forbidden opcodes or storage access boundaries. Wallets have complete freedom to use any EVM capabilities during validation and execution. -2) This proposal does not specify any replay protection logic since all existing smart contract wallets already have their own, and designs can vary among them. Nonces can be communicated to the bundler using a `dependency`. -3) This proposal does not specify a pre-deployment logic because it can be handled directly by the entrypoint. -4) This proposal does not require wallets to accept `execution` transactions from a trusted entrypoint contract, reducing overhead and allowing existing wallets to be compatible with the proposal. -5) This proposal does not distinguish between `execution` and `signature` payloads, this distinction remains implementation-specific. - - -## Backwards Compatibility -This EIP does not change he consensus layer, nor does impose changes on existing smart contract wallets, so there are no backwards compatibility issues. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5189.md diff --git a/EIPS/eip-5192.md b/EIPS/eip-5192.md index 30e5d147e25d5d..30ce91099879ab 100644 --- a/EIPS/eip-5192.md +++ b/EIPS/eip-5192.md @@ -1,71 +1 @@ ---- -eip: 5192 -title: Minimal Soulbound NFTs -description: Minimal interface for soulbinding EIP-721 NFTs -author: Tim Daubenschütz (@TimDaub), Anders (@0xanders) -discussions-to: https://ethereum-magicians.org/t/eip-5192-minimal-soulbound-nfts/9814 -status: Final -type: Standards Track -category: ERC -created: 2022-07-01 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [EIP-721](./eip-721.md). It proposes a minimal interface to make tokens soulbound using the feature detection functionality of [EIP-165](./eip-165.md). A soulbound token is a non-fungible token bound to a single account. - -## Motivation - -The Ethereum community has expressed a need for non-transferrable, non-fungible, and socially-priced tokens similar to World of Warcraft’s soulbound items. But the lack of a token standard leads many developers to simply throw errors upon a user's invocation of transfer functionalities. Over the long term, this will lead to fragmentation and less composability. - -In this document, we outline a minimal addition to [EIP-721](./eip-721.md) that allows wallet implementers to check for a token contract's permanent (non-)transferability using [EIP-165](./eip-165.md). - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Contract Interface - -A token with a `uint256 tokenId` may be bound to a receiving account with `function locked(...)` returning `true`. In this case, all [EIP-721](./eip-721.md) functions of the contract that transfer the token from one account to another must throw. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5192 { - /// @notice Emitted when the locking status is changed to locked. - /// @dev If a token is minted and the status is locked, this event should be emitted. - /// @param tokenId The identifier for a token. - event Locked(uint256 tokenId); - - /// @notice Emitted when the locking status is changed to unlocked. - /// @dev If a token is minted and the status is unlocked, this event should be emitted. - /// @param tokenId The identifier for a token. - event Unlocked(uint256 tokenId); - - /// @notice Returns the locking status of an Soulbound Token - /// @dev SBTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param tokenId The identifier for an SBT. - function locked(uint256 tokenId) external view returns (bool); -} -``` - -To aid recognition that an [EIP-721](./eip-721.md) token implements "soulbinding" via this EIP upon calling [EIP-721](./eip-721.md)'s `function supportsInterface(bytes4 interfaceID) external view returns (bool)` with `interfaceID=0xb45a3c0e`, a contract implementing this EIP must return `true`. - -## Rationale - -The above model is the simplest possible path towards a canonical interface for Soulbound tokens. It reflects upon the numerous Soulbound token implementations that simply revert upon transfers. - -## Backwards Compatibility - -This proposal is fully backward compatible with [EIP-721](./eip-721.md). - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5192.md diff --git a/EIPS/eip-5202.md b/EIPS/eip-5202.md index b504c4e281a885..1f0f0d5706afd3 100644 --- a/EIPS/eip-5202.md +++ b/EIPS/eip-5202.md @@ -1,152 +1 @@ ---- -eip: 5202 -title: Blueprint contract format -description: Define a bytecode container format for indexing and utilizing blueprint contracts -author: Charles Cooper (@charles-cooper), Edward Amor (@skellet0r) -discussions-to: https://ethereum-magicians.org/t/erc-5202-standard-factory-contract-format/9851 -status: Final -type: Standards Track -category: ERC -created: 2022-06-23 -requires: 170 ---- - -## Abstract - -Define a standard for "blueprint" contracts, or contracts which represent initcode that is stored on-chain. - -## Motivation - -To decrease deployer contract size, a useful pattern is to store initcode on chain as a "blueprint" contract, and then use `EXTCODECOPY` to copy the initcode into memory, followed by a call to `CREATE` or `CREATE2`. However, this comes with the following problems: - -- It is hard for external tools and indexers to detect if a contract is a "regular" runtime contract or a "blueprint" contract. Heuristically searching for patterns in bytecode to determine if it is initcode poses maintenance and correctness problems. -- Storing initcode byte-for-byte on-chain is a correctness and security problem. Since the EVM does not have a native way to distinguish between executable code and other types of code, unless the initcode explicitly implements ACL rules, *anybody* can call such a "blueprint" contract and execute the initcode directly as ordinary runtime code. This is particularly problematic if the initcode stored by the blueprint contract has side effects such as writing to storage or calling external contracts. If the initcode stored by the blueprint contract executes a `SELFDESTRUCT` opcode, the blueprint contract could even be removed, preventing the correct operation of downstream deployer contracts that rely on the blueprint existing. For this reason, it would be good to prefix blueprint contracts with a special preamble to prevent execution. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -A blueprint contract MUST use the preamble `0xFE71`. 6 bits are allocated to the version, and 2 bits to the length encoding. The first version begins at 0 (`0b000000`), and versions increment by 1. The value `0b11` for `` is reserved. In the case that the length bits are `0b11`, the third byte is considered a continuation byte (that is, the version requires multiple bytes to encode). The exact encoding of a multi-byte version is left to a future ERC. - -A blueprint contract MUST contain at least one byte of initcode. - -A blueprint contract MAY insert any bytes (data or code) between the version byte(s) and the initcode. If such variable length data is used, the preamble must be `0xFE71`. The `` represent a number between 0 and 2 (inclusive) describing how many bytes `` takes, and `` is the big-endian encoding of the number of bytes that `` takes. - -## Rationale - -- To save gas and storage space, the preamble should be as minimal as possible. - -- It is considered "bad" behavior to try to CALL a blueprint contract directly, therefore the preamble starts with `INVALID (0xfe)` to end execution with an exceptional halting condition (rather than a "gentler" opcode like `STOP (0x00)`). - -- To help distinguish a blueprint contract from other contracts that may start with `0xFE`, a "magic" byte is used. The value `0x71` was arbitrarily chosen by taking the last byte of the keccak256 hash of the bytestring "blueprint" (i.e.: `keccak256(b"blueprint")[-1]`). - -- An empty initcode is disallowed by the spec to prevent what might be a common mistake. - -- Users may want to include arbitrary data or code in their preamble. To allow indexers to ignore these bytes, a variable length encoding is proposed. To allow the length to be only zero or one bytes (in the presumably common case that `len(data bytes)` is smaller than 256), two bits of the third byte are reserved to specify how many bytes the encoded length takes. - -- In case we need an upgrade path, version bits are included. While we do not expect to exhaust the version bits, in case we do, a continuation sequence is reserved. Since only two bytes are required for `` (as [EIP-170](./eip-170.md) restricts contract length to 24KB), a `` value of 3 would never be required to describe ``. For that reason, the special `` value of `0b11` is reserved as a continuation sequence marker. - -- The length of the initcode itself is not included by default in the preamble because it takes space, and it can be trivially determined using `EXTCODESIZE`. - -- The Ethereum Object Format (EOF) could provide another way of specifying blueprint contracts, by adding another section kind (3 - initcode). However, it is not yet in the EVM, and we would like to be able to standardize blueprint contracts today, without relying on EVM changes. If, at some future point, section kind 3 becomes part of the EOF spec, and the EOF becomes part of the EVM, this ERC will be considered to be obsolesced since the EOF validation spec provides much stronger guarantees than this ERC. - - -## Backwards Compatibility - -No known issues - -## Test Cases - -- An example (and trivial!) blueprint contract with no data section, whose initcode is just the `STOP` instruction: - -``` -0xFE710000 -``` - -- An example blueprint contract whose initcode is the trivial `STOP` instruction and whose data section contains the byte `0xFF` repeated seven times: - -``` -0xFE710107FFFFFFFFFFFFFF00 -``` - -Here, 0xFE71 is the magic header, `0x01` means version 0 + 1 length bit, `0x07` encodes the length in bytes of the data section. These are followed by the data section, and then the initcode. For illustration, the above code with delimiters would be `0xFE71|01|07|FFFFFFFFFFFFFF|00`. - -- An example blueprint whose initcode is the trivial `STOP` instruction and whose data section contains the byte `0xFF` repeated 256 times: - -``` -0xFE71020100FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00 -``` - -Delimited, that would be `0xFE71|02|0100|FF...FF|00`. - -## Reference Implementation - -```python -from typing import Optional, Tuple - -def parse_blueprint_preamble(bytecode: bytes) -> Tuple[int, Optional[bytes], bytes]: - """ - Given bytecode as a sequence of bytes, parse the blueprint preamble and - deconstruct the bytecode into: - the ERC version, preamble data and initcode. - Raises an exception if the bytecode is not a valid blueprint contract - according to this ERC. - arguments: - bytecode: a `bytes` object representing the bytecode - returns: - (version, - None if is 0, otherwise the bytes of the data section, - the bytes of the initcode, - ) - """ - if bytecode[:2] != b"\xFE\x71": - raise Exception("Not a blueprint!") - - erc_version = (bytecode[2] & 0b11111100) >> 2 - - n_length_bytes = bytecode[2] & 0b11 - if n_length_bytes == 0b11: - raise Exception("Reserved bits are set") - - data_length = int.from_bytes(bytecode[3:3 + n_length_bytes], byteorder="big") - - if n_length_bytes == 0: - preamble_data = None - else: - data_start = 3 + n_length_bytes - preamble_data = bytecode[data_start:data_start + data_length] - - initcode = bytecode[3 + n_length_bytes + data_length:] - - if len(initcode) == 0: - raise Exception("Empty initcode!") - - return erc_version, preamble_data, initcode -``` - -The following reference function takes the desired initcode for a blueprint as a parameter, and returns EVM code which will deploy a corresponding blueprint contract (with no data section): - -```python -def blueprint_deployer_bytecode(initcode: bytes) -> bytes: - blueprint_preamble = b"\xFE\x71\x00" # ERC5202 preamble - blueprint_bytecode = blueprint_preamble + initcode - - # the length of the deployed code in bytes - len_bytes = len(blueprint_bytecode).to_bytes(2, "big") - - # copy to memory and `RETURN` it per EVM creation semantics - # PUSH2 RETURNDATASIZE DUP2 PUSH1 10 RETURNDATASIZE CODECOPY RETURN - deploy_bytecode = b"\x61" + len_bytes + b"\x3d\x81\x60\x0a\x3d\x39\xf3" - - return deploy_bytecode + blueprint_bytecode -``` - -## Security Considerations - -There could be contracts on-chain already which happen to start with the same prefix as proposed in this ERC. However, this is not considered a serious risk, because the way it is envisioned that indexers will use this is to verify source code by compiling it and prepending the preamble. - -As of 2022-07-08, no contracts deployed on the Ethereum mainnet have a bytecode starting with `0xFE71`. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5202.md diff --git a/EIPS/eip-5216.md b/EIPS/eip-5216.md index 1e16fd4ce598cb..a53324ea8cccae 100644 --- a/EIPS/eip-5216.md +++ b/EIPS/eip-5216.md @@ -1,104 +1 @@ ---- -eip: 5216 -title: EIP-1155 Approval By Amount Extension -description: Extension for EIP-1155 secure approvals -author: Iván Mañús (@ivanmmurciaua), Juan Carlos Cantó (@EscuelaCryptoES) -discussions-to: https://ethereum-magicians.org/t/eip-erc1155-approval-by-amount/9898 -status: Last Call -last-call-deadline: 2022-11-12 -type: Standards Track -category: ERC -created: 2022-07-11 -requires: 20, 165, 1155 ---- - -## Abstract - -This EIP defines standard functions for granular approval of [EIP-1155](./eip-1155.md) tokens by both `id` and `amount`. This EIP extends [EIP-1155](./eip-1155.md). - -## Motivation - -[EIP-1155](./eip-1155.md)'s popularity means that multi-token management transactions occur on a daily basis. Although it can be used as a more comprehensive alternative to [EIP-721](./eip-721.md), EIP-1155 is most commonly used as intended: creating multiple `id`s, each with multiple tokens. While many projects interface with these semi-fungible tokens, by far the most common interactions are with NFT marketplaces. - -Due to the nature of the blockchain, programming errors or malicious operators can cause permanent loss of funds. It is therefore essential that transactions are as trustless as possible. EIP-1155 uses the `setApprovalForAll` function, which approves ALL tokens with a specific `id`. This system has obvious minimum required trust flaws. This EIP combines ideas from [EIP-20](./eip-20.md) and [EIP-721](./eip-721.md) in order to create a trust mechanism where an owner can allow a third party, such as a marketplace, to approve a limited (instead of unlimited) number of tokens of one `id`. - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Contracts using this EIP MUST implement the `IERC1155ApprovalByAmount` interface. - -### Interface implementation - -```solidity -/** - * @title ERC-1155 Approval By Amount Extension - * Note: the ERC-165 identifier for this interface is 0x1be07d74 - */ -interface IERC1155ApprovalByAmount is IERC1155 { - - /** - * @notice Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to - * `id` and with an amount: `amount`. - */ - event ApprovalByAmount(address indexed account, address indexed operator, uint256 id, uint256 amount); - - /** - * @notice Grants permission to `operator` to transfer the caller's tokens, according to `id`, and an amount: `amount`. - * Emits an {ApprovalByAmount} event. - * - * Requirements: - * - `operator` cannot be the caller. - */ - function approve(address operator, uint256 id, uint256 amount) external; - - /** - * @notice Returns the amount allocated to `operator` approved to transfer `account`'s tokens, according to `id`. - */ - function allowance(address account, address operator, uint256 id) external view returns (uint256); -} -``` - -The `approve(address operator, uint256 id, uint256 amount)` function MUST be either `public` or `external`. - -The `allowance(address account, address operator, uint256 id)` function MUST be either `public` or `external` and MUST be `view`. - -The `safeTrasferFrom` function (as defined by EIP-1155) MUST: - -- Not revert if the user has approved `msg.sender` with a sufficient `amount` -- Subtract the transferred amount of tokens from the approved amount if `msg.sender` is not approved with `setApprovalForAll` - -In addition, the `safeBatchTransferFrom` MUST: - -- Add an extra condition that checks if the `allowance` of all `ids` have the approved `amounts` (See `_checkApprovalForBatch` function reference implementation) - -The `ApprovalByAmount` event MUST be emitted when a certain number of tokens are approved. - -The `supportsInterface` method MUST return `true` when called with `0x1be07d74`. - -## Rationale - -The name "EIP-1155 Approval By Amount Extension" was chosen because it is a succinct description of this EIP. Users can approve their tokens by `id` and `amount` to `operator`s. - -By having a way to approve and revoke in a manner similar to [EIP-20](./eip-20.md), the trust level can be more directly managed by users: - -- Using the `approve` function, users can approve an operator to spend an `amount` of tokens for each `id`. -- Using the `allowance` function, users can see the approval that an operator has for each `id`. - -The [EIP-20](./eip-20.md) name patterns were used due to similarities with [EIP-20](./eip-20.md) approvals. - -## Backwards Compatibility - -This standard is compatible with [EIP-1155](./eip-1155.md). - -## Reference Implementation - -The reference implementation can be found [here](../assets/eip-5216/ERC1155ApprovalByAmount.sol). - -## Security Considerations - -Users of this EIP must thoroughly consider the amount of tokens they give permission to `operators`, and should revoke unused authorizations. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5216.md diff --git a/EIPS/eip-5218.md b/EIPS/eip-5218.md index 78dd05c5bd3046..0869a359b5d479 100644 --- a/EIPS/eip-5218.md +++ b/EIPS/eip-5218.md @@ -1,239 +1 @@ ---- -eip: 5218 -title: NFT Rights Management -description: An interface for creating copyright licenses that transfer with an NFT. -author: James Grimmelmann (@grimmelm), Yan Ji (@iseriohn), Tyler Kell (@relyt29) -discussions-to: https://ethereum-magicians.org/t/eip-5218-nft-rights-management/9911 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-07-11 -requires: 721 ---- - - - -## Abstract - -The following standard defines an API for managing NFT licenses. This standard provides basic functionality to create, transfer, and revoke licenses, and to determine the current licensing state of an NFT. The standard does not define the legal details of the license. Instead, it provides a structured framework for recording licensing details. - -We consider use cases of NFT creators who wish to give the NFT holder a copyright license to use a work associated with the NFT. The holder of an active license can issue sublicenses to others to carry out the rights granted under the license. The license can be transferred with the NFT, so do all the sublicenses. The license can optionally be revoked under conditions specified by the creator. - - -## Motivation - -The [ERC-721](./eip-721.md) standard defines an API to track and transfer ownership of an NFT. When an NFT is to represent some off-chain asset, however, we would need some legally effective mechanism to *tether* the on-chain asset (NFT) to the off-chain property. One important case of off-chain property is creative work such as an image or music file. Recently, most NFT projects involving creative works have used licenses to clarify what legal rights are granted to the NFT owner. But these licenses are almost always off-chain and the NFTs themselves do not indicate what licenses apply to them, leading to uncertainty about rights to use the work associated with the NFT. It is not a trivial task to avoid all the copyright vulnerabilities in NFTs, nor have existing EIPs addressed rights management of NFTs beyond the simple cases of direct ownership (see [ERC-721](./eip-721.md)) or rental (see [ERC-4907](./eip-4907.md)). - -This EIP attempts to provide a standard to facilitate rights management of NFTs in the world of Web3. In particular, [ERC-5218](./eip-5218.md) smart contracts allow all licenses to an NFT, including the *root license* issued to the NFT owner and *sublicenses* granted by a license holder, to be recorded and easily tracked with on-chain data. These licenses can consist of human-readable legal code, machine-readable summaries such as those written in CC REL, or both. An ERC-5218 smart contract points to a license by recording a URI, providing a reliable reference for users to learn what legal rights they are granted and for NFT creators and auditors to detect unauthorized infringing uses. - - - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -**Every ERC-5218 compliant contract *must* implement the `IERC5218` interface**: - -```solidity -pragma solidity ^0.8.0; - -/// @title ERC-5218: NFT Rights Management -interface IERC5218 is IERC721 { - - /// @dev This emits when a new license is created by any mechanism. - event CreateLicense(uint256 _licenseId, uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string _uri, address _revoker); - - /// @dev This emits when a license is revoked. Note that under some - /// license terms, the sublicenses may be `implicitly` revoked following the - /// revocation of some ancestral license. In that case, your smart contract - /// may only emit this event once for the ancestral license, and the revocation - /// of all its sublicenses can be implied without consuming additional gas. - event RevokeLicense(uint256 _licenseId); - - /// @dev This emits when the a license is transferred to a new holder. The - /// root license of an NFT should be transferred with the NFT in an ERC721 - /// `transfer` function call. - event TransferLicense(uint256 _licenseId, address _licenseHolder); - - /// @notice Check if a license is active. - /// @dev A non-existing or revoked license is inactive and this function must - /// return `false` upon it. Under some license terms, a license may become - /// inactive because some ancestral license has been revoked. In that case, - /// this function should return `false`. - /// @param _licenseId The identifier for the queried license - /// @return Whether the queried license is active - function isLicenseActive(uint256 _licenseId) external view returns (bool); - - /// @notice Retrieve the token identifier a license was issued upon. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The token identifier the queried license was issued upon - function getLicenseTokenId(uint256 _licenseId) external view returns (uint256); - - /// @notice Retrieve the parent license identifier of a license. - /// @dev Throws unless the license is active. If a license doesn't have a - /// parent license, return a special identifier not referring to any license - /// (such as 0). - /// @param _licenseId The identifier for the queried license - /// @return The parent license identifier of the queried license - function getParentLicenseId(uint256 _licenseId) external view returns (uint256); - - /// @notice Retrieve the holder of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The holder address of the queried license - function getLicenseHolder(uint256 _licenseId) external view returns (address); - - /// @notice Retrieve the URI of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The URI of the queried license - function getLicenseURI(uint256 _licenseId) external view returns (string memory); - - /// @notice Retrieve the revoker address of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The revoker address of the queried license - function getLicenseRevoker(uint256 _licenseId) external view returns (address); - - /// @notice Retrieve the root license identifier of an NFT. - /// @dev Throws unless the queried NFT exists. If the NFT doesn't have a root - /// license tethered to it, return a special identifier not referring to any - /// license (such as 0). - /// @param _tokenId The identifier for the queried NFT - /// @return The root license identifier of the queried NFT - function getLicenseIdByTokenId(uint256 _tokenId) external view returns (uint256); - - /// @notice Create a new license. - /// @dev Throws unless the NFT `_tokenId` exists. Throws unless the parent - /// license `_parentLicenseId` is active, or `_parentLicenseId` is a special - /// identifier not referring to any license (such as 0) and the NFT - /// `_tokenId` doesn't have a root license tethered to it. Throws unless the - /// message sender is eligible to create the license, i.e., either the - /// license to be created is a root license and `msg.sender` is the NFT owner, - /// or the license to be created is a sublicense and `msg.sender` is the holder - /// of the parent license. - /// @param _tokenId The identifier for the NFT the license is issued upon - /// @param _parentLicenseId The identifier for the parent license - /// @param _licenseHolder The address of the license holder - /// @param _uri The URI of the license terms - /// @param _revoker The revoker address - /// @return The identifier of the created license - function createLicense(uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string memory _uri, address _revoker) external returns (uint256); - - /// @notice Revoke a license. - /// @dev Throws unless the license is active and the message sender is the - /// eligible revoker. This function should be used for revoking both root - /// licenses and sublicenses. Note that if a root license is revoked, the - /// NFT should be transferred back to its creator. - /// @param _licenseId The identifier for the queried license - function revokeLicense(uint256 _licenseId) external; - - /// @notice Transfer a sublicense. - /// @dev Throws unless the sublicense is active and `msg.sender` is the license - /// holder. Note that the root license of an NFT should be tethered to and - /// transferred with the NFT. Whenever an NFT is transferred by calling the - /// ERC721 `transfer` function, the holder of the root license should be - /// changed to the new NFT owner. - /// @param _licenseId The identifier for the queried license - /// @param _licenseHolder The new license holder - function transferSublicense(uint256 _licenseId, address _licenseHolder) external; -} -``` - -Licenses to an NFT in general have a tree structure as below: - -![The license tree](../assets/eip-5218/license-tree.png) - -There is one root license to the NFT itself, granting the NFT owner some rights to the linked work. The NFT owner (i.e., the root license holder) may create sublicenses, holders of which may also create sublicenses recursively. - -The full log of license creation, transfer, and revocation *must* be traceable via event logs. Therefore, all license creations and transfers *must* emit a corresponding log event. Revocation may differ a bit. An implementation of this EIP may emit a `Revoke` event only when a license is revoked in a function call, or for every revoked license, both are sufficient to trace the status of all licenses. The former costs less gas if revoking a license automatically revokes all sublicenses under it, while the latter is efficient in terms of interrogation of a license status. Implementers should make the tradeoffs depending on their license terms. - -The `revoker` of a license may be the licensor, the license holder, or a smart contract address which calls the `revokeLicense` function when some conditions are met. Implementers should be careful with the authorization, and may make the `revoker` smart contract forward compatible with transfers by not hardcoding the addresses of `licensor` or `licenseHolder`. - -The license `URI` may point to a JSON file that conforms to the "ERC-5218 Metadata JSON Schema" as below, which adopts the "three-layer" design of the Creative Commons Licenses: - -```json -{ - "title": "License Metadata", - "type": "object", - "properties": { - "legal-code": { - "type": "string", - "description": "The legal code of the license." - }, - "human-readable": { - "type": "string", - "description": "The human readable license deed." - }, - "machine-readable": { - "type": "string", - "description": "The machine readable code of the license that can be recognized by software, such as CC REL." - } - } -} -``` - -Note that this EIP doesn't include a function to update license URI so the license terms should be persistent by default. It is recommended to store the license metadata on a decentralized storage service such as IPFS or adopt the IPFS-style URI which encodes the hash of the metadata for integrity verification. On the other hand, license updatability, if necessary in certain scenarios, can be realized by revoking the original license and creating a new license, or adding a updating function, the eligibile caller of which must be carefully specified in the license and securely implemented in the smart contract. - -The `supportsInterface` method MUST return `true` when called with `0xac7b5ca9`. - -## Rationale - -This EIP aims to allow tracing all licenses to an NFT to facilitate right management. The ERC-721 standard only logs the property but not the legal rights tethered to NFTs. Even when logging the license via the optional ERC-721 Metadata extension, sublicenses are not traceable, which doesn't comply with the transparency goals of Web3. Some implementations attempt to get around this limitation by minting NFTs to represent a particular license, such as the BAYC #6068 Royalty-Free Usage License. This is not an ideal solution because the linking between different licenses to an NFT is ambiguous. An auditor has to investigate all NFTs in the blockchain and inspect the metadata which hasn't been standardized in terms of sublicense relationship. To avoid these problems, this EIP logs all licenses to an NFT in a tree data structure, which is compatible with ERC-721 and allows efficient traceability. - -This EIP attempts to tether NFTs with copyright licenses to the creative work by default and is not subject to the high legal threshold for copyright ownership transfers which require an explicit signature from the copyright owner. To transfer and track copyright ownership, one may possibly integrate ERC-5218 and [ERC-5289](./eip-5289.md) after careful scrutinizing and implement a smart contract that atomically (1) signs the legal contract via ERC-5289, and (2) transfers the NFT together with the copyright ownership via ERC-5218. Either both take place or both revert. - -## Backwards Compatibility - -This standard is compatible with the current ERC-721 standards: a contract can inherit from both ERC-721 and ERC-5218 at the same time. - -## Test Cases - -Test cases are available [here](../assets/eip-5218/contracts/test/Contract.t.sol). - -## Reference Implementation - -A reference implementation maintains the following data structures: - -```solidity - struct License { - bool active; // whether the license is active - uint256 tokenId; // the identifier of the NFT the license is tethered to - uint256 parentLicenseId; // the identifier of the parent license - address licenseHolder; // the license holder - string uri; // the license URI - address revoker; // the license revoker - } - mapping(uint256 => License) private _licenses; // maps from a license identifier to a license object - mapping(uint256 => uint256) private _licenseIds; // maps from an NFT to its root license identifier -``` - -Each NFT has a license tree and starting from each license, one can trace back to the root license via `parentLicenseId` along the path. - -In the reference implementation, once a license is revoked, all sublicenses under it are revoked. This is realized in a *lazy* manner for lower gas cost, i.e., assign `active=false` only for licenses that are explicitly revoked in a `revokeLicense` function call. Therefore, `isLicenseActive` returns `true` only if all its ancestral licenses haven't been revoked. - -For non-root licenses, the creation, transfer and revocation are straightforward: - -1. Only the holder of an active license can create sublicenses. -2. Only the holder of an active license can transfer it to a different license holder. -3. Only the revoker of an active license can revoke it. - -The root license must be compatible with `ERC-721`: - -1. When an NFT is minted, a license is granted to the NFT owner. -2. When an NFT is transferred, the license holder is changed to the new owner of the NFT. -3. When a root license is revoked, the NFT is returned to the NFT creator, and the NFT creator may later transfer it to a new owner with a new license. - -The complete implementation can be found [here](../assets/eip-5218/contracts/src/RightsManagement.sol). - -In addition, the [Token-Bound NFT License](../assets/eip-5218/ic3license/ic3license.pdf) is specifically designed to work with this interface and provides a reference to the language of NFT licenses. - -## Security Considerations - -Implementors of the `IERC5218` standard must consider thoroughly the permissions they give to `licenseHolder` and `revoker`. If the license is ever to be transferred to a different license holder, the `revoker` smart contract should not hardcode the `licenseHolder` address to avoid undesirable scenarios. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5218.md diff --git a/EIPS/eip-5219.md b/EIPS/eip-5219.md index 57567e59d15876..d74331ffa8cd57 100644 --- a/EIPS/eip-5219.md +++ b/EIPS/eip-5219.md @@ -1,79 +1 @@ ---- -eip: 5219 -title: Contract Resource Requests -description: Allows the requesting of resources from contracts -author: Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/pr-5219-discussion-contract-rest/9907 -status: Final -type: Standards Track -category: ERC -created: 2022-07-10 ---- - -## Abstract - -This EIP standardizes an interface to make resource requests to smart contracts and to receive HTTP-like responses. - -## Motivation - -Ethereum is the most-established blockchain for building decentralized applications (referred to as `DApp`s). Due to this, the Ethereum DApp ecosystem is very diverse. However, one issue that plagues DApps is the fact that they are not fully decentralized. Specifically, to interface a "decentralized" application, one first needs to access a *centralized* website containing the DApp's front-end code, presenting a few issues. The following are some risks associated with using centralized websites to interface with decentralized applications: - -- Trust Minimization: An unnecessarily large number of entities need to be trusted -- Censorship: A centralized website is not resistant to being censored -- Permanence: The interface may not have a mechanism that permits it to be permanently stored -- Interoperability: Smart Contracts cannot directly interact with DApp interfaces - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Name Resolution - -EIPs that propose a name resolution mechanism MAY reference this EIP and MAY recommend that clients support their mechanism. Clients MAY also support regular DNS, as defined in RFC 1034 and RFC 1035. - -### Separation of Concerns - -It is RECOMMENDED to separate the application logic from the front-end logic (the contract implementing the interface defined in [Contract Interface](#contract-interface)). - -### Contract Interface - -DApp contracts MUST implement the interface defined in the following file: [Contract Interface](../assets/eip-5219/IDecentralizedApp.sol). - -### Note to Implementers - -To save gas costs, it is recommended to use the `message/external-body` MIME-type, which allows you to point to data that the smart contract might not have access to. For example, the following response would tell a client to fetch the data off of IPFS: - -```yaml -statusCode: 200 -body: THIS IS NOT REALLY THE BODY! -headers: - - key: Content-type - value: message/external-body; access-type=URL; URL="ipfs://11148a173fd3e32c0fa78b90fe42d305f202244e2739" -``` - -## Rationale - -The `request` method was chosen to be readonly because all data should be sent to the contract from the parsed DApp. Here are some reasons why: - -- Submitting a transaction to send a request would be costly and would require waiting for the transaction to be mined, resulting in bad user experience. -- Complicated front-end logic should not be stored in the smart contract, as it would be costly to deploy and would be better run on the end-user's machine. -- Separation of Concerns: the front-end contract shouldn't have to worry about interacting with the back-end smart contract. -- Other EIPs can be used to request state changing operations in conjunction with a `307 Temporary Redirect` status code. - -Instead of mimicking a full HTTP request, a highly slimmed version was chosen for the following reasons: - -- The only particularly relevant HTTP method is `GET` -- Query parameters can be encoded in the resource. -- Request headers are, for the most part, unnecessary for `GET` requests. - -## Backwards Compatibility - -This EIP is backwards compatible with all standards listed in the [Name Resolution](#name-resolution) section. - -## Security Considerations - -The normal security considerations of accessing normal URLs apply here, such as potential privacy leakage by following `3XX` redirects. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5219.md diff --git a/EIPS/eip-5247.md b/EIPS/eip-5247.md index e77d5179a2dc41..d998838ac8ff23 100644 --- a/EIPS/eip-5247.md +++ b/EIPS/eip-5247.md @@ -1,147 +1 @@ ---- -eip: 5247 -title: Smart Contract Executable Proposal Interface -description: An interface to create and execute proposals. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-5247-executable-proposal-standard/9938 -status: Review -type: Standards Track -category: ERC -created: 2022-07-13 ---- - -## Abstract - -This EIP presents an interface for "smart contract executable proposals": proposals that are submitted to, recorded on, and possibly executed on-chain. Such proposals include a series of information about -function calls including the target contract address, ether value to be transmitted, gas limits and calldatas. - -## Motivation - -It is oftentimes necessary to separate the code that is to be executed from the actual execution of the code. - -A typical use case for this EIP is in a Decentralized Autonomous Organization (DAO). A proposer will create a smart proposal and advocate for it. Members will then choose whether or not to endorse the proposal and vote accordingly (see `ERC-1202`). Finallym when consensus has been formed, the proposal is executed. - -A second typical use-case is that one could have someone who they trust, such as a delegator, trustee, or an attorney-in-fact, or any bilateral collaboration format, where a smart proposal will be first composed, discussed, approved in some way, and then put into execution. - -A third use-case is that a person could make an "offer" to a second person, potentially with conditions. The smart proposal can be presented as an offer and the second person can execute it if they choose to accept this proposal. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -interface IERC5247 { - event ProposalCreated( - address indexed proposer, - uint256 indexed proposalId, - address[] targets, - uint256[] values, - uint256[] gasLimits, - bytes[] calldatas, - bytes extraParams - ); - - event ProposalExecuted( - address indexed executor, - uint256 indexed proposalId, - bytes extraParams - ); - - function createProposal( - uint256 proposalId, - address[] calldata targets, - uint256[] calldata values, - uint256[] calldata gasLimits, - bytes[] calldata calldatas, - bytes calldata extraParams - ) external returns (uint256 registeredProposalId); - - function executeProposal(uint256 proposalId, bytes calldata extraParams) external; -} -``` - -## Rationale - -* Originally, this interface was part of part of `ERC-1202`. However, the proposal itself can potentially have many use cases outside of voting. It is possible that voting may not need to be upon a proposal in any particular format. Hence, we decide to *decouple the voting interface and proposal interface*. -* Arrays were used for `target`s, `value`s, `calldata`s instead of single variables, allowing a proposal to carry arbitrarily long multiple functional calls. -* `registeredProposalId` is returned in `createProposal` so the standard can support implementation to decide their own format of proposal id. - -## Test Cases - -A simple test case can be found as - -```ts - it("Should work for a simple case", async function () { - const { contract, erc721, owner } = await loadFixture(deployFixture); - const callData1 = erc721.interface.encodeFunctionData("mint", [owner.address, 1]); - const callData2 = erc721.interface.encodeFunctionData("mint", [owner.address, 2]); - await contract.connect(owner) - .createProposal( - 0, - [erc721.address, erc721.address], - [0,0], - [0,0], - [callData1, callData2], - []); - expect(await erc721.balanceOf(owner.address)).to.equal(0); - await contract.connect(owner).executeProposal(0, []); - expect(await erc721.balanceOf(owner.address)).to.equal(2); - }); -``` - -See [testProposalRegistry.ts](../assets/eip-5247/testProposalRegistry.ts) for the whole testset. - -## Reference Implementation - -A simple reference implementation can be found. - -```solidity - function createProposal( - uint256 proposalId, - address[] calldata targets, - uint256[] calldata values, - uint256[] calldata gasLimits, - bytes[] calldata calldatas, - bytes calldata extraParams - ) external returns (uint256 registeredProposalId) { - require(targets.length == values.length, "GeneralForwarder: targets and values length mismatch"); - require(targets.length == gasLimits.length, "GeneralForwarder: targets and gasLimits length mismatch"); - require(targets.length == calldatas.length, "GeneralForwarder: targets and calldatas length mismatch"); - registeredProposalId = proposalCount; - proposalCount++; - - proposals[registeredProposalId] = Proposal({ - by: msg.sender, - proposalId: proposalId, - targets: targets, - values: values, - calldatas: calldatas, - gasLimits: gasLimits - }); - emit ProposalCreated(msg.sender, proposalId, targets, values, gasLimits, calldatas, extraParams); - return registeredProposalId; - } - function executeProposal(uint256 proposalId, bytes calldata extraParams) external { - Proposal storage proposal = proposals[proposalId]; - address[] memory targets = proposal.targets; - string memory errorMessage = "Governor: call reverted without message"; - for (uint256 i = 0; i < targets.length; ++i) { - (bool success, bytes memory returndata) = proposal.targets[i].call{value: proposal.values[i]}(proposal.calldatas[i]); - Address.verifyCallResult(success, returndata, errorMessage); - } - emit ProposalExecuted(msg.sender, proposalId, extraParams); - } -``` - -See [ProposalRegistry.sol](../assets/eip-5247/ProposalRegistry.sol) for more information. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5247.md diff --git a/EIPS/eip-5252.md b/EIPS/eip-5252.md index a948f908c35d1a..05e46805a6617e 100644 --- a/EIPS/eip-5252.md +++ b/EIPS/eip-5252.md @@ -1,247 +1 @@ ---- -eip: 5252 -title: Account-bound Finance -description: An ERC-5114 extension that aids in preventing arbitrary loss of funds -author: Hyungsuk Kang (@hskang9), Viktor Pernjek (@smuxx) -discussions-to: https://ethereum-magicians.org/t/pr-5252-discussion-account-bound-finance/10027 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-06-29 -requires: 20, 721, 1155, 5114 ---- - -## Abstract - -This EIP proposes a form of smart contract design pattern and a new type of account abstraction on how one's finance should be managed, ensuring transparency of managing investments and protection with self-sovereignty even from its financial operators. This EIP enables greater self-sovereignty of one's assets using a personal finance contract for each individual. The seperation between an investor's funds and the operation fee is clearly specified in the personal smart contract, so investors can ensure safety from arbitrary loss of funds by the operating team's control. - -This EIP extends [ERC-5114](./eip-5114.md) to further enable transferring fund to other accounts for mobility between managing multiple wallets. - -## Motivation - -Decentralized finance (DeFi) faces a trust issue. Smart contracts are often proxies, with the actual logic of the contract hidden away in a separate logic contract. Many projects include a multi-signature "wallet" with unnecessarily-powerful permissions. And it is not possible to independently verify that stablecoins have enough real-world assets to continue maintaining their peg, creating a large loss of funds (such as happened in the official bankruptcy announcement of Celsius and UST de-pegging and anchor protocol failure). One should not trust exchanges or other third parties with one's own investments with the operators' clout in Web3.0. - -Smart contracts are best implemented as a promise between two parties written in code, but current DeFi contracts are often formed using less than 7 smart contracts to manage their whole investors' funds, and often have a trusted key that has full control. This is evidently an issue, as investors have to trust contract operators with their funds, meaning that users do not actually own their funds. - -The pattern with personal finance contract also offers more transparency than storing mixed fund financial data in the operating team's contract. With a personal finance contract, an account's activity is easier to track than one global smart contract's activity. The pattern introduces a Non-Fungiible Account-Bound Token (ABT) to store credentials from the personal finance contract. - -### Offchain-identity vs Soul-bound token on credentials - -This EIP provides a better alternative to off-chain identity solutions which take over the whole system because their backends eventually rely on the trust of the operator, not cryptographic proof (e.g. Proof-of-work, Proof-of-stake, etc). Off-chain identity as credentials are in direct opposition to the whole premise of crypto. Soulbound tokens are a better, verifiable credential, and data stored off-chain is only to store token metadata. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -The specification consists of two patterns for **Interaction** and **Governance**. - -### Interaction - -#### Interfaces - -The interaction pattern consists of 4 components for interaction; manager, factory, finance, account-bound token, and extension. - -Interaction contract pattern is defined with these contracts: - -- A soul-bound or account bound token contract to give access to interact with a financial contract with credentials -- A manager contract that interacts first contact with an investor -- A factory contract that creates a financial contract for each user -- A finance contract that can interact with the investor - -#### Requirements - -A soul-bound or account bound token contract is defined with these properties: - -1. It SHALL be non-fungible and MUST satisfy [ERC-721](./eip-721.md). -2. Credentials SHOULD be represented with its metadata with `tokenURI()` function. -3. It MUST only reference factory to verify its minting. -4. If it is transferrable, it is account-bound. If not, it is soul-bound. - -A manager contract is defined with these properties: - -1. It MUST be the only kind of contract which calls factory to create. -2. It SHOULD store all related configurations for financial parameters. - -A factory contract is defined with these properties: - -1. It SHALL clone the finance contract with uniform implementation. -2. It MUST be the only contract that can mint account-bound token. -3. It MUST keep an recent id of account bound token. - -A finance contract is defined with these properties: - -1. A finance contract MUST only be initialized once from factory contract in constructor. -2. Funds in the contract SHALL NOT be transferred to other contracts nor accounts unless sender who owns soul-bound or account bound token signs to do so. -3. Every state-changing function of the smart contract MUST only accept sender who owns soul-bound or account bound-token except global function(e.g. liquidation). -4. Global function SHOULD be commented as `/* global */` to clarify the function is can be accessed with anyone. -5. Each finance contract SHOULD be able to represent transaction that has happened only with those who had account-bound token. -6. If soul-bound token is used for access, the finance contract MUST be able to represent transaction that has happened only between whom had the private key and the finance contract. - -#### Contracts - -![Diagram](../assets/eip-5252/media/media.svg) - -
-Contract Diagram of [ERC-5252](eip-5252.md) -
- -**`Manager`**: **`Manager`** contract acts as an entry point to interact with the investor. The contract also stores parameters for **`Finance`** contract. - -**`Factory`**: **`Factory`** contract manages contract bytecode to create for managing investor's fund and clones **`Finance`** contract on **`Manager`** contract's approval. It also mints account-bound tokens to interact with the `Finance` contract. - -**`Finance`**: **`Finance`** contract specifies all rules on managing an investor's fund. The contract is only accessible with an account that has an Account-bound token. When an investor deposits a fund to **`Manager`** contract, the contract sends the fund to **`Finance`** contract account after separating fees for operation. - -**`Account-bound token`**: **`Account-bound token`** contract in this EIP can bring the **`Finance`** contract's data and add metadata. For example, if there is a money market lending -**`Finance`** contract, its **`Account-bound token`** can show how much balance is in agreement using SVG. - -**`Extension`**: **`Extension`** contract is another contract that can utilize locked funds in **`Finance`** contract. The contract can access with **`Finance`** contract on operator's approval managed in **`Manager`** contract. Example use case of `Extension` can be a membership. - -**`Metadata`**: **`Metadata`** contract is the contract where it stores metadata related to account credentials. Credential related data are stored with specific key. Images are usually displayed as SVG, but offchain image is possible. - ---- - -### Governance - -The governance pattern consists of 2 components; influencer and governor. - -#### Interfaces - -#### Requirements - -An influencer contract is defined with these properties: - -1. The contract SHALL manage multiplier for votes. -2. The contract SHALL set a decimal to calculated normalized scores. -3. The contract SHALL set a function where governance can decide factor parameters. - -A governor contract is defined with these properties: - -1. The contract MUST satisfy Governor contract from OpenZeppelin. -2. The contract SHALL refer influencer contract for multiplier -3. The contract MUST limit transfer of account bound token once claimed for double vote prevention. - -#### From Token Governance To Contribution Based Governance - -| | Token Governance | Credential-based Governance | -| ----------- | ---------------------------- | ---------------------------------- | -| Enforcement | More tokens, more power | More contribution, More power | -| Incentives | More tokens, more incentives | More contribution, more incentives | -| Penalty | No penalty | Loss of power | -| Assignment | One who holds the token | One who has the most influence | - -
-Token Governance vs Credential Based Governance -
- -Token governance is not sustainable in that it gives **more** power to "those who most want to rule". Any individual who gets more than 51% of the token supply can forcefully take control. - -New governance that considers contributions to the protocol is needed because: - -- **Rulers can be penalized on breaking the protocol** -- **Rulers can be more effectively incentivized on maintaining the protocol** - -The power should be given to "those who are most responsible". Instead of locked or owned tokens, voting power is determined with contributions marked in Account Bound Tokens (ABT). This EIP defines this form of voting power as **`Influence`**. - -#### Calculating Influence - -**`Influence`** is a multiplier on staked tokens that brings more voting power of a DAO to its contributors. To get **`Influence`**, a score is calculated on weighted contribution matrix. Then, the score is normalized to give a member's position in whole distribution. Finally, the multiplier is determined on the position in every community members. - -#### Calculating score - -The weights represent relative importance on each factor. The total importance is the total sum of the factors. More factors that can be normalized at the time of submitting proposal can be added by community. - -| | Description | -| --- | ----------------------------------------------------------------------------------------- | -| α | Contribution value per each **`Finance`** contract from current proposal | -| β | Time they maintained **`Finance`** per each contract from current timestamp of a proposal | - -```math -(score per each ABT) = α * (contribution value) + β * (time that abt was maintained from now) -``` - -#### Normalization - -Normalization is applied for data integrity on user's contribution in a DAO. -Normalized score can be calculated from the state of submitting a proposal - -```math -(Normalized score per each ABT) = α * (contribution value)/(total contribution value at submitting tx) + β * (time that abt was maintained)/(time passed from genesis to proposal creation) -``` - -and have a value between 0 and 1 (since α + β = 1). - -#### Multiplier - -The multiplier is determined linearly from base factor (b) and multiplier(m). - -The equation for influence is : - -```math -(influence) = m * (sum(normalized_score)) -``` - -#### Example - -For example, if a user has 3 **`Account-bound tokens`** with normalized score of each 1.0, 0.5, 0.3 and the locked token is 100, and multiplier is 0.5 and base factor is 1.5. Then the total influence is - -````math -0.5 * {(1.0 + 0.5 + 0.3) / 3} + 1.5 = 1.8 - - The total voting power would be - -```math -(voting power) = 1.8 * sqrt(100) = 18 -```` - -#### Stakers vs Enforcers - -| | Stakers | Enforcers | -| ------------ | --------------------------------- | --------------------------------------------------------------------------------------- | -| Role | stake governance token for voting | Contributed on the system, can make proposal to change rule, more voting power like 1.5 | -| Populations | many | small | -| Contribution | Less effect | More effect | -| Influence | sqrt(locked token) | Influence \* sqrt(locked token) | - -
-Stakers vs Enforcers -
- -**Stakers**: Stakers are people who vote to enforcers' proposals and get dividend for staked tokens - -**Enforcers**: Enforcers are people who takes risk on managing protocol and contributes to the protocol by making a proposal and change to it. - -#### Contracts - -**`Influencer`**: An **`Influencer`** contract stores influence configurations and measures the contribution of a user from his activities done in a registered Account Bound Token contract. The contract puts a lock on that Account Bound Token until the proposal is finalized. - -**`Governor`**: **`Governor`** contract is compatible with the current governor contract in OpenZeppelin. For its special use case, it configures factors where the influencer manages and has access to changing parameters of **`Manager`** configs. Only the `Enforcer` can propose new parameters. - -## Rationale - -### Gas saving for end user - -The gas cost of using multiple contracts (as opposed to a single one) actually saves gas long-run if the clone factory pattern is applied. One contract storing users' states globally means each user is actually paying for the storage cost of other users after interacting with the contract. This, for example, means that MakerDAO's contract operating cost is sometimes over 0.1 ETH, limitimg users' minimum deposit for CDP in order to save gas costs. To solve inefficient n-times charging gas cost interaction for future users, one contract per user is used. - -#### Separation between investor's and operation fund - -The separation between an investor's funds and operation fee is clearly specified in the smart contract, so investors can ensure safety from arbitrary loss of funds by the operating team's control. - -## Backwards Compatibility - -This EIP has no known backward compatibility issues. - -## Reference Implementation - -[Reference implementation](../assets/eip-5252/README.md) is a simple deposit account contract as `Finance` contract and its contribution value α is measured with deposit amount with ETH. - -## Security Considerations - -- **`Factory`** contracts must ensure that each **`Finance`** contract is registered in the factory and check that **`Finance`** contracts are sending transactions related to their bounded owner. - -- Reentrancy attack guard should be applied or change state before delegatecall in each user function in **`Manager`** contract or **`Finance`** contract. Otherwise, **`Finance`** can be generated as double and ruin whole indices. - -- Once a user locks influence on a proposal's vote, an **`Account Bound Token`** cannot be transferred to another wallet. Otherwise, double influence can happen. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5252.md diff --git a/EIPS/eip-5267.md b/EIPS/eip-5267.md index 7201e790a638d1..8790ddf0221476 100644 --- a/EIPS/eip-5267.md +++ b/EIPS/eip-5267.md @@ -1,175 +1 @@ ---- -eip: 5267 -title: Retrieval of EIP-712 domain -description: A way to describe and retrieve an EIP-712 domain to securely integrate EIP-712 signatures. -author: Francisco Giordano (@frangio) -discussions-to: https://ethereum-magicians.org/t/eip-5267-retrieval-of-eip-712-domain/9951 -status: Final -type: Standards Track -category: ERC -created: 2022-07-14 -requires: 155, 712, 2612 ---- - -## Abstract - -This EIP complements [EIP-712](./eip-712.md) by standardizing how contracts should publish the fields and values that describe their domain. This enables applications to retrieve this description and generate appropriate domain separators in a general way, and thus integrate EIP-712 signatures securely and scalably. - -## Motivation - -EIP-712 is a signature scheme for complex structured messages. In order to avoid replay attacks and mitigate phishing, the scheme includes a "domain separator" that makes the resulting signature unique to a specific domain (e.g., a specific contract) and allows user-agents to inform end users the details of what is being signed and how it may be used. A domain is defined by a data structure with fields from a predefined set, all of which are optional, or from extensions. Notably, EIP-712 does not specify any way for contracts to publish which of these fields they use or with what values. This has likely limited adoption of EIP-712, as it is not possible to develop general integrations, and instead applications find that they need to build custom support for each EIP-712 domain. A prime example of this is [EIP-2612](./eip-2612.md) (permit), which has not been widely adopted by applications even though it is understood to be a valuable improvement to the user experience. The present EIP defines an interface that can be used by applications to retrieve a definition of the domain that a contract uses to verify EIP-712 signatures. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Compliant contracts MUST define `eip712Domain` exactly as declared below. All specified values MUST be returned even if they are not used, to ensure proper decoding on the client side. - -```solidity -function eip712Domain() external view returns ( - bytes1 fields, - string name, - string version, - uint256 chainId, - address verifyingContract, - bytes32 salt, - uint256[] extensions -); -``` - -The return values of this function MUST describe the domain separator that is used for verification of EIP-712 signatures in the contract. They describe both the form of the `EIP712Domain` struct (i.e., which of the optional fields and extensions are present) and the value of each field, as follows. - -- `fields`: A bit map where bit `i` is set to 1 if and only if domain field `i` is present (`0 ≤ i ≤ 4`). Bits are read from least significant to most significant, and fields are indexed in the order that is specified by EIP-712, identical to the order in which they are listed in the function type. -- `name`, `version`, `chainId`, `verifyingContract`, `salt`: The value of the corresponding field in `EIP712Domain`, if present according to `fields`. If the field is not present, the value is unspecified. The semantics of each field is defined in EIP-712. -- `extensions`: A list of EIP numbers, each of which MUST refer to an EIP that extends EIP-712 with new domain fields, along with a method to obtain the value for those fields, and potentially conditions for inclusion. The value of `fields` does not affect their inclusion. - -The return values of this function (equivalently, its EIP-712 domain) MAY change throughout the lifetime of a contract, but changes SHOULD NOT be frequent. The `chainId` field, if used, SHOULD change to mirror the [EIP-155](./eip-155.md) id of the underlying chain. Contracts MAY emit the event `EIP712DomainChanged` defined below to signal that the domain could have changed. - -```solidity -event EIP712DomainChanged(); -``` - -## Rationale - -A notable application of EIP-712 signatures is found in EIP-2612 (permit), which specifies a `DOMAIN_SEPARATOR` function that returns a `bytes32` value (the actual domain separator, i.e., the result of `hashStruct(eip712Domain)`). This value does not suffice for the purposes of integrating with EIP-712, as the RPC methods defined there receive an object describing the domain and not just the separator in hash form. Note that this is not a flaw of the RPC methods, it is indeed part of the security proposition that the domain should be validated and informed to the user as part of the signing process. On its own, a hash does not allow this to be implemented, given it is opaque. The present EIP fills this gap in both EIP-712 and EIP-2612. - -Extensions are described by their EIP numbers because EIP-712 states: "Future extensions to this standard can add new fields [...] new fields should be proposed through the EIP process." - -## Backwards Compatibility - -This is an optional extension to EIP-712 that does not introduce backwards compatibility issues. - -Upgradeable contracts that make use of EIP-712 signatures MAY be upgraded to implement this EIP. - -User-agents or applications that use this EIP SHOULD additionally support those contracts that due to their immutability cannot be upgraded to implement it. The simplest way to achieve this is to hardcode common domains based on contract address and chain id. However, it is also possible to implement a more general solution by guessing possible domains based on a few common patterns using the available information, and selecting the one whose hash matches a `DOMAIN_SEPARATOR` or `domainSeparator` function in the contract. - -## Reference Implementation - -### Solidity Example - -```solidity -pragma solidity 0.8.0; - -contract EIP712VerifyingContract { - function eip712Domain() external view returns ( - bytes1 fields, - string memory name, - string memory version, - uint256 chainId, - address verifyingContract, - bytes32 salt, - uint256[] memory extensions - ) { - return ( - hex"0d", // 01101 - "Example", - "", - block.chainid, - address(this), - bytes32(0), - new uint256[](0) - ); - } -} -``` - -This contract's domain only uses the fields `name`, `chainId`, and `verifyingContract`, therefore the `fields` value is `01101`, or `0d` in hexadecimal. - -Assuming this contract is on Ethereum mainnet and its address is 0x0000000000000000000000000000000000000001, the domain it describes is: - -```json5 -{ - name: "Example", - chainId: 1, - verifyingContract: "0x0000000000000000000000000000000000000001" -} -``` - -### JavaScript - -A domain object can be constructed based on the return values of an `eip712Domain()` invocation. - -```javascript -/** Retrieves the EIP-712 domain of a contract using EIP-5267 without extensions. */ -async function getDomain(contract) { - const { fields, name, version, chainId, verifyingContract, salt, extensions } = - await contract.eip712Domain(); - - if (extensions.length > 0) { - throw Error("Extensions not implemented"); - } - - return buildBasicDomain(fields, name, version, chainId, verifyingContract, salt); -} - -const fieldNames = ['name', 'version', 'chainId', 'verifyingContract', 'salt']; - -/** Builds a domain object without extensions based on the return values of `eip712Domain()`. */ -function buildBasicDomain(fields, name, version, chainId, verifyingContract, salt) { - const domain = { name, version, chainId, verifyingContract, salt }; - - for (const [i, field] of fieldNames.entries()) { - if (!(fields & (1 << i))) { - delete domain[field]; - } - } - - return domain; -} -``` - -#### Extensions - -Suppose EIP-XYZ defines a new field `subdomain` of type `bytes32` and a function `getSubdomain()` to retrieve its value. - -The function `getDomain` from above would be extended as follows. - -```javascript -/** Retrieves the EIP-712 domain of a contract using EIP-5267 with support for EIP-XYZ. */ -async function getDomain(contract) { - const { fields, name, version, chainId, verifyingContract, salt, extensions } = - await contract.eip712Domain(); - - const domain = buildBasicDomain(fields, name, version, chainId, verifyingContract, salt); - - for (const n of extensions) { - if (n === XYZ) { - domain.subdomain = await contract.getSubdomain(); - } else { - throw Error(`EIP-${n} extension not implemented`); - } - } - - return domain; -} -``` - -Additionally, the type of the `EIP712Domain` struct needs to be extended with the `subdomain` field. This is left out of scope of this reference implementation. - -## Security Considerations - -While this EIP allows a contract to specify a `verifyingContract` other than itself, as well as a `chainId` other than that of the current chain, user-agents and applications should in general validate that these do match the contract and chain before requesting any user signatures for the domain. This may not always be a valid assumption. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5267.md diff --git a/EIPS/eip-5269.md b/EIPS/eip-5269.md index a68c789c9359e0..153a6e13c78b05 100644 --- a/EIPS/eip-5269.md +++ b/EIPS/eip-5269.md @@ -1,276 +1 @@ ---- -eip: 5269 -title: ERC Detection and Discovery -description: An interface to identify if major behavior or optional behavior specified in an ERC is supported for a given caller. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc5269-human-readable-interface-detection/9957 -status: Review -type: Standards Track -category: ERC -created: 2022-07-15 -requires: 5750 ---- - -## Abstract - -An interface for better identification and detection of ERC by numbers. -It designates a field in which it's called `majorERCIdentifier` which is normally known or referred to as "ERC number". For example, `ERC-721` aka [ERC-721](./eip-721.md) has a `majorERCIdentifier = 721`. This ERC has a `majorERCIdentifier = 5269`. - -Calling it a `majorERCIdentifier` instead of `ERCNumber` makes it future-proof: anticipating there is a possibility where future ERC is not numbered or if we want to incorporate other types of standards. - -It also proposes a new concept of `minorERCIdentifier` which is left for authors of -individual ERC to define. For example, ERC-721's author may define `ERC721Metadata` -interface as `minorERCIdentifier= keccak256("ERC721Metadata")`. - -It also proposes an event to allow smart contracts to optionally declare the ERCs they support. - -## Motivation - -This ERC is created as a competing standard for [ERC-165](./eip-165.md). - -Here are the major differences between this ERC and [ERC-165](./eip-165.md). - -1. [ERC-165](./eip-165.md) uses the hash of a method's signature which declares the existence of one method or multiple methods, -therefore it requires at least one method to *exist* in the first place. In some cases, some ERCs interface does not have a method, such as some ERCs related to data format and signature schemes or the "Soul-Bound-ness" aka SBT which could just revert a transfer call without needing any specific method. -1. [ERC-165](./eip-165.md) doesn't provide query ability based on the caller. -The compliant contract of this ERC will respond to whether it supports certain ERC *based on* a given caller. - -Here is the motivation for this ERC given ERC-165 already exists: - -1. Using ERC numbers improves human readability as well as make it easier to work with named contract such as ENS. - -2. Instead of using an ERC-165 identifier, we have seen an increasing interest to use ERC numbers as the way to identify or specify an ERC. For example - -- [ERC-5267](./eip-5267.md) specifies `extensions` to be a list of ERC numbers. -- [ERC-600](./eip-600.md), and [ERC-601](./eip-601.md) specify an `ERC` number in the `m / purpose' / subpurpose' / ERC' / wallet'` path. -- [ERC-5568](./eip-5568.md) specifies `The instruction_id of an instruction defined by an ERC MUST be its ERC number unless there are exceptional circumstances (be reasonable)` -- [ERC-6120](./eip-6120.md) specifies `struct Token { uint eip; ..., }` where `uint eip` is an ERC number to identify ERCs. -- `ERC-867`(Stagnant) proposes to create `erpId: A string identifier for this ERP (likely the associated ERC number, e.g. “ERC-1234”).` - -3. Having an ERC/ERC number detection interface reduces the need for a lookup table in smart contract to -convert a function method or whole interface in any ERC in the bytes4 ERC-165 identifier into its respective ERC number and massively simplifies the way to specify ERC for behavior expansion. - -4. We also recognize a smart contract might have different behavior given different caller accounts. One of the most notable use cases is that when using Transparent Upgradable Pattern, a proxy contract gives an Admin account and Non-Admin account different treatment when they call. - -## Specification - -In the following description, we use ERC and ERC inter-exchangeably. This was because while most of the time the description applies to an ERC category of the Standards Track of ERC, the ERC number space is a subspace of ERC number space and we might sometimes encounter ERCs that aren't recognized as ERCs but has behavior that's worthy of a query. - -1. Any compliant smart contract MUST implement the following interface - -```solidity -// DRAFTv1 -pragma solidity ^0.8.9; - -interface IERC5269 { - event OnSupportERC( - address indexed caller, // when emitted with `address(0x0)` means all callers. - uint256 indexed majorERCIdentifier, - bytes32 indexed minorERCIdentifier, // 0 means the entire ERC - bytes32 ercStatus, - bytes extraData - ); - - /// @dev The core method of ERC Interface Detection - /// @param caller, a `address` value of the address of a caller being queried whether the given ERC is supported. - /// @param majorERCIdentifier, a `uint256` value and SHOULD BE the ERC number being queried. Unless superseded by future ERC, such ERC number SHOULD BE less or equal to (0, 2^32-1]. For a function call to `supportERC`, any value outside of this range is deemed unspecified and open to implementation's choice or for future ERCs to specify. - /// @param minorERCIdentifier, a `bytes32` value reserved for authors of individual ERC to specify. For example the author of [ERC-721](/ERCS/eip-721) MAY specify `keccak256("ERC721Metadata")` or `keccak256("ERC721Metadata.tokenURI")` as `minorERCIdentifier` to be quired for support. Author could also use this minorERCIdentifier to specify different versions, such as ERC-712 has its V1-V4 with different behavior. - /// @param extraData, a `bytes` for [ERC-5750](/ERCS/eip-5750) for future extensions. - /// @return ercStatus, a `bytes32` indicating the status of ERC the contract supports. - /// - For FINAL ERCs, it MUST return `keccak256("FINAL")`. - /// - For non-FINAL ERCs, it SHOULD return `keccak256("DRAFT")`. - /// During ERC procedure, ERC authors are allowed to specify their own - /// ercStatus other than `FINAL` or `DRAFT` at their discretion such as `keccak256("DRAFTv1")` - /// or `keccak256("DRAFT-option1")`and such value of ercStatus MUST be documented in the ERC body - function supportERC( - address caller, - uint256 majorERCIdentifier, - bytes32 minorERCIdentifier, - bytes calldata extraData) - external view returns (bytes32 ercStatus); -} -``` - -In the following description, `ERC_5269_STATUS` is set to be `keccak256("DRAFTv1")`. - -In addition to the behavior specified in the comments of `IERC5269`: - -1. Any `minorERCIdentifier=0` is reserved to be referring to the main behavior of the ERC being queried. -2. The Author of compliant ERC is RECOMMENDED to declare a list of `minorERCIdentifier` for their optional interfaces, behaviors and value range for future extension. -3. When this ERC is FINAL, any compliant contract MUST return an `ERC_5269_STATUS` for the call of `supportERC((any caller), 5269, 0, [])` - -*Note*: at the current snapshot, the `supportERC((any caller), 5269, 0, [])` MUST return `ERC_5269_STATUS`. - -4. Any complying contract SHOULD emit an `OnSupportERC(address(0), 5269, 0, ERC_5269_STATUS, [])` event upon construction or upgrade. -5. Any complying contract MAY declare for easy discovery any ERC main behavior or sub-behaviors by emitting an event of `OnSupportERC` with relevant values and when the compliant contract changes whether the support an ERC or certain behavior for a certain caller or all callers. -6. For any `ERC-XXX` that is NOT in `Final` status, when querying the `supportERC((any caller), xxx, (any minor identifier), [])`, it MUST NOT return `keccak256("FINAL")`. It is RECOMMENDED to return `0` in this case but other values of `ercStatus` is allowed. Caller MUST treat any returned value other than `keccak256("FINAL")` as non-final, and MUST treat 0 as strictly "not supported". -7. The function `supportERC` MUST be mutability `view`, i.e. it MUST NOT mutate any global state of EVM. - -## Rationale - -1. When data type `uint256 majorERCIdentifier`, there are other alternative options such as: - -- (1) using a hashed version of the ERC number, -- (2) use a raw number, or -- (3) use an ERC-165 identifier. - -The pros for (1) are that it automatically supports any evolvement of future ERC numbering/naming conventions. -But the cons are it's not backward readable: seeing a `hash(ERC-number)` one usually can't easily guess what their ERC number is. - -We choose the (2) in the rationale laid out in motivation. - -2. We have a `bytes32 minorERCIdentifier` in our design decision. Alternatively, it could be (1) a number, forcing all ERC authors to define its numbering for sub-behaviors so we go with a `bytes32` and ask the ERC authors to use a hash for a string name for their sub-behaviors which they are already doing by coming up with interface name or method name in their specification. - -3. Alternatively, it's possible we add extra data as a return value or an array of all ERC being supported but we are unsure how much value this complexity brings and whether the extra overhead is justified. - -4. Compared to [ERC-165](./eip-165.md), we also add an additional input of `address caller`, given the increasing popularity of proxy patterns such as those enabled by [ERC-1967](./eip-1967.md). One may ask: why not simply use `msg.sender`? This is because we want to allow query them without transaction or a proxy contract to query whether interface ERC-`number` will be available to that particular sender. - -1. We reserve the input `majorERCIdentifier` greater than or equals `2^32` in case we need to support other collections of standards which is not an ERC/ERC. - -## Test Cases - -```typescript - -describe("ERC5269", function () { - async function deployFixture() { - // ... - } - - describe("Deployment", function () { - // ... - it("Should emit proper OnSupportERC events", async function () { - let { txDeployErc721 } = await loadFixture(deployFixture); - let events = txDeployErc721.events?.filter(event => event.event === 'OnSupportERC'); - expect(events).to.have.lengthOf(4); - - let ev5269 = events!.filter( - (event) => event.args!.majorERCIdentifier.eq(5269)); - expect(ev5269).to.have.lengthOf(1); - expect(ev5269[0].args!.caller).to.equal(BigNumber.from(0)); - expect(ev5269[0].args!.minorERCIdentifier).to.equal(BigNumber.from(0)); - expect(ev5269[0].args!.ercStatus).to.equal(ethers.utils.id("DRAFTv1")); - - let ev721 = events!.filter( - (event) => event.args!.majorERCIdentifier.eq(721)); - expect(ev721).to.have.lengthOf(3); - expect(ev721[0].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[0].args!.minorERCIdentifier).to.equal(BigNumber.from(0)); - expect(ev721[0].args!.ercStatus).to.equal(ethers.utils.id("FINAL")); - - expect(ev721[1].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[1].args!.minorERCIdentifier).to.equal(ethers.utils.id("ERC721Metadata")); - expect(ev721[1].args!.ercStatus).to.equal(ethers.utils.id("FINAL")); - - // ... - }); - - it("Should return proper ercStatus value when called supportERC() for declared supported ERC/features", async function () { - let { erc721ForTesting, owner } = await loadFixture(deployFixture); - expect(await erc721ForTesting.supportERC(owner.address, 5269, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("DRAFTv1")); - expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("FINAL")); - expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.id("ERC721Metadata"), [])).to.equal(ethers.utils.id("FINAL")); - // ... - - expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0)); - expect(await erc721ForTesting.supportERC(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0)); - }); - - it("Should return zero as ercStatus value when called supportERC() for non declared ERC/features", async function () { - let { erc721ForTesting, owner } = await loadFixture(deployFixture); - expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0)); - expect(await erc721ForTesting.supportERC(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0)); - }); - }); -}); -``` - -See [`TestERC5269.ts`](../assets/eip-5269/test/TestERC5269.ts). - -## Reference Implementation - -Here is a reference implementation for this ERC: - -```solidity -contract ERC5269 is IERC5269 { - bytes32 constant public ERC_STATUS = keccak256("DRAFTv1"); - constructor () { - emit OnSupportERC(address(0x0), 5269, bytes32(0), ERC_STATUS, ""); - } - - function _supportERC( - address /*caller*/, - uint256 majorERCIdentifier, - bytes32 minorERCIdentifier, - bytes calldata /*extraData*/) - internal virtual view returns (bytes32 ercStatus) { - if (majorERCIdentifier == 5269) { - if (minorERCIdentifier == bytes32(0)) { - return ERC_STATUS; - } - } - return bytes32(0); - } - - function supportERC( - address caller, - uint256 majorERCIdentifier, - bytes32 minorERCIdentifier, - bytes calldata extraData) - external virtual view returns (bytes32 ercStatus) { - return _supportERC(caller, majorERCIdentifier, minorERCIdentifier, extraData); - } -} -``` - -See [`ERC5269.sol`](../assets/eip-5269/contracts/ERC5269.sol). - -Here is an example where a contract of [ERC-721](./eip-721.md) also implement this ERC to make it easier -to detect and discover: - -```solidity -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "../ERC5269.sol"; -contract ERC721ForTesting is ERC721, ERC5269 { - - bytes32 constant public ERC_FINAL = keccak256("FINAL"); - constructor() ERC721("ERC721ForTesting", "E721FT") ERC5269() { - _mint(msg.sender, 0); - emit OnSupportERC(address(0x0), 721, bytes32(0), ERC_FINAL, ""); - emit OnSupportERC(address(0x0), 721, keccak256("ERC721Metadata"), ERC_FINAL, ""); - emit OnSupportERC(address(0x0), 721, keccak256("ERC721Enumerable"), ERC_FINAL, ""); - } - - function supportERC( - address caller, - uint256 majorERCIdentifier, - bytes32 minorERCIdentifier, - bytes calldata extraData) - external - override - view - returns (bytes32 ercStatus) { - if (majorERCIdentifier == 721) { - if (minorERCIdentifier == 0) { - return keccak256("FINAL"); - } else if (minorERCIdentifier == keccak256("ERC721Metadata")) { - return keccak256("FINAL"); - } else if (minorERCIdentifier == keccak256("ERC721Enumerable")) { - return keccak256("FINAL"); - } - } - return super._supportERC(caller, majorERCIdentifier, minorERCIdentifier, extraData); - } -} - -``` - -See [`ERC721ForTesting.sol`](../assets/eip-5269/contracts/testing/ERC721ForTesting.sol). - -## Security Considerations - -Similar to [ERC-165](./eip-165.md) callers of the interface MUST assume the smart contract -declaring they support such ERC interfaces doesn't necessarily correctly support them. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5269.md diff --git a/EIPS/eip-5289.md b/EIPS/eip-5289.md index 2675ba044ce129..286e1dccbbdeb7 100644 --- a/EIPS/eip-5289.md +++ b/EIPS/eip-5289.md @@ -1,95 +1 @@ ---- -eip: 5289 -title: Ethereum Notary Interface -description: Allows Smart Contracts to be Legally Binding Off-Chain -author: Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/pr-5289-discussion-notary-interface/9980 -status: Review -type: Standards Track -category: ERC -created: 2022-07-16 -requires: 165, 5568 ---- - -## Abstract - -Currently, the real-world applications of smart contracts are limited by the fact that they aren't legally binding. This EIP proposes a standard that allows smart contracts to be legally binding by providing IPFS links to legal documents and ensuring that the users of the smart contract have privity with the relevant legal documents. - -Please note that the authors are not lawyers, and that this EIP is not legal advice. - -## Motivation - -NFTs have oftentimes been branded as a way to hold and prove copyright of a specific work. However, this, in practice, has almost never been the case. Most of the time, NFTs have no legally-binding meaning, and in the rare cases that do, the NFT simply provides a limited license for the initial holder to use the work (but cannot provide any license for any future holders). - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Legal Contract Library Interface - -```solidity -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC165.sol"; - -interface IERC5289Library is IERC165 { - /// @notice Emitted when signDocument is called - event DocumentSigned(address indexed signer, uint16 indexed documentId); - - /// @notice An immutable link to the legal document (RECOMMENDED to be hosted on IPFS). This MUST use a common file format, such as PDF, HTML, TeX, or Markdown. - function legalDocument(uint16 documentId) external view returns (string memory); - - /// @notice Returns whether or not the given user signed the document. - function documentSigned(address user, uint16 documentId) external view returns (bool signed); - - /// @notice Returns when the the given user signed the document. - /// @dev If the user has not signed the document, the timestamp may be anything. - function documentSignedAt(address user, uint16 documentId) external view returns (uint64 timestamp); - - /// @notice Sign a document - /// @dev This MUST be validated by the smart contract. This MUST emit DocumentSigned or throw. - function signDocument(address signer, uint16 documentId) external; -} -``` - -### Requesting a Signature - -To request that certain documents be signed, revert with an [ERC-5568](./eip-5568.md) signal. The format of the `instruction_data` is an ABI-encoded `(address, uint16)` pair, where the address is the address of the library, and the `uint16` is the `documentId` of the document: - -```solidity -throw WalletSignal24(0, 5289, abi.encode(0xcbd99eb81b2d8ca256bb6a5b0ef7db86489778a7, 12345)); -``` - -### Signing a Document - -When a signature is requested, wallets MUST call `legalDocument`, display the resulting document to the user, and prompt them to either sign the document or cancel: - -![image](../assets/eip-5289/example-popup.png) - -If the user agrees, the wallet MUST call `signDocument`. - -## Rationale - -- `uint64` was chosen for the timestamp return type as 64-bit time registers are standard. -- `uint16` was chosen for the document ID as 65536 documents are likely sufficient for any use case, and the contract can always be re-deployed. -- `signDocument` doesn't take an ECDSA signature for future compatibility with account abstraction. In addition, future extensions can supply this functionality. -- IPFS is mandatory because the authenticity of the signed document can be proven. - -## Backwards Compatibility - -No backwards compatibility issues found. - -## Reference Implementation - -### Legal Contract Library - -See [`IERC5289Library`](../assets/eip-5289/interfaces/IERC5289Library.sol), [`ERC5289Library`](../assets/eip-5289/ERC5289Library.sol). - -## Security Considerations - -Users can claim that their private key was stolen and used to fraudulently "sign" contracts. As such, **documents must only be permissive in nature, not restrictive.** For example, a document granting a license to use the image attached to an NFT would be acceptable, as there is no reason for the signer to plausibly deny signing the document. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5289.md diff --git a/EIPS/eip-5298.md b/EIPS/eip-5298.md index 05c7f3b2690860..93b944b3e094b8 100644 --- a/EIPS/eip-5298.md +++ b/EIPS/eip-5298.md @@ -1,153 +1 @@ ---- -eip: 5298 -title: ENS Trust to hold NFTs under ENS name -description: An interface for a smart contract acting as a "trust" that holds tokens by ENS name. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-eip-5198-ens-as-token-holder/10374 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-07-12 -requires: 137, 721, 1155 ---- - -## Abstract - -This EIP standardizes an interface for smart contracts to hold of [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) tokens on behalf of ENS domains. - -## Motivation - -Currently, if someone wants to receive a token, they have to set up a wallet address. This EIP decouples NFT ownership from wallet addresses. - -## Specification - -1. Compliant contracts MUST implement `ERC721TokenReceiver`, as defined in [EIP-721](./eip-721.md). -2. Compliant contracts implement the following interface: - -```solidity -interface IERC_ENS_TRUST is ERC721Receiver, ERC1155Receiver { - function claimTo(address to, bytes32 ensNode, address operator, uint256 tokenId) payable external; -} -``` - -3. `claimTo` MUST check if `msg.sender` is the owner of the ENS node identified by `bytes32 ensNode` (and/or approved by the domain in implementation-specific ways). The compliant contract then MUST make a call to the `safeTransferFrom` function of [EIP-721](./eip-712.md) or [EIP-1155](./eip-1155.md). - -4. Any `ensNode` is allowed. - -## Rationale - -1. ENS was chosen because it is a well-established scoped ownership namespace. -This is nonetheless compatible with other scoped ownership namespaces. - -2. We didn't expose getters or setters for ensRoot because it is outside of the scope of this EIP. - -## Backwards Compatibility - -No backward compatibility issues were found. - -## Test Cases - -```ts -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("FirstENSBankAndTrust", function () { - - describe("Receive and Claim Token", function () { - - it("Should ACCEPT/REJECT claimTo based on if ENS owner is msg.sender", async function () { - ... - // Steps of testing: - // mint to charlie - // charlie send to ENSTrust and recorded under bob.xinbenlvethsf.eth - // bob try to claimTo alice, first time it should be rejected - // bob then set the ENS record - // bob claim to alice, second time it should be accepted - - // mint to charlie - await erc721ForTesting.mint(charlie.address, fakeTokenId); - - // charlie send to ENSTrust and recorded under bob.xinbenlvethsf.eth - await erc721ForTesting.connect(charlie)["safeTransferFrom(address,address,uint256,bytes)"]( - charlie.address, firstENSBankAndTrust.address, - fakeTokenId, - fakeReceiverENSNamehash - ); - - // bob try to claimTo alice, first time it should be rejected - await expect(firstENSBankAndTrust.connect(bob).claimTo( - alice.address, - fakeReceiverENSNamehash, - firstENSBankAndTrust.address, - fakeTokenId - )) - .to.be.rejectedWith("ENSTokenHolder: node not owned by sender"); - - // bob then set the ENS record - await ensForTesting.setOwner( - fakeReceiverENSNamehash, bob.address - ); - - // bob claim to alice, second time it should be accepted - await expect(firstENSBankAndTrust.connect(bob).claimTo( - alice.address, - fakeReceiverENSNamehash, - erc721ForTesting.address, - fakeTokenId - )); - }); - }); -}); -``` - -## Reference Implementation - -```solidity -pragma solidity ^0.8.9; - -contract FirstENSBankAndTrust is IERC721Receiver, Ownable { - function getENS() public view returns (ENS) { - return ENS(ensAddress); - } - - function setENS(address newENSAddress) public onlyOwner { - ensAddress = newENSAddress; - } - - // @dev This function is called by the owner of the token to approve the transfer of the token - // @param data MUST BE the ENS node of the intended token receiver this ENSHoldingServiceForNFT is holding on behalf of. - function onERC721Received( - address operator, - address /*from*/, - uint256 tokenId, - bytes calldata data - ) external override returns (bytes4) { - require(data.length == 32, "ENSTokenHolder: last data field must be ENS node."); - // --- START WARNING --- - // DO NOT USE THIS IN PROD - // this is just a demo purpose of using extraData for node information - // In prod, you should use a struct to store the data. struct should clearly identify the data is for ENS - // rather than anything else. - bytes32 ensNode = bytes32(data[0:32]); - // --- END OF WARNING --- - - addToHolding(ensNode, operator, tokenId); // conduct the book keeping - return ERC721_RECEIVER_MAGICWORD; - } - - function claimTo(address to, bytes32 ensNode, address tokenContract uint256 tokenId) public { - require(getENS().owner(ensNode) == msg.sender, "ENSTokenHolder: node not owned by sender"); - removeFromHolding(ensNode, tokenContract, tokenId); - IERC721(tokenContract).safeTransferFrom(address(this), to, tokenId); - } -} -``` - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5298.md diff --git a/EIPS/eip-5313.md b/EIPS/eip-5313.md index 7c7e53f0cfe77f..407550b93d2d77 100644 --- a/EIPS/eip-5313.md +++ b/EIPS/eip-5313.md @@ -1,61 +1 @@ ---- -eip: 5313 -title: Light Contract Ownership -description: An interface for identifying ownership of contracts -author: William Entriken (@fulldecent) -discussions-to: https://ethereum-magicians.org/t/eip-5313-light-contract-ownership/10052 -status: Final -type: Standards Track -category: ERC -created: 2022-07-22 -requires: 165, 173 ---- - -## Abstract - -This specification defines the minimum interface required to identify an account that controls a contract. - -## Motivation - -This is a slimmed-down alternative to [EIP-173](./eip-173.md). - -## Specification - -The key word “MUST” in this document is to be interpreted as described in RFC 2119. - -Every contract compliant with this EIP MUST implement the `EIP5313` interface. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.15; - -/// @title EIP-5313 Light Contract Ownership Standard -interface EIP5313 { - /// @notice Get the address of the owner - /// @return The address of the owner - function owner() view external returns(address); -} -``` - -## Rationale - -Key factors influencing the standard: - -- Minimize the number of functions in the interface -- Backwards compatibility with existing contracts - -This standard can be (and has been) extended by other standards to add additional ownership functionality. The smaller scope of this specification allows more and more straightforward ownership implementations, see limitations explained in EIP-173 under "other schemes that were considered". - -Implementing [EIP-165](./eip-165.md) could be a valuable addition to this interface specification. However, this EIP is being written to codify existing protocols that connect contracts (often NFTs), with third-party websites (often a well-known NFT marketplace). - -## Backwards Compatibility - -Every contract that implements EIP-173 already implements this specification. - -## Security Considerations - -Because this specification does not extend EIP-165, calling this EIP's `owner` function cannot result in complete certainty that the result is indeed the owner. For example, another function with the same function signature may return some value that is then interpreted to be the true owner. If this EIP is used solely to identify if an account is the owner of a contract, then the impact of this risk is minimized. But if the interrogator is, for example, sending a valuable NFT to the identified owner of any contract on the network, then the risk is heightened. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5313.md diff --git a/EIPS/eip-5334.md b/EIPS/eip-5334.md index d2d26624bae83b..8c990a99e6d5e0 100644 --- a/EIPS/eip-5334.md +++ b/EIPS/eip-5334.md @@ -1,115 +1 @@ ---- -eip: 5334 -title: EIP-721 User And Expires And Level Extension -description: Add a time-limited role with restricted permissions to EIP-721 tokens. -author: Yan (@yan253319066) -discussions-to: https://ethereum-magicians.org/t/erc-721-user-and-expires-and-level-extension/10097 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-07-25 -requires: 165, 721 ---- - -## Abstract - -An [EIP-721](./eip-721.md) extension that adds an additional role (`user`) which can be granted to addresses, and a time where the role is automatically revoked (`expires`) and (`level`) . The `user` role represents permission to "use" the NFT, but not the ability to transfer it or set users. - -## Motivation - -Some NFTs have certain utilities. For example, virtual land can be "used" to build scenes, and NFTs representing game assets can be "used" in-game. In some cases, the owner and user may not always be the same. There may be an owner of the NFT that rents it out to a “user”. The actions that a “user” should be able to take with an NFT would be different from the “owner” (for instance, “users” usually shouldn’t be able to sell ownership of the NFT).  In these situations, it makes sense to have separate roles that identify whether an address represents an “owner” or a “user” and manage permissions to perform actions accordingly. - -Some projects already use this design scheme under different names such as “operator” or “controller” but as it becomes more and more prevalent, we need a unified standard to facilitate collaboration amongst all applications. - -Furthermore, applications of this model (such as renting) often demand that user addresses have only temporary access to using the NFT. Normally, this means the owner needs to submit two on-chain transactions, one to list a new address as the new user role at the start of the duration and one to reclaim the user role at the end. This is inefficient in both labor and gas and so an “expires” and “level” function is introduced that would facilitate the automatic end of a usage term without the need of a second transaction. - -Here are some of the problems that are solved by this standard: - -### Clear Rights Assignment - -With Dual “owner” and “user” roles, it becomes significantly easier to manage what lenders and borrowers can and cannot do with the NFT (in other words, their rights). Additionally, owners can control who the user is and it’s easy for other projects to assign their own rights to either the owners or the users. - -### Simple On-chain Time Management - -Once a rental period is over, the user role needs to be reset and the “user” has to lose access to the right to use the NFT. This is usually accomplished with a second on-chain transaction but that is gas inefficient and can lead to complications because it’s imprecise. With the `expires` function, there is no need for another transaction because the “user” is invalidated automatically after the duration is over. - -### Easy Third-Party Integration - -In the spirit of permission less interoperability, this standard makes it easier for third-party protocols to manage NFT usage rights without permission from the NFT issuer or the NFT application. Once a project has adopted the additional `user` role and `expires` and `level`, any other project can directly interact with these features and implement their own type of transaction. For example, a PFP NFT using this standard can be integrated into both a rental platform where users can rent the NFT for 30 days AND, at the same time, a mortgage platform where users can use the NFT while eventually buying ownership of the NFT with installment payments. This would all be done without needing the permission of the original PFP project. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Contract Interface -Solidity Interface with NatSpec & OpenZeppelin v4 Interfaces (also available at [`IERC5334.sol`](../assets/eip-5334/IERC5334.sol)): - -```solidity -interface IERC5334 { - - // Logged when the user of a NFT, expires, or level is changed - /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed or the user `level` is changed - /// The zero address for user indicates that there is no user address - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires, uint8 level); - - /// @notice set the user and expires and level of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - /// @param level user level - function setUser(uint256 tokenId, address user, uint64 expires, uint8 level) external; - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId) external view returns(address); - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) external view returns(uint256); - - /// @notice Get the user level of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user level for - /// @return The user level for this NFT - function userLevel(uint256 tokenId) external view returns(uint256); -} -``` - -The `userOf(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `userExpires(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `userLevel(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `setUser(uint256 tokenId, address user, uint64 expires)` function MAY be implemented as `public` or `external`. - -The `UpdateUser` event MUST be emitted when a user address is changed or the user expires is changed or the user level is changed. - - - -## Rationale - -TBD - -## Backwards Compatibility - -As mentioned in the specifications section, this standard can be fully EIP-721 compatible by adding an extension function set. - -In addition, new functions introduced in this standard have many similarities with the existing functions in EIP-721. This allows developers to easily adopt the standard quickly. - -## Reference Implementation -A reference implementation of this standard can be found in the assets folder. - - -## Security Considerations - -This EIP standard can completely protect the rights of the owner, the owner can change the NFT user and expires and level at any time. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5334.md diff --git a/EIPS/eip-5375.md b/EIPS/eip-5375.md index 3eaa5516c7ee1f..3233524b21dd4a 100644 --- a/EIPS/eip-5375.md +++ b/EIPS/eip-5375.md @@ -1,304 +1 @@ ---- -eip: 5375 -title: NFT Author Information and Consent -description: An extension of EIP-721 for NFT authorship and author consent. -author: Samuele Marro (@samuelemarro), Luca Donno (@lucadonnoh) -discussions-to: https://ethereum-magicians.org/t/eip-5375-nft-authorship/10182 -status: Final -type: Standards Track -category: ERC -created: 2022-07-30 -requires: 55, 155, 712, 721, 1155 ---- - -## Abstract - -This EIP standardizes a JSON format for storing off-chain information about NFT authors. Specifically, it adds a new field which provides a list of author names, addresses, and proofs of _authorship consent_: proofs that the authors have agreed to be named as authors. Note that a proof of authorship _consent_ is not a proof of authorship: an address can consent without having authored the NFT. - -## Motivation - -There is currently no standard to identify authors of an NFT, and existing techniques have issues: - -- Using the mint `tx.origin` or `msg.sender` - - Assumes that the minter and the author are the same - - Does not support multiple authors -- Using the first Transfer event for a given ID - - Contract/minter can claim that someone else is the author without their consent - - Does not support multiple authors -- Using a custom method/custom JSON field - - Requires per-contract support by NFT platforms - - Contract/minter can claim that someone else is the author without their consent - -The first practice is the most common. However, there are several situations where the minter and the author might not be the same, such as: - -- NFTs minted by a contract -- Lazy minting -- NFTs minted by an intermediary (which can be particularly useful when the author is not tech-savvy and/or the minting process is convoluted) - -This document thus defines a standard which allows the minter to provide authorship information, while also preventing authorship claims without the author's consent. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -All addresses used in this standard MUST follow the casing rules described in [EIP-55](./eip-55.md). - -### Definitions - -- **Authors**: creators of an NFT -- **Minter**: entity responsible for the actual minting transaction; the minter and the authors MAY be the same -- **Verifier**: entity that wants to verify the authorship of an NFT (e.g. a user or an NFT marketplace) -- **Author Consent Proof (ACP)**: a signed message that proves that the signer agrees to be considered the author of the NFT - -### Authorship Support - -The standard introduces a new JSON field, named `authorInfo`. It provides a REQUIRED interface for authorship claiming, as well as an OPTIONAL interface for author consent proofs. - -`authorInfo` is a top-level field of the NFT metadata. Specifically: - -- If a contract supports the metadata extension for [EIP-721](./eip-721.md), the JSON document pointed by `tokenURI(uint256 _tokenId)` MUST include the top-level field `authorInfo` -- If a contract supports the metadata extension for [EIP-1155](./eip-1155.md), the JSON document pointed by `uri(uint256 _id)` MUST include a top-level field `authorInfo` - -The JSON schema of `authorInfo` (named `ERC5375AuthorInfoSchema`) is defined as follows: - -```json -{ - "type": "object", - "properties": { - "consentInfo": { - "type": "object", - "description": "Helper fields for consent verification", - "properties": { - "chainId": { - "type": "integer", - "description": "EIP-155 chain id" - }, - "id": { - "type": "string", - "description": "NFT id" - }, - "contractAddress": { - "type": "string", - "description": "0x-prefixed address of the smart contract" - } - } - }, - "authors": { - "type": "array", - "items": "ERC5375AuthorSchema" - } - }, - "required": [ "authors" ] -} -``` - -Note that `authors` MAY be an empty array. - -`ERC5375AuthorSchema` is defined as follows: - -```json -{ - "type": "object", - "properties": { - "address": { - "type": "string", - "description": "0x-prefixed address of the author" - }, - "consent": { - "type": "ERC5375AuthorConsentSchema", - "description": "Author consent information" - } - }, - "required": [ "address" ] -} -``` - -Moreover, if the `consent` field is present, the `consentInfo` field of `authorInfo` MUST be present. - -`ERC5375AuthorConsentSchema` is defined as follows: - -```json -{ - "type": "object", - "properties": { - "consentData": { - "type": "object", - "properties": { - "version": { - "type": "string", - "description": "NFT authorship consent schema version" - }, - "issuer": { - "type": "string", - "description": "0x-prefixed address of the author" - }, - "metadataFields": { - "type": "object" - } - }, - "required": ["version", "issuer", "metadataFields"] - }, - "publicKey": { - "type": "string", - "description": "EVM public key of the author" - }, - "signature": { - "type": "string", - "description": "EIP-712 signature of the consent message" - } - }, - "required": ["consentData", "publicKey", "signature"] -} -``` - -where `metadataFields` is an object containing the JSON top-level fields (excluding `authorInfo`) that the author will certify. Note that the keys of `metadataFields` MAY be a (potentially empty) subset of the set of fields. - -`consentData` MAY support additional fields as defined by other EIPs. `consentData` MUST contain all the information (which is not already present in other fields) required to verify the validity of an authorship consent proof. - -### Author Consent - -Consent is obtained by signing an [EIP-712](./eip-712.md) compatible message. Specifically, the structure is defined as follows: - -```solidity -struct Author { - address subject; - uint256 tokenId; - string metadata; -} -``` - -where `subject` is the address of the NFT contract, `tokenId` is the id of the NFT and `metadata` is the JSON encoding of the fields listed in `metadataFields`. `metadata`: - -- MUST contain exactly the same fields as the ones listed in `metadataFields`, in the same order -- MUST escape all non-ASCII characters. If the escaped character contains hexadecimal letters, they MUST be uppercase -- MUST not contain any whitespace that is not part of a field name or value - -For example, if the top-level JSON fields are: - -```json -{ - "name": "The Holy Hand Grenade of Antioch", - "description": "Throw in the general direction of your favorite rabbit, et voilà", - "damage": 500, - "authors": [...], - ... -} -``` - -and the content of `metadataFields` is `["name", "description"]`, the content of `metadata` is: - -```json -{ - "name": "The Holy Hand Grenade of Antioch", - "description": "Throw in the general direction of your favorite rabbit, et voil\u00E0" -} -``` - -Similarly to `consentData`, this structure MAY support additional fields as defined by other EIPs. - -The domain separator structure is - -```solidity -struct EIP712Domain { - string name; - string version; - uint256 chainId; -} -``` - -where `name` and `version` are the same fields described in `consentData` - -This structure MAY support additional fields as defined by other EIPs. - -### Author Consent Verification - -Verification is performed using EIP-712 on an author-by-author basis. Specifically, given a JSON document D1, a consent proof is valid if all of the following statements are true: - -- D1 has a top-level `authorInfo` field that matches `ERC5375AuthorInfoSchema` -- `consent` exists and matches `ERC5375AuthorConsentSchema`; -- If calling `tokenURI` (for EIP-721) or `uri` (for EIP-1155) returns the URI of a JSON document D2, all the top-level fields listed in `metadataFields` MUST exist and have the same value; -- The EIP-712 signature in `signature` (computed using the fields specified in the JSON document) is valid; - -Verifiers MUST NOT assume that an NFT with a valid consent proof from address X means that X is the actual author. On the other hand, verifiers MAY assume that if an NFT does not provide a valid consent proof for address X, then X is not the actual author. - -## Rationale - -### Why provide only an author consent proof? - -Adding support for full authorship proofs (i.e. Alice is the author and no one else is the author) requires a protocol to prove that someone is the only author of an NFT. -In other words, we need to answer the question: "Given an NFT Y and a user X claiming to be the author, is X the original author of Y?". - -For the sake of the argument, assume that there exists a protocol that, given an NFT Y, can determine the original author of Y. Even if such method existed, an attacker could slightly modify Y, thus obtaining a new NFT Y', and rightfully claim to be the author of Y', despite the fact that it is not an original work. Real-world examples include changing some pixels of an image or replacing some words of a text with synonyms. -Preventing this behavior would require a general formal definition of when two NFTs are semantically equivalent. Even if defining such a concept were possible, it would still be beyond the scope of this EIP. - -Note that this issue is also present when using the minter's address as a proxy for the author. - -### Why off-chain? - -There are three reasons: - -- Adding off-chain support does not require modifications to existing smart contracts; -- Off-chain storage is usually much cheaper than on-chain storage, thus reducing the implementation barrier; -- While there may be some use cases for full on-chain authorship proofs (e.g. a marketplace providing special features for authors), there are limited applications for on-chain author consent, due to the fact that it is mostly used by users to determine the subjective value of an NFT. - -### Why repeat id, chainId and contractAddress? - -In many cases, this data can be derived from contextual information. However, requiring their inclusion in the JSON document ensures that author consent can be verified using only the JSON document. - -### Why not implement a revocation system? - -Authorship is usually final: either someone created an NFT or they didn't. Moreover, a revocation system would impose additional implementation requirements on smart contracts and increase the complexity of verification. Smart contracts MAY implement a revocation system, such as the one defined in other EIPs. - -#### Why escape non-ASCII characters in the signature message? - -EIP-712 is designed with the possibility of on-chain verification in mind; while on-chain verification is not a priority for this EIP, non-ASCII characters are escaped due to the high complexity of dealing with non-ASCII strings in smart contracts. - -### Usability Improvements for Authors - -Since the author only needs to sign an EIP-712 message, this protocol allows minters to handle the technical aspects of minting while still preserving the secrecy of the author's wallet. Specifically, the author only needs to: - -- Obtain an EVM wallet; -- Learn how to read and sign a EIP-712 message (which can often be simplified by using a Dapp) - -without needing to: - -- Obtain the chain's native token (e.g. through trading or bridging); -- Sign a transaction; -- Understand the pricing mechanism of transactions; -- Verify if a transaction has been included in a block - -This reduces the technical barrier for authors, thus increasing the usability of NFTs, without requiring authors to hand over their keys to a tech-savvy intermediary. - -### Limitations of Address-Based Consent - -The standard defines a protocol to verify that a certain _address_ provided consent. However, it does not guarantee that the address corresponds to the expected author (such as the one provided in the `name` field). Proving a link between an address and the entity behind it is beyond the scope of this document. - -## Backwards Compatibility - -No backward compatibility issues were found. - -## Security Considerations - -### Attacks - -A potential attack that exploits this EIP involves tricking authors into signing authorship consent messages against their wishes. For this reason, authors MUST verify that all signature fields match the required ones. - -A more subtle approach involves not adding important fields to `metadataFields`. By doing so, the author signature might be valid even if the minter changes critical information. - -### Deprecated Features - -`ERC5375AuthorInfoSchema` also originally included a field to specify a human-readable name for the author (without any kind of verification). This was scrapped due to the high risk of author spoofing, i.e.: - -- Alice mints an NFT using Bob's name and Alice's address -- Charlie does not check the address and instead relies on the provided name -- Charlie buys Alice's NFT while believing that it was created by Bob - -For this reason, smart contract developers SHOULD NOT add support for unverifiable information to the JSON document. We believe that the most secure way to provide complex authorship information (e.g. the name of the author) is to prove that the information is associated with the _author's address_, instead of with the NFT itself. - -### Replay Attack Resistance - -The chain id, the contract address and the token id uniquely identify an NFT; for this reason, there is no need to implement additional replay attack countermeasures (e.g. a nonce system). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5375.md diff --git a/EIPS/eip-5380.md b/EIPS/eip-5380.md index 320784e5252558..79d31cbc39a973 100644 --- a/EIPS/eip-5380.md +++ b/EIPS/eip-5380.md @@ -1,176 +1 @@ ---- -eip: 5380 -title: ERC-721 Entitlement Extension -description: Allows token owners to grant the ability for others to use specific properties of those tokens -author: Gavin John (@Pandapip1), Tim Daubenschütz (@TimDaub) -discussions-to: https://ethereum-magicians.org/t/pr-5380-eip-4907-alternative-design/10190 -status: Final -type: Standards Track -category: ERC -created: 2022-03-11 -requires: 165, 721, 1046 ---- - -## Abstract - -This EIP proposes a new interface that allows [ERC-721](./eip-721.md) token owners to grant limited usage of those tokens to other addresses. - -## Motivation - -There are many scenarios in which it makes sense for the owner of a token to grant certain properties to another address. One use case is renting tokens. If the token in question represents a trading card in an on-chain TCG (trading card game), one might want to be able to use that card in the game without having to actually buy it. Therefore, the owner might grant the renter the "property" of it being able to be played in the TCG. However, this property should only be able to be assigned to one person at a time, otherwise a contract could simply "rent" the card to everybody. If the token represents usage rights instead, the property of being allowed to use the associated media does not need such a restriction, and there is no reason that the property should be as scarce as the token. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Base - -Compliant entitlement contracts MUST implement the following Solidity interface: - -```solidity -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface ERC5380Entitlement is ERC165 { - /// @notice Emitted when the amount of entitlement a user has changes. If user is the zero address, then the user is the owner - event EntitlementChanged(address indexed user, address indexed contract, uint256 indexed tokenId); - - /// @notice Set the user associated with the given ERC-721 token as long as the owner is msg.sender. - /// @dev SHOULD NOT revert if the owner is not msg.sender. - /// @param user The user to grant the entitlement to - /// @param contract The property to grant - /// @param tokenId The tokenId to grant the properties of - function entitle(address user, address contract, uint256 tokenId) external; - - /// @notice Get the maximum number of users that can receive this entitlement - /// @param contract The contract to query - /// @param tokenId The tokenId to query - function maxEntitlements(address contract, uint256 tokenId) external view (uint256 max); - - /// @notice Get the user associated with the given contract and tokenId. - /// @dev Defaults to maxEntitlements(contract, tokenId) assigned to contract.ownerOf(tokenId) - /// @param user The user to query - /// @param contract The contract to query - /// @param tokenId The tokenId to query - function entitlementOf(address user, address contract, uint256 tokenId) external view returns (uint256 amt); -} -``` - -`supportsInterface` MUST return true when called with `ERC5380Entitlement`'s interface ID. - -### Enumerable Extension - -This OPTIONAL Solidity interface is RECOMMENDED. - -```solidity -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface ERC5380EntitlementEnumerable is ERC5380Entitlement { // Also implicitly supports ERC-165 - /// @notice Enumerate tokens with nonzero entitlement assigned to a user - /// @dev Throws if the index is out of bounds or if user == address(0) - /// @param user The user to query - /// @param index A counter - function entitlementOfUserByIndex(address user, uint256 index) external view returns (address contract, uint256 tokenId); -} -``` - -`supportsInterface` MUST return true when called with `ERC5380EntitlementEnumerable`'s interface ID. - -### Metadata Extension - -This OPTIONAL Solidity interface is RECOMMENDED. - -This extension uses [ERC-1046](./eip-1046.md) for `tokenURI` compatibility. - -```solidity -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface ERC5380EntitlementMetadata is ERC5380Entitlement { // Also implicitly supports ERC-165 - /// @notice ERC-1046 token URI - /// @dev See ERC-1046 and the metadata schema below - function tokenURI() external view returns (string); -} -``` - -`supportsInterface` MUST return true when called with `ERC5380EntitlementMetadata`'s interface ID. - -#### Interoperability Metadata Extension - -ERC-1046's `InteroperabilityMetadata` is extended with the following TypeScript interface: - -```typescript -/** - * ERC-5380's extension to ERC-1046's Interoperability metadata. - */ -interface ERC5380InteroperabilityMetadata is InteroperabilityMetadata { - /** - * This MUST be true if this is ERC-5380 Token Metadata, otherwise, this MUST be omitted. - * Setting this to true indicates to wallets that the address should be treated as an ERC-5380 entitlement. - **/ - erc5380?: boolean | undefined; -} -``` - -#### `tokenURI` Metadata Schema - -The resolved `tokenURI` data MUST conform to the following TypeScript interface: - -```typescript -/** - * ERC-5380 Asset Metadata - * Can be extended - */ -interface ERC5380TokenMetadata { - /** - * Interoperabiliy, to differentiate between different types of tokens and their corresponding URIs. - **/ - interop: ERC5380InteroperabilityMetadata; - - /** - * The name of the ERC-5380 token. - */ - name?: string; - - /** - * The symbol of the ERC-5380 token. - */ - symbol?: string; - - /** - * Provides a short one-paragraph description of the ERC-5380 token, without any markup or newlines. - */ - description?: string; - - /** - * One or more URIs each pointing to a resource with mime type `image/*` that represents this token. - * If an image is a bitmap, it SHOULD have a width between 320 and 1080 pixels - * Images SHOULD have an aspect ratio between 1.91:1 and 4:5 inclusive. - */ - images?: string[]; - - /** - * One or more URIs each pointing to a resource with mime type `image/*` that represent an icon for this token. - * If an image is a bitmap, it SHOULD have a width between 320 and 1080 pixels, and MUST have a height equal to its width - * Images MUST have an aspect ratio of 1:1, and use a transparent background - */ - icons?: string[]; -} -``` - -## Rationale - -[ERC-20](./eip-20.md) and [ERC-1155](./eip-1155.md) are unsupported as partial ownership is much more complex to track than boolean ownership. - -## Backwards Compatibility - -No backward compatibility issues were found. - -## Security Considerations - -The security considerations of [ERC-721](./eip-721.md) and [ERC-1046](./eip-1046.md) apply. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5380.md diff --git a/EIPS/eip-5409.md b/EIPS/eip-5409.md index a276e696cee28c..77b1f9ef332cb1 100644 --- a/EIPS/eip-5409.md +++ b/EIPS/eip-5409.md @@ -1,61 +1 @@ ---- -eip: 5409 -title: EIP-1155 Non-Fungible Token extension -description: Allow EIP-1155 to represent Non-Fungible Tokens (tokens who have a unique owner) -author: Ronan Sandford (@wighawag) -discussions-to: https://ethereum-magicians.org/t/eip-5409-non-fungible-token-extension-for-eip-1155/10240 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-07-23 -requires: 165, 721, 1155 ---- - -## Abstract - -This standard is an extension of [EIP-1155](./eip-1155.md). It proposes an additional function, `ownerOf`, which allows EIP-1155 tokens to support Non-Fungibility (unique owners). By implementing this extra function, EIP-1155 tokens can benefit from [EIP-721](./eip-721.md)'s core functionality without implementing the (less efficient) EIP-721 specification in the same contract. - -## Motivation - -Currently, EIP-1155 does not allow an external caller to detect whether a token is truly unique (can have only one owner) or fungible. This is because EIP-1155 do not expose a mechanism to detect whether a token will have its supply remain to be "1". Furthermore, it does not let an external caller retrieve the owner directly on-chain. - -The EIP-1155 specification does mention the use of split id to represent non-fungible tokens, but this requires a pre-established convention that is not part of the standard, and is not as simple as EIP-721's `ownerOf`. - -The ability to get the owner of a token enables novel use-cases, including the ability for the owner to associate data with it. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Contract Interface - -```solidity -interface IERC1155OwnerOf { - - /// @notice Find the owner of an NFT - /// @dev The zero address indicates that there is no owner: either the token does not exist or it is not an NFT (supply potentially bigger than 1) - /// @param tokenId The identifier for an NFT - /// @return The address of the owner of the NFT - function ownerOf(uint256 tokenId) external view returns (address); -} -``` - -The `ownerOf(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `supportsInterface` method MUST return `true` when called with `0x6352211e`. - -## Rationale - -`ownerOf` does not throw when a token does not exist (or does not have an owner). This simplifies the handling of such a case. Since it would be a security risk to assume all EIP-721 implementation would throw, it should not break compatibility with contract handling EIP-721 when dealing with this EIP-1155 extension. - -## Backwards Compatibility - -This EIP is fully backward compatible with EIP-1155. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5409.md diff --git a/EIPS/eip-5437.md b/EIPS/eip-5437.md index ee2feb3c789892..e87fa700b72606 100644 --- a/EIPS/eip-5437.md +++ b/EIPS/eip-5437.md @@ -1,154 +1 @@ ---- -eip: 5437 -title: Security Contact Interface -description: An interface for security notice using asymmetric encryption -author: Zainan Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-interface-for-security-contract/10303 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-09 -requires: 165 ---- - -## Abstract -An interface for security notice using asymmetric encryption. The interface exposes a asymmetric encryption key and a destination of delivery. - -## Motivation -Currently there is no consistent way to specify an official channel for security researchers to report security issues to smart contract maintainers. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -interface IEIP5437 { - - /// REQUIRED - function getSecurityContact(uint8 type, bytes memory data) public view - returns ( - uint8 type, - bytes memory publicKey, - bytes memory extraData - ); - - /// OPTIONAL - // TODO consider remove if not needed before finalized - function setSecurityContact( - uint8 type, - bytes memory publicKey, - bytes memory extraData) public; - event SecurityContactChanged(uint8 type, bytes memory publicKeyForEncryption, bytes memory extraData); - - /// OPTIONAL - function securityNotify(uint8 type, bytes memory data) public payable; - /// OPTIONAL - event OnSecurityNotification(uint8 type, bytes memory sourceData, uint256 value); - - /// OPTIONAL - // TODO consider to make it a separate EIP - function bountyPolicy(uint256 id) public view returns(string, bytes memory extraData); -} -``` - -1. Compliant interfaces MUST implement the `getSecurityContact` method. - -`type` is a one byte data with valid range of `[0x10, 0x7f]`. The ranges of `[0x00, 0x0f]` and `[0x80, 0xff]` are reserved for future extension. - -The `type` indicates the format of the `publicKey` and `extraData` in the following way - ------------------------------------------------------------------------------------------------- -| Type | Encryption scheme | extraData | --------|-------------------------------------|-------------------------------------------------- -| 0x10 | GnuPG - RSA/3072 | Email address(es) encoded in format of RFC 2822 | ------------------------------------------------------------------------------------------------- - -A new version of this table can be proposed by future EIPs by specifying a new `type` number. - -2. The `publicKey` returned from `getSecurityContact` MUST follow the encryption scheme specified -in the table above. - -The following is an example of a `publicKey` using `RSA/3072` generated via GnuPG in an RFC 20 ASCII-encoding of the public key string: - -```text ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQGNBGLzM2YBDADnCxAW/A0idvKNeQ6s/iYUeIIE+2mWmHcBGqLi0zrfz7pKWI+D -m6Hek51sg2c7ZlswPEp8KqANrj/CV1stXHF+KAZtYeFiAqpIZl1wtB6QgKYWGsJf -sXjBU3duLzLut2yvTfbEZsWAvrEaDjlXywdpboorHvfTE2vOvI6iGcjdh7PW7W7g -IGzlL6ukLGG7y9FUO2dSMjCR/tWMLCupnDDLN2cUHnfEnHZ34FMd61NxcHLC7cIk -P8xkFt8GCxURniTjqI5HAB8bGfR34kflVpr2+iKD5e+vQxcWK7vB443nruVf8osn -udDF8Z6mgl7bKBbGyYH58QsVlmZ8g3E4YaMKjpwOzEK3V2R8Yh4ETdr670ZCRrIz -QWVkibGgmQ3J/9RYps5Hfqpj4wV60Bsh1xUIJEIAs3ubMt7Z5JYFeze7VlXGlwot -P+SnAfKzlZT4CDEl2LEEDrbpnpOEdp0x9hYsEaXTxBGSpTDaxP2MyhW3u6pYeehG -oD0UVTLjWgU+6akAEQEAAbQjc29tZXJlYWxuYW1lIDxncGcubG9jYWwuZ2VuQHp6 -bi5pbT6JAdQEEwEIAD4WIQTDk/9jzRZ+lU2cY8rSVJNbud1lrQUCYvMzZgIbAwUJ -EswDAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRDSVJNbud1lraulDACqFbQg -e9hfoK17UcPVz/u4ZnwmFd9zFAWSYkGqrK9XMvz0R8pr7Y3Dp5hfvaptqID/lHhA -2oPEZ1ViIYDBcqG9WoWjCOYNoIosEAczrvf8YtUC2MHI+5DdYHtST74jDLuWMw3U -AbBXHds3KcRY5/j01kqqi4uwsMBCYyH3Jl3IwjKgy0KDBbuQakvaHPmNnt81ayvZ -ucdsNB9n/JMDxUWNCcySR+cllW4mk68pdiuK5qw0JMaoUjHFoWsgMTbFSlAV/lre -qu8MnrLSs5iPvvaJ3uDOuYROB2FsbvWxayfAAVS1iZf2vQFBJPnDwDdYoPNYMjLp -s2SfU02MVRGp3wanbtvM52uP42SLLNjBqUvJV03/QwfxCRejgAJOBn+iaOxP9NOe -qfQdKzYPbA9FohdkL9991n21XBZcZzAgF9RyU9IZAPAnwZyex1zfzJsUp/HrjhP8 -Ljs8MIcjIlmpLk66TmJte4dN5eML1bpohmfMX8k0ILESLSUhxEg1JBNYIDK5AY0E -YvMzZgEMALnIkONpqCkV+yaP8Tb8TBjmM+3TioJQROViINUQZh6lZM3/M+DPxAWZ -r0MIh1a3+o+ThlZ70tlS67w3Sjd62sWAFzALzW4F+gTqjBTh6LURDqDV8OXUrggA -SKK222aDP+Fr21h/TtPLeyDvcgm8Xvi4Cy7Jmf5CfT5jDio7a+FyFBNlTFSVqzLM -TgFOkUFBg8kJKvDjWIrS2fcTkELwZ8+IlQ52YbrXwbDar843x1fRmsY+x9nnuGuP -RYn1U4Jbptu2pEkG5q94jzUzTkGZHCzBJY7a8mtvS0mLqIE0Se1p+HFLY76Rma/F -HB6J4JNOTzBZ0/1FVvUOcMkjuZ2dX81qoCZ8NP6eafzKvNYZrGa5NJnjWO1ag5jQ -D8qHuOwxs8Fy9evmkwAVl51evLFNT532I4LK0zHSbF8MccZjpEFMSKwalKJn02Ml -yTd+ljYLf8SKMOLVps8kc4VyMR1lz0PwSpKDFOmkC1LRURpM7UTtCK+/RFg1OLyQ -SKBmdI37KQARAQABiQG8BBgBCAAmFiEEw5P/Y80WfpVNnGPK0lSTW7ndZa0FAmLz -M2YCGwwFCRLMAwAACgkQ0lSTW7ndZa2oFgv8DAxHtRZchTvjxtdLhQEUSHt80JCQ -zgHd7OUI9EU3K+oDj9AKtKZF1fqMlQoOskgBsLy/xpWwyhatv2ONLtHSjYDkZ7qs -jsXshqpuvJ3X00Yn9PXG1Z1jKl7rzy2/0DnQ8aFP+gktfu2Oat4uIu4YSqRsVW/Z -sbdTsW3T4E6Uf0qUKDf49mK3Y2nhTwY0YZqJnuQkSuUvpuM5a/4zSoaIRz+vSNjX -MoXUIK/f8UnWABPm90OCptTMTzXCC1UXEHTNm6iBJThFiq3GeLZH+GnIola5KLO1 -+YbsFEchLfLZ27pWGfIbyppvsuQmrHef+J3g6sXybOWDHVYr3Za1fzxQVIbwoIEe -ndKG0bu7ZAi2b/c8uH/wHT5IvtfzHLeSTjDqG8UyLTnaDxHQZIE9JIzWSQ1DSoNC -YrU7CQtL+/HRpiGFHfClaXln8VWkjnUvp+Fg1ZPtE1t/SKddZ7m29Hd9nzUc0OQW -MOA+HDqgA3a9kWbQKSloORq4unft1eu/FCra -=O6Bf ------END PGP PUBLIC KEY BLOCK----- -``` - -3. IF `setSecurityContact` is implemented and a call to it has succeeded in setting a new security contact, an event `SecurityContactChanged` MUST be emitted with the identical passed-in-parameters of `setSecurityContact` - -4. It's also RECOMMENDED that an on-chain security notify method `securityNotify` -to implemented to receive security notice onchain. If it's implemented and a call -has succeeded, it MUST emit an `OnSecurityNotification` with identical pass-in-parameter data. - -5. Compliant interfaces MUST implement [EIP-165](./eip-165.md). - - - -6. It's recommended to set a bounty policy via `bountyPolicy` method. The `id = 0` is preserved for a full overview, while other digits are used for different individual bounty policies. The returned -string will be URI to content of bounty policies. -No particular format of bounty policy is specified. - -## Rationale -1. For simplicity, this EIP specifies a simple GPG scheme with a given encryption scheme and uses email addresses as a contact method. It's possible that future EIPs will specify new encryption schemes or delivery methods. -2. This EIP adds an optional method, `setSecurityContact`, to set the security contact, because it might change due to circumstances such as the expiration of the cryptographic keys. -3. This EIP explicitly marks `securityNotify` as `payable`, in order to allow implementers to set a staking amount to report a security vulnerability. -4. This EIP allows for future expansion by adding the `bountyPolicy` the `extraData` fields. Additional values of these fields may be added in future EIPs. - -## Backwards Compatibility -Currently, existing solutions such as OpenZeppelin use plaintext in source code - -```solidity -/// @custom:security-contact some-user@some-domain.com -``` - -It's recommend that new versions of smart contracts adopt this EIP in addition to the legacy `@custom:security-contact` approach. - -## Security Considerations - -Implementors should properly follow security practices required by the encryption scheme to ensure the security of the chosen communication channel. Some best practices are as follows: - -1. Keep security contact information up-to-date; -2. Rotate encryption keys in the period recommended by best practice; -3. Regularly monitor the channel to receive notices in a timely manner. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5437.md diff --git a/EIPS/eip-5453.md b/EIPS/eip-5453.md index 70305a3162f001..0deb7e85a84417 100644 --- a/EIPS/eip-5453.md +++ b/EIPS/eip-5453.md @@ -1,340 +1 @@ ---- -eip: 5453 -title: Endorsement - Permit for Any Functions -description: A general protocol for approving function calls in the same transaction rely on ERC-5750. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-5453-endorsement-standard/10355 -status: Last Call -last-call-deadline: 2023-09-27 -type: Standards Track -category: ERC -created: 2022-08-12 -requires: 165, 712, 1271, 5750 ---- - -## Abstract - -This EIP establish a general protocol for permitting approving function calls in the same transaction rely on [ERC-5750](./eip-5750.md). -Unlike a few prior art ([ERC-2612](./eip-2612.md) for [ERC-20](./eip-20.md), `ERC-4494` for [ERC-721](./eip-721.md) that -usually only permit for a single behavior (`transfer` for ERC-20 and `safeTransferFrom` for ERC-721) and a single approver in two transactions (first a `permit(...)` TX, then a `transfer`-like TX), this EIP provides a way to permit arbitrary behaviors and aggregating multiple approvals from arbitrary number of approvers in the same transaction, allowing for Multi-Sig or Threshold Signing behavior. - -## Motivation - -1. Support permit(approval) alongside a function call. -2. Support a second approval from another user. -3. Support pay-for-by another user -4. Support multi-sig -5. Support persons acting in concert by endorsements -6. Support accumulated voting -7. Support off-line signatures - -## 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. - -### Interfaces - -The interfaces and structure referenced here are as followed - -```solidity -pragma solidity ^0.8.9; - -struct ValidityBound { - bytes32 functionParamStructHash; - uint256 validSince; - uint256 validBy; - uint256 nonce; -} - -struct SingleEndorsementData { - address endorserAddress; // 32 - bytes sig; // dynamic = 65 -} - -struct GeneralExtensionDataStruct { - bytes32 erc5453MagicWord; - uint256 erc5453Type; - uint256 nonce; - uint256 validSince; - uint256 validBy; - bytes endorsementPayload; -} - -interface IERC5453EndorsementCore { - function eip5453Nonce(address endorser) external view returns (uint256); - function isEligibleEndorser(address endorser) external view returns (bool); -} - -interface IERC5453EndorsementDigest { - function computeValidityDigest( - bytes32 _functionParamStructHash, - uint256 _validSince, - uint256 _validBy, - uint256 _nonce - ) external view returns (bytes32); - - function computeFunctionParamHash( - string memory _functionName, - bytes memory _functionParamPacked - ) external view returns (bytes32); -} - -interface IERC5453EndorsementDataTypeA { - function computeExtensionDataTypeA( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address endorserAddress, - bytes calldata sig - ) external view returns (bytes memory); -} - - -interface IERC5453EndorsementDataTypeB { - function computeExtensionDataTypeB( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address[] calldata endorserAddress, - bytes[] calldata sigs - ) external view returns (bytes memory); -} -``` - -See [`IERC5453.sol`](../assets/eip-5453/IERC5453.sol). - -### Behavior specification - -As specified in [ERC-5750 General Extensibility for Method Behaviors](./eip-5750.md), any compliant method that has an `bytes extraData` as its -last method designated for extending behaviors can conform to [ERC-5453](./eip-5453.md) as the way to indicate a permit from certain user. - -1. Any compliant method of this EIP MUST be a [ERC-5750](./eip-5750.md) compliant method. -2. Caller MUST pass in the last parameter `bytes extraData` conforming a solidity memory encoded layout bytes of `GeneralExtensonDataStruct` specified in _Section Interfaces_. The following descriptions are based on when decoding `bytes extraData` into a `GeneralExtensonDataStruct` -3. In the `GeneralExtensonDataStruct`-decoded `extraData`, caller MUST set the value of `GeneralExtensonDataStruct.erc5453MagicWord` to be the `keccak256("ERC5453-ENDORSEMENT")`. -4. Caller MUST set the value of `GeneralExtensonDataStruct.erc5453Type` to be one of the supported values. - -```solidity -uint256 constant ERC5453_TYPE_A = 1; -uint256 constant ERC5453_TYPE_B = 2; -``` - -5. When the value of `GeneralExtensonDataStruct.erc5453Type` is set to be `ERC5453_TYPE_A`, `GeneralExtensonDataStruct.endorsementPayload` MUST be abi encoded bytes of a `SingleEndorsementData`. -6. When the value of `GeneralExtensonDataStruct.erc5453Type` is set to be `ERC5453_TYPE_B`, `GeneralExtensonDataStruct.endorsementPayload` MUST be abi encoded bytes of `SingleEndorsementData[]` (a dynamic array). - -7. Each `SingleEndorsementData` MUST have a `address endorserAddress;` and a 65-bytes `bytes sig` signature. - -8. Each `bytes sig` MUST be an ECDSA (secp256k1) signature using private key of signer whose corresponding address is `endorserAddress` signing `validityDigest` which is the a hashTypeDataV4 of [EIP-712](./eip-712.md) of hashStruct of `ValidityBound` data structure as followed: - -```solidity -bytes32 validityDigest = - eip712HashTypedDataV4( - keccak256( - abi.encode( - keccak256( - "ValidityBound(bytes32 functionParamStructHash,uint256 validSince,uint256 validBy,uint256 nonce)" - ), - functionParamStructHash, - _validSince, - _validBy, - _nonce - ) - ) - ); -``` - -9. The `functionParamStructHash` MUST be computed as followed - -```solidity - bytes32 functionParamStructHash = keccak256( - abi.encodePacked( - keccak256(bytes(_functionStructure)), - _functionParamPacked - ) - ); - return functionParamStructHash; -``` - -whereas - -- `_functionStructure` MUST be computed as `function methodName(type1 param1, type2 param2, ...)`. -- `_functionParamPacked` MUST be computed as `enc(param1) || enco(param2) ...` - -10. Upon validating that `endorserAddress == ecrecover(validityDigest, signature)` or `EIP1271(endorserAddress).isValidSignature(validityDigest, signature) == ERC1271.MAGICVALUE`, the single endorsement MUST be deemed valid. -11. Compliant method MAY choose to impose a threshold for a number of endorsements needs to be valid in the same `ERC5453_TYPE_B` kind of `endorsementPayload`. - -12. The `validSince` and `validBy` are both inclusive. Implementer MAY choose to use blocknumber or timestamp. Implementor SHOULD find away to indicate whether `validSince` and `validBy` is blocknumber or timestamp. - -## Rationale - -1. We chose to have both `ERC5453_TYPE_A`(single-endorsement) and `ERC5453_TYPE_B`(multiple-endorsements, same nonce for entire contract) so we -could balance a wider range of use cases. E.g. the same use cases of ERC-2612 and `ERC-4494` can be supported by `ERC5453_TYPE_A`. And threshold approvals can be done via `ERC5453_TYPE_B`. More complicated approval types can also be extended by defining new `ERC5453_TYPE_?` - -2. We chose to include both `validSince` and `validBy` to allow maximum flexibility in expiration. This can be also be supported by EVM natively at if adopted `ERC-5081` but `ERC-5081` will not be adopted anytime soon, we choose to add these two numbers in our protocol to allow -smart contract level support. - -## Backwards Compatibility - -The design assumes a `bytes calldata extraData` to maximize the flexibility of future extensions. This assumption is compatible with [ERC-721](eip-721.md), [ERC-1155](eip-1155.md) and many other ERC-track EIPs. Those that aren't, such as [ERC-20](./eip-20.md), can also be updated to support it, such as using a wrapper contract or proxy upgrade. - -## Reference Implementation - -In addition to the specified algorithm for validating endorser signatures, we also present the following reference implementations. - -```solidity -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; - -import "./IERC5453.sol"; - -abstract contract AERC5453Endorsible is EIP712, - IERC5453EndorsementCore, IERC5453EndorsementDigest, IERC5453EndorsementDataTypeA, IERC5453EndorsementDataTypeB { - // ... - - function _validate( - bytes32 msgDigest, - SingleEndorsementData memory endersement - ) internal virtual { - require( - endersement.sig.length == 65, - "AERC5453Endorsible: wrong signature length" - ); - require( - SignatureChecker.isValidSignatureNow( - endersement.endorserAddress, - msgDigest, - endersement.sig - ), - "AERC5453Endorsible: invalid signature" - ); - } - // ... - - modifier onlyEndorsed( - bytes32 _functionParamStructHash, - bytes calldata _extensionData - ) { - require(_isEndorsed(_functionParamStructHash, _extensionData)); - _; - } - - function computeExtensionDataTypeB( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address[] calldata endorserAddress, - bytes[] calldata sigs - ) external pure override returns (bytes memory) { - require(endorserAddress.length == sigs.length); - SingleEndorsementData[] - memory endorsements = new SingleEndorsementData[]( - endorserAddress.length - ); - for (uint256 i = 0; i < endorserAddress.length; ++i) { - endorsements[i] = SingleEndorsementData( - endorserAddress[i], - sigs[i] - ); - } - return - abi.encode( - GeneralExtensionDataStruct( - MAGIC_WORLD, - ERC5453_TYPE_B, - nonce, - validSince, - validBy, - abi.encode(endorsements) - ) - ); - } -} - -``` - -See [`AERC5453.sol`](../assets/eip-5453/AERC5453.sol) - -### Reference Implementation of `EndorsableERC721` - -Here is a reference implementation of `EndorsableERC721` that achieves similar behavior of `ERC-4494`. - -```solidity -pragma solidity ^0.8.9; - -contract EndorsableERC721 is ERC721, AERC5453Endorsible { - //... - - function mint( - address _to, - uint256 _tokenId, - bytes calldata _extraData - ) - external - onlyEndorsed( - _computeFunctionParamHash( - "function mint(address _to,uint256 _tokenId)", - abi.encode(_to, _tokenId) - ), - _extraData - ) - { - _mint(_to, _tokenId); - } -} -``` - -See [`EndorsableERC721.sol`](../assets/eip-5453/EndorsableERC721.sol) - -### Reference Implementation of `ThresholdMultiSigForwarder` - -Here is a reference implementation of ThresholdMultiSigForwarder that achieves similar behavior of multi-sig threshold approval -remote contract call like a Gnosis-Safe wallet. - -```solidity -pragma solidity ^0.8.9; - -contract ThresholdMultiSigForwarder is AERC5453Endorsible { - //... - function forward( - address _dest, - uint256 _value, - uint256 _gasLimit, - bytes calldata _calldata, - bytes calldata _extraData - ) - external - onlyEndorsed( - _computeFunctionParamHash( - "function forward(address _dest,uint256 _value,uint256 _gasLimit,bytes calldata _calldata)", - abi.encode(_dest, _value, _gasLimit, keccak256(_calldata)) - ), - _extraData - ) - { - string memory errorMessage = "Fail to call remote contract"; - (bool success, bytes memory returndata) = _dest.call{value: _value}( - _calldata - ); - Address.verifyCallResult(success, returndata, errorMessage); - } - -} - -``` - -See [`ThresholdMultiSigForwarder.sol`](../assets/eip-5453/ThresholdMultiSigForwarder.sol) - -## Security Considerations - -### Replay Attacks - -A replay attack is a type of attack on cryptography authentication. In a narrow sense, it usually refers to a type of attack that circumvents the cryptographically signature verification by reusing an existing signature for a message being signed again. Any implementations relying on this EIP must realize that all smart endorsements described here are cryptographic signatures that are _public_ and can be obtained by anyone. They must foresee the possibility of a replay of the transactions not only at the exact deployment of the same smart contract, but also other deployments of similar smart contracts, or of a version of the same contract on another `chainId`, or any other similar attack surfaces. The `nonce`, `validSince`, and `validBy` fields are meant to restrict the surface of attack but might not fully eliminate the risk of all such attacks, e.g. see the [Phishing](#phishing) section. - -### Phishing - -It's worth pointing out a special form of replay attack by phishing. An adversary can design another smart contract in a way that the user be tricked into signing a smart endorsement for a seemingly legitimate purpose, but the data-to-designed matches the target application - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5453.md diff --git a/EIPS/eip-5484.md b/EIPS/eip-5484.md index a885f529885651..44d7a1e926d4a0 100644 --- a/EIPS/eip-5484.md +++ b/EIPS/eip-5484.md @@ -1,116 +1 @@ ---- -eip: 5484 -title: Consensual Soulbound Tokens -description: Interface for special NFTs with immutable ownership and pre-determined immutable burn authorization -author: Buzz Cai (@buzzcai) -discussions-to: https://ethereum-magicians.org/t/eip-5484-consensual-soulbound-tokens/10424 -status: Final -type: Standards Track -category: ERC -created: 2022-08-17 -requires: 165, 721 ---- - - -## Abstract - -This EIP defines an interface extending [EIP-721](./eip-721.md) to create soulbound tokens. Before issuance, both parties (the issuer and the receiver), have to agree on who has the authorization to burn this token. Burn authorization is immutable after declaration. After its issuance, a soulbound token can't be transferred, but can be burned based on a predetermined immutable burn authorization. - -## Motivation - -The idea of soulbound tokens has gathered significant attention since its publishing. Without a standard interface, however, soulbound tokens are incompatible. It is hard to develop universal services targeting at soulbound tokens without minimal consensus on the implementation of the tokens. - -This EIP envisions soulbound tokens as specialized NFTs that will play the roles of credentials, credit records, loan histories, memberships, and many more. In order to provide the flexibility in these scenarios, soulbound tokens must have an application-specific burn authorization and a way to distinguish themselves from regular EIP-721 tokens. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -- The token MUST implement the following interfaces: - - 1. [EIP-165](./eip-165.md)’s `ERC165` (`0x01ffc9a7`) - 1. [EIP-721](./eip-721.md)’s `ERC721` (`0x80ac58cd`) - -- `burnAuth` SHALL be presented to receiver before issuance. -- `burnAuth` SHALL be Immutable after issuance. -- `burnAuth` SHALL be the sole factor that determines which party has the rights to burn token. -- The issuer SHALL present token metadata to the receiver and acquire receiver's signature before issuance. -- The issuer SHALL NOT change metadata after issuance. - -/// Note: the EIP-165 identifier for this interface is 0x0489b56f - -### Contract Interface - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5484 { - /// A guideline to standardlize burn-authorization's number coding - enum BurnAuth { - IssuerOnly, - OwnerOnly, - Both, - Neither - } - - /// @notice Emitted when a soulbound token is issued. - /// @dev This emit is an add-on to nft's transfer emit in order to distinguish sbt - /// from vanilla nft while providing backward compatibility. - /// @param from The issuer - /// @param to The receiver - /// @param tokenId The id of the issued token - event Issued ( - address indexed from, - address indexed to, - uint256 indexed tokenId, - BurnAuth burnAuth - ); - - /// @notice provides burn authorization of the token id. - /// @dev unassigned tokenIds are invalid, and queries do throw - /// @param tokenId The identifier for a token. - function burnAuth(uint256 tokenId) external view returns (BurnAuth); -} -``` - -## Rationale - -### Soulbound Token (SBTs) as an extension to EIP-721 - -We believe that soulbound token serves as a specialized subset of the existing EIP-721 tokens. The advantage of such design is seamless compatibility of soulbound token with existing NFT services. Service providers can treat SBTs like NFTs and do not need to make drastic changes to their existing codebase. - -### Non-Transferable - -One problem with current soulbound token implementations that extend from [EIP-721](./eip-721.md) is that all transfer implementations throw errors. A much cleaner approach would be for transfer functions to still throw, but also enable third parties to check beforehand if the contract implements the soulbound interface to avoid calling transfer. - -### Burn Authorization - -We want maximum freedom when it comes to interface usage. A flexible and predetermined rule to burn is crucial. Here are some sample scenarios for different burn authorizations: - -- `IssuerOnly`: Loan record -- `ReceiverOnly`: Paid membership -- `Both`: Credentials -- `Neither`: Credit history - -Burn authorization is tied to specific tokens and immutable after issuance. It is therefore important to inform the receiver and gain receiver's consent before the token is issued. - -### Issued Event - -On issuing, an `Issued` event will be emitted alongside [EIP-721](./eip-721.md)'s `Transfer` event. This design keeps backward compatibility while giving clear signals to thrid-parties that this is a soulBound token issuance event. - -### Key Rotations - -A concern Ethereum users have is that soulbound tokens having immutable ownership discourage key rotations. This is a valid concern. Having a burnable soulbound token, however, makes key rotations achievable. The owner of the soulbound token, when in need of key rotations, can inform the issuer of the token. Then the party with burn authorization can burn the token while the issuer can issue a replica to the new address. - -## Backwards Compatibility - -This proposal is fully backward compatible with [EIP-721](./eip-721.md) - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5484.md diff --git a/EIPS/eip-5485.md b/EIPS/eip-5485.md index 03878d5f2d95f2..6a56a1e40630fc 100644 --- a/EIPS/eip-5485.md +++ b/EIPS/eip-5485.md @@ -1,95 +1 @@ ---- -eip: 5485 -title: Legitimacy, Jurisdiction and Sovereignty -description: An interface for identifying the legitimacy, jurisdiction and sovereignty. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-5485-interface-for-legitimacy-jurisdiction-and-sovereignty/10425 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-17 -requires: 165, 5247 ---- - -## Abstract - -Provide a way for compliant smart contracts to declare their legitimacy lineage, jurisdiction they observe, and sovereignty if they choose to not fall onto any jurisdiction. - -## Motivation - -Today, smart contracts have no standard way to specify their legitimacy lineage, jurisdiction, or sovereignty relationship. The introduction of such a standard, supports better integration with today's legal and regulative scenarios: - -1. it supports a regulative body to allow or deny interoperability with smart contracts. -2. it also allows DAOs to clearly declare "self-sovereignty" by announcing via this interface by saying they do not assert legitimacy from any source other than themselves. - -A real-world example is that ContractA represents an **A company registered in a country**, ContractB represents a **The Secretary of State of the country**, and ContractC represents the **Supreme Court of the Country**. - -Another real example is a contract that declares "self-sovereignty" that doesn't follow any jurisdiction. - -This interface supports both cases, providing a way to allow smart contracts to determine if they want to allow/prohibit interaction based on sovereignty. - -For example, a country might want to require any digital money service's all smart contracts to observe their [ERC-5485](./eip-5485.md) jurisdiction before they are allowed to operate money in their (real world) legal jurisdiction. - -Another real world use-case is that in some jurisdiction e.g. in United States, if an token issuer choose to issue a token, -they can try to petition SEC to recognize their token as registered security, if approved, will gain legitimacy from SEC. -Should they choose to petition Commodity Futures Trading Commission (CFTC) to recognize them as a commodity, if approved, will -gain legitimacy from CFTC. - -On the other hand, a DAO with a strong decentralized ideology might choose to only inter-operate with EOA or "self-sovereign" smart contracts to avoid being affected by any country. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -1. Compliant contract MUSTS implement the following interface. - -```solidity -interface IERC5485 { - /// A function that returns the source of legitimacy in an address - /// - /// @returns address of EOA or contract that grants legitimacy status to this contract. - /// See Specification #2 and #3 for more details. - function sourceOfLegitimacy() public view returns (address); - - /// A function that returns the sovereign jurisdiction in an address - /// - /// @returns address of EOA or contract that *ultimately* grants legitimacy status (root of legitimacy lineage). - /// See Specification #4 and #4 for more details. - function jurisdiction() public view returns (address); -} -``` - -2. Suppose a compliant `ContractA` is deployed at address `0x` and gains its legitimacy from another contract or External Owned Account (EOA) at `0x`. In this case, it MUST return the `0x` when being queried for `sourceOfLegitimacy()`. For example. If `ContractA` gains legitimacy from `ContractB`, `ContractB` gains legitimacy from `ContractC` but `ContractC` doesn't gain legitimacy from anyone, ContractC is considered the "jurisdiction" that both `ContractB` and `ContractA` observe. Both `ContractA` and `ContractB` MUST return the address of `ContractC` as the result of jurisdiction. - -3. A contract that matches ANY of the following cases is considered a "sovereign contract": - -- Case 1: it does not implement this EIP -- Case 2: it implements this EIP but return its *own address*. -- Case 3: it implements this EIP but return a ZERO address. - -4. Any compliant contract MUST return its legitimacy sources chain up to the sovereign contract when queried by `jurisdiction()`. - -5. If ContractA gains legitimacy from ContractB, ContractA MUST observe the ContractB's order to ANY of the following -- Order Type 1: execute a `selfdestruct` of ContractA - -6. If ContractA observes jurisdiction of ContractC (a sovereignty), ContractA MUST observe ContractC's order to ANY the following -- Order Type 1: execute a `selfdestruct` of ContractA -- Order Type 2: execute ANY smart contract executable proposals as specified in [ERC-5247](./eip-5247.md) - -TODO determine whether to address "add/remove legitimacy" in this EIP or leave it as a future EIP - -## Rationale - -Needs discussion. - -## Backwards Compatibility - -Needs discussion. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5485.md diff --git a/EIPS/eip-5489.md b/EIPS/eip-5489.md index aec096b66bc551..edf2247fdb996f 100644 --- a/EIPS/eip-5489.md +++ b/EIPS/eip-5489.md @@ -1,172 +1 @@ ---- -eip: 5489 -title: NFT Hyperlink Extension -description: NFT Hyperlink Extension embeds hyperlinks onto NFTs, allowing users to click any hNFT and be transported to any url set by the owner. -author: IronMan_CH (@coderfengyun) -discussions-to: https://ethereum-magicians.org/t/eip-5489-nft-hyperlink-extension/10431 -status: Final -type: Standards Track -category: ERC -created: 2022-08-16 -requires: 165, 721 ---- - -## Abstract - -This EIP proposes a new extension for NFTs (non-fungible token, aka [EIP-721](./eip-721.md)): nft-hyperlink-extention (hNFT), embedding NFTs with hyperlinks, referred to as “hNFTs”. As owners of hNFTs, users may authorize a URL slot to a specific address which can be either an externally-owned account (EOA) or a contract address and hNFT owners are entitled to revoke that authorization at any time. The address which has slot authorization can manage the URL of that slot. - - -## Motivation - -As NFTs attract more attention, they have the potential to become the primary medium of Web3. Currently, end users can’t attach rich texts, videos, or images to NFTs, and there’s no way to render these rich-content attachments. Many industries eagerly look forward to this kind of rich-content attachment ability. Attaching, editing, and displaying highly customized information can usefully be standardized. - -This EIP uses hyperlinks as the aforementioned form of “highly customized attachment on NFT”, and also specifies how to attach, edit, and display these attachments on NFTs. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Interface - -#### `IERC5489` - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5489 { - /** - * @dev this event emits when the slot on `tokenId` is authorzized to `slotManagerAddr` - */ - event SlotAuthorizationCreated(uint256 indexed tokenId, address indexed slotManagerAddr); - - /** - * @dev this event emits when the authorization on slot `slotManagerAddr` of token `tokenId` is revoked. - * So, the corresponding DApp can handle this to stop on-going incentives or rights - */ - event SlotAuthorizationRevoked(uint256 indexed tokenId, address indexed slotManagerAddr); - - /** - * @dev this event emits when the uri on slot `slotManagerAddr` of token `tokenId` has been updated to `uri`. - */ - event SlotUriUpdated(uint256 indexed tokenId, address indexed slotManagerAddr, string uri); - - /** - * @dev - * Authorize a hyperlink slot on `tokenId` to address `slotManagerAddr`. - * Indeed slot is an entry in a map whose key is address `slotManagerAddr`. - * Only the address `slotManagerAddr` can manage the specific slot. - * This method will emit SlotAuthorizationCreated event - */ - function authorizeSlotTo(uint256 tokenId, address slotManagerAddr) external; - - /** - * @dev - * Revoke the authorization of the slot indicated by `slotManagerAddr` on token `tokenId` - * This method will emit SlotAuthorizationRevoked event - */ - function revokeAuthorization(uint256 tokenId, address slotManagerAddr) external; - - /** - * @dev - * Revoke all authorizations of slot on token `tokenId` - * This method will emit SlotAuthorizationRevoked event for each slot - */ - function revokeAllAuthorizations(uint256 tokenId) external; - - /** - * @dev - * Set uri for a slot on a token, which is indicated by `tokenId` and `slotManagerAddr` - * Only the address with authorization through {authorizeSlotTo} can manipulate this slot. - * This method will emit SlotUriUpdated event - */ - function setSlotUri( - uint256 tokenId, - string calldata newUri - ) external; - - /** - * @dev Throws if `tokenId` is not a valid NFT. URIs are defined in RFC 3986. - * The URI MUST point to a JSON file that conforms to the "EIP5489 Metadata JSON schema". - * - * returns the latest uri of an slot on a token, which is indicated by `tokenId`, `slotManagerAddr` - */ - function getSlotUri(uint256 tokenId, address slotManagerAddr) - external - view - returns (string memory); -} -``` - -The `authorizeSlotTo(uint256 tokenId, address slotManagerAddr)` function MAY be implemented as public or external. - -The `revokeAuthorization(uint256 tokenId, address slotManagerAddr)` function MAY be implemented as public or external. - -The `revokeAllAuthorizations(uint256 tokenId)` function MAY be implemented as public or external. - -The `setSlotUri(uint256 tokenId, string calldata newUri)` function MAY be implemented as public or external. - -The `getSlotUri(uint256 tokenId, address slotManagerAddr)` function MAY be implemented as pure or view. - -The `SlotAuthorizationCreated` event MUST be emitted when a slot is authorized to an address. - -The `SlotAuthorizationRevoked` event MUST be emitted when a slot authorization is revoked. - -The `SlotUriUpdated` event MUSt be emitted when a slot's URI is changed. - -The `supportInterface` method MUST return true when called with `0x8f65987b`. - -### Authentication - -The `authorizeSlotTo`, `revokeAuthorization`, and `revokeAllAuthorizations` functions are authenticated if and only if the message sender is the owner of the token. - -### Metadata JSON schema - -```json -{ - "title": "AD Metadata", - "type": "object", - "properties": { - "icon": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the slot's occupier. Consider making any images at a width between 48 and 1080 pixels and aspect ration between 1.91:1 and 4:5 inclusive. Suggest to show this as an thumbnail of the target resource" - }, - "description": { - "type": "string", - "description": "A paragraph which briefly introduce what is the target resource" - }, - "target": { - "type": "string", - "description": "A URI pointing to target resource, sugguest to follow 30X status code to support more redirections, the mime type and content rely on user's setting" - } - } -} -``` - -## Rationale - -### Extends NFT with hyperlinks - -URIs are used to represent the value of slots to ensure enough flexibility to deal with different use cases. - -### Authorize slot to address - -We use addresses to represent the key of slots to ensure enough flexibility to deal with all use cases. - -## Backwards Compatibility - -As mentioned in the specifications section, this standard can be fully EIP-721 compatible by adding an extension function set. - -In addition, new functions introduced in this standard have many similarities with the existing functions in EIP-721. This allows developers to easily adopt the standard quickly. - -## Reference Implementation - -You can find an implementation of this standard in [`ERC5489.sol`](../assets/eip-5489/contracts/ERC5489.sol). - -## Security Considerations - -No security considerations were found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5489.md diff --git a/EIPS/eip-5496.md b/EIPS/eip-5496.md index ea436746269521..1767f1307f8507 100644 --- a/EIPS/eip-5496.md +++ b/EIPS/eip-5496.md @@ -1,254 +1 @@ ---- -eip: 5496 -title: Multi-privilege Management NFT Extension -description: Create shareable multi-privilege NFTs for EIP-721 -author: Jeremy Z (@wnft) -discussions-to: https://ethereum-magicians.org/t/eip-5496-multi-privilege-management-extension-for-erc-721/10427 -status: Last Call -last-call-deadline: 2022-11-29 -type: Standards Track -category: ERC -created: 2022-07-30 -requires: 721 ---- - - -## Abstract - -This EIP defines an interface extending [EIP-721](./eip-721.md) to provide shareable multi-privileges for NFTs. Privileges may be on-chain (voting rights, permission to claim an airdrop) or off-chain (a coupon for an online store, a discount at a local restaurant, access to VIP lounges in airports). Each NFT may contain many privileges, and the holder of a privilege can verifiably transfer that privilege to others. Privileges may be non-shareable or shareable. Shareable privileges can be cloned, with the provider able to adjust the details according to the spreading path. Expiration periods can also be set for each privilege. - -## Motivation - -This standard aims to efficiently manage privileges attached to NFTs in real-time. Many NFTs have functions other than just being used as profile pictures or art collections, they may have real utilities in different scenarios. For example, a fashion store may give a discount for its own NFT holders; a DAO member NFT holder can vote for the proposal of how to use their treasury; a dApp may create an airdrop event to attract a certain group of people like some blue chip NFT holders to claim; the grocery store can issue its membership card on chain (as an NFT) and give certain privileges when the members shop at grocery stores, etc. There are cases when people who own NFTs do not necessarily want to use their privileges. By providing additional data recording different privileges a NFT collection has and interfaces to manage them, users can transfer or sell privileges without losing their ownership of the NFT. - -[EIP-721](./eip-721.md) only records the ownership and its transfer, the privileges of an NFT are not recorded on-chain. This extension would allow merchants/projects to give out a certain privilege to a specified group of people, and owners of the privileges can manage each one of the privileges independently. This facilitates a great possibility for NFTs to have real usefulness. - -For example, an airline company issues a series of [EIP-721](./eip-721.md)/[EIP-1155](./eip-1155.md) tokens to Crypto Punk holders to give them privileges, in order to attract them to join their club. However, since these tokens are not bound to the original NFT, if the original NFT is transferred, these privileges remain in the hands of the original holders, and the new holders cannot enjoy the privileges automatically. -So, we propose a set of interfaces that can bind the privileges to the underlying NFT, while allowing users to manage the privileges independently. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Every contract complying with this standard MUST implement the `IERC5496` interface. The **shareable multi-privilege extension** is OPTIONAL for EIP-721 contracts. - -```solidity -/// @title multi-privilege extension for EIP-721 -/// Note: the EIP-165 identifier for this interface is 0x076e1bbb -interface IERC5496{ - /// @notice Emitted when `owner` changes the `privilege holder` of a NFT. - event PrivilegeAssigned(uint256 tokenId, uint256 privilegeId, address user, uint256 expires); - /// @notice Emitted when `contract owner` changes the `total privilege` of the collection - event PrivilegeTotalChanged(uint256 newTotal, uint256 oldTotal); - - /// @notice set the privilege holder of a NFT. - /// @dev expires should be less than 30 days - /// Throws if `msg.sender` is not approved or owner of the tokenId. - /// @param tokenId The NFT to set privilege for - /// @param privilegeId The privilege to set - /// @param user The privilege holder to set - /// @param expires For how long the privilege holder can have - function setPrivilege(uint256 tokenId, uint256 privilegeId, address user, uint256 expires) external; - - /// @notice Return the expiry timestamp of a privilege - /// @param tokenId The identifier of the queried NFT - /// @param privilegeId The identifier of the queried privilege - /// @return Whether a user has a certain privilege - function privilegeExpires(uint256 tokenId, uint256 privilegeId) external view returns(uint256); - - /// @notice Check if a user has a certain privilege - /// @param tokenId The identifier of the queried NFT - /// @param privilegeId The identifier of the queried privilege - /// @param user The address of the queried user - /// @return Whether a user has a certain privilege - function hasPrivilege(uint256 tokenId, uint256 privilegeId, address user) external view returns(bool); -} -``` - -Every contract implementing this standard SHOULD set a maximum privilege number before setting any privilege, the `privilegeId` MUST NOT be greater than the maximum privilege number. - -The `PrivilegeAssigned` event MUST be emitted when `setPrivilege` is called. - -The `PrivilegeTotalChanged` event MUST be emitted when the `total privilege` of the collection is changed. - -The `supportsInterface` method MUST return `true` when called with `0x076e1bbb`. - -```solidity -/// @title Cloneable extension - Optional for EIP-721 -interface IERC721Cloneable { - /// @notice Emitted when set the `privilege ` of a NFT cloneable. - event PrivilegeCloned(uint tokenId, uint privId, address from, address to); - - /// @notice set a certain privilege cloneable - /// @param tokenId The identifier of the queried NFT - /// @param privilegeId The identifier of the queried privilege - /// @param referrer The address of the referrer - /// @return Whether the operation is successful or not - function clonePrivilege(uint tokenId, uint privId, address referrer) external returns (bool); -} -``` - -The `PrivilegeCloned` event MUST be emitted when `clonePrivilege` is called. - -For Compliant contract, it is RECOMMENDED to use [EIP-1271](./eip-1271.md) to validate the signatures. - -## Rationale - -### Shareable Privileges - -The number of privilege holders is limited by the number of NFTs if privileges are non-shareable. A shareable privilege means the original privilege holder can copy the privilege and give it to others, not transferring his/her own privilege to them. This mechanism greatly enhances the spread of privileges as well as the adoption of NFTs. - -### Expire Date Type - -The expiry timestamp of a privilege is a timestamp and stored in `uint256` typed variables. - -### Beneficiary of Referrer - -For example, a local pizza shop offers a 30% off Coupon and the owner of the shop encourages their consumers to share the coupon with friends, then the friends can get the coupon. Let's say Tom gets 30% off Coupon from the shop and he shares the coupon with Alice. Alice gets the coupon too and Alice's referrer is Tom. For some certain cases, Tom may get more rewards from the shop. This will help the merchants in spreading the promotion among consumers. - -### Proposal: NFT Transfer - -If the owner of the NFT transfers ownership to another user, there is no impact on "privileges". But errors may occur if the owner tries to withdraw the original [EIP-721](./eip-721.md) token from the wrapped NFT through `unwrap()` if any available privileges are still ongoing. We protect the rights of holders of the privileges to check the last expiration date of the privilege. - -```solidity -function unwrap(uint256 tokenId, address to) external { - require(getBlockTimestamp() >= privilegeBook[tokenId].lastExpiresAt, "privilege not yet expired"); - - require(ownerOf(tokenId) == msg.sender, "not owner"); - - _burn(tokenId); - - IERC721(nft).transferFrom(address(this), to, tokenId); - - emit Unwrap(nft, tokenId, msg.sender, to); -} -``` - -## Backwards Compatibility - -This EIP is compatible with any kind of NFTs that follow the EIP-721 standard. It only adds more functions and data structures without interfering with the original [EIP-721](./eip-721.md) standard. - -## Test Cases - -Test cases are implemented with the reference implementation. - -### Test Code - -[test.js](../assets/eip-5496/test/test.js) - -Run in terminal: - -```shell -truffle test ./test/test.js -``` - -[testCloneable.js](../assets/eip-5496/test/testCloneable.js) - -Run in terminal: - -```shell -truffle test ./test/testCloneable.js -``` - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "./IERC5496.sol"; - -contract ERC5496 is ERC721, IERC5496 { - struct PrivilegeRecord { - address user; - uint256 expiresAt; - } - struct PrivilegeStorage { - uint lastExpiresAt; - // privId => PrivilegeRecord - mapping(uint => PrivilegeRecord) privilegeEntry; - } - - uint public privilegeTotal; - // tokenId => PrivilegeStorage - mapping(uint => PrivilegeStorage) public privilegeBook; - mapping(address => mapping(address => bool)) private privilegeDelegator; - - constructor(string memory name_, string memory symbol_) - ERC721(name_,symbol_) - { - - } - - function setPrivilege( - uint tokenId, - uint privId, - address user, - uint64 expires - ) external virtual { - require((hasPrivilege(tokenId, privId, ownerOf(tokenId)) && _isApprovedOrOwner(msg.sender, tokenId)) || _isDelegatorOrHolder(msg.sender, tokenId, privId), "ERC721: transfer caller is not owner nor approved"); - require(expires < block.timestamp + 30 days, "expire time invalid"); - require(privId < privilegeTotal, "invalid privilege id"); - privilegeBook[tokenId].privilegeEntry[privId].user = user; - if (_isApprovedOrOwner(msg.sender, tokenId)) { - privilegeBook[tokenId].privilegeEntry[privId].expiresAt = expires; - if (privilegeBook[tokenId].lastExpiresAt < expires) { - privilegeBook[tokenId].lastExpiresAt = expires; - } - } - emit PrivilegeAssigned(tokenId, privId, user, uint64(privilegeBook[tokenId].privilegeEntry[privId].expiresAt)); - } - - function hasPrivilege( - uint256 tokenId, - uint256 privId, - address user - ) public virtual view returns(bool) { - if (privilegeBook[tokenId].privilegeEntry[privId].expiresAt >= block.timestamp){ - return privilegeBook[tokenId].privilegeEntry[privId].user == user; - } - return ownerOf(tokenId) == user; - } - - function privilegeExpires( - uint256 tokenId, - uint256 privId - ) public virtual view returns(uint256){ - return privilegeBook[tokenId].privilegeEntry[privId].expiresAt; - } - - function _setPrivilegeTotal( - uint total - ) internal { - emit PrivilegeTotalChanged(total, privilegeTotal); - privilegeTotal = total; - } - - function getPrivilegeInfo(uint tokenId, uint privId) external view returns(address user, uint256 expiresAt) { - return (privilegeBook[tokenId].privilegeEntry[privId].user, privilegeBook[tokenId].privilegeEntry[privId].expiresAt); - } - - function setDelegator(address delegator, bool enabled) external { - privilegeDelegator[msg.sender][delegator] = enabled; - } - - function _isDelegatorOrHolder(address delegator, uint256 tokenId, uint privId) internal virtual view returns (bool) { - address holder = privilegeBook[tokenId].privilegeEntry[privId].user; - return (delegator == holder || isApprovedForAll(holder, delegator) || privilegeDelegator[holder][delegator]); - } - - function supportsInterface(bytes4 interfaceId) public override virtual view returns (bool) { - return interfaceId == type(IERC5496).interfaceId || super.supportsInterface(interfaceId); - } -} -``` - -## Security Considerations - -Implementations must thoroughly consider who has the permission to set or clone privileges. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5496.md diff --git a/EIPS/eip-55.md b/EIPS/eip-55.md index 325a70f9683ef0..2220fda77bd4e8 100644 --- a/EIPS/eip-55.md +++ b/EIPS/eip-55.md @@ -1,119 +1 @@ ---- -eip: 55 -title: Mixed-case checksum address encoding -author: Vitalik Buterin , Alex Van de Sande -discussions-to: https://github.com/ethereum/eips/issues/55 -type: Standards Track -category: ERC -status: Final -created: 2016-01-14 ---- - -# Specification - -Code: - -``` python -import eth_utils - - -def checksum_encode(addr): # Takes a 20-byte binary address as input - hex_addr = addr.hex() - checksummed_buffer = "" - - # Treat the hex address as ascii/utf-8 for keccak256 hashing - hashed_address = eth_utils.keccak(text=hex_addr).hex() - - # Iterate over each character in the hex address - for nibble_index, character in enumerate(hex_addr): - - if character in "0123456789": - # We can't upper-case the decimal digits - checksummed_buffer += character - elif character in "abcdef": - # Check if the corresponding hex digit (nibble) in the hash is 8 or higher - hashed_address_nibble = int(hashed_address[nibble_index], 16) - if hashed_address_nibble > 7: - checksummed_buffer += character.upper() - else: - checksummed_buffer += character - else: - raise eth_utils.ValidationError( - f"Unrecognized hex character {character!r} at position {nibble_index}" - ) - - return "0x" + checksummed_buffer - - -def test(addr_str): - addr_bytes = eth_utils.to_bytes(hexstr=addr_str) - checksum_encoded = checksum_encode(addr_bytes) - assert checksum_encoded == addr_str, f"{checksum_encoded} != expected {addr_str}" - - -test("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") -test("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359") -test("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB") -test("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb") - -``` - -In English, convert the address to hex, but if the `i`th digit is a letter (ie. it's one of `abcdef`) print it in uppercase if the `4*i`th bit of the hash of the lowercase hexadecimal address is 1 otherwise print it in lowercase. - -# Rationale - -Benefits: -- Backwards compatible with many hex parsers that accept mixed case, allowing it to be easily introduced over time -- Keeps the length at 40 characters -- On average there will be 15 check bits per address, and the net probability that a randomly generated address if mistyped will accidentally pass a check is 0.0247%. This is a ~50x improvement over ICAP, but not as good as a 4-byte check code. - -# Implementation - -In javascript: - -```js -const createKeccakHash = require('keccak') - -function toChecksumAddress (address) { - address = address.toLowerCase().replace('0x', '') - var hash = createKeccakHash('keccak256').update(address).digest('hex') - var ret = '0x' - - for (var i = 0; i < address.length; i++) { - if (parseInt(hash[i], 16) >= 8) { - ret += address[i].toUpperCase() - } else { - ret += address[i] - } - } - - return ret -} -``` - -``` -> toChecksumAddress('0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359') -'0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359' -``` - -Note that the input to the Keccak256 hash is the lowercase hexadecimal string (i.e. the hex address encoded as ASCII): - -``` - var hash = createKeccakHash('keccak256').update(Buffer.from(address.toLowerCase(), 'ascii')).digest() -``` - -# Test Cases - -``` -# All caps -0x52908400098527886E0F7030069857D2E4169EE7 -0x8617E340B3D01FA5F11F306F4090FD50E238070D -# All Lower -0xde709f2102306220921060314715629080e2fb77 -0x27b1fdb04752bbc536007a920d24acb045561c26 -# Normal -0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed -0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359 -0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB -0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb -``` +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-55.md diff --git a/EIPS/eip-5501.md b/EIPS/eip-5501.md index a041ee3af257e4..b04868d25d95eb 100644 --- a/EIPS/eip-5501.md +++ b/EIPS/eip-5501.md @@ -1,251 +1 @@ ---- -eip: 5501 -title: Rental & Delegation NFT - EIP-721 Extension -description: Adds a conditional time-limited user role to EIP-721. This role can be delegated or borrowed. -author: Jan Smrža (@smrza), David Rábel (@rabeles11), Tomáš Janča , Jan Bureš (@JohnyX89), DOBBYLABS (@DOBBYLABS) -discussions-to: https://ethereum-magicians.org/t/eip-tbd-rental-delegation-nft-erc-721-extension/10441 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-18 -requires: 165, 721, 4400, 4907 ---- - -## Abstract -The following standard proposes an additional `user` role for [EIP-721](./eip-721.md). This role grants the permission to use the NFT with no ability to transfer or set users. It has an expiry and a flag if the token is borrowed or not. `Owner` can delegate the NFT for usage to hot wallets or lend the NFT. If the token is borrowed, not even the owner can change the user until the status expires or both parties agree to terminate. This way, it is possible to keep both roles active at the same time. - -## Motivation -Collectibles, gaming assets, metaverse, event tickets, music, video, domains, real item representation are several among many NFT use cases. With [EIP-721](./eip-721.md) only the owner can reap the benefits. However, with most of the utilities it would be beneficial to distinguish between the token owner and its user. For instance music or movies could be rented. Metaverse lands could be delegated for usage. - -The two reasons why to set the user are: - -* **delegation** - Assign user to your hot wallet to interact with applications securely. In this case, the owner can change the user at any time. -* **renting** - This use case comes with additional requirements. It is needed to terminate the loan once the established lending period is over. This is provided by `expires` of the user. It is also necessary to protect the borrower against resetting their status by the owner. Thus, `isBorrowed` check must be implemented to disable the option to set the user before the contract expires. - -The most common use cases for having an additional user role are: - -* **delegation** - For security reasons. -* **gaming** - Would you like to try a game (or particular gaming assets) but are you unsure whether or not you will like it? Rent assets first. -* **guilds** - Keep the owner of the NFTs as the multisig wallet and set the user to a hot wallet with shared private keys among your guild members. -* **events** - Distinguish between `ownerOf` and `userOf`. Each role has a different access. -* **social** - Differentiate between roles for different rooms. For example owner has read + write access while userOf has read access only. - -This proposal is a follow up on [EIP-4400](./eip-4400.md) and [EIP-4907](./eip-4907.md) and introduces additional upgrades for lending and borrowing which include: - -* **NFT stays in owner's wallet during rental period** -* **Listing and sale of NFT without termination of the rent** -* **Claiming owner benefits during rental period** - -Building the standard with additional isBorrowed check now allows to create rental marketplaces which can set the user of NFT without the necessary staking mechanism. With current standards if a token is not staked during the rental period, the owner can simply terminate the loan by setting the user repeatedly. This is taken care of by disabling the function if the token is borrowed which in turn is providing the owner additional benefits. They can keep the token tied to their wallet, meaning they can still receive airdrops, claim free mints based on token ownership or otherwise use the NFT provided by third-party services for owners. They can also keep the NFT listed for sale. Receiving airdrops or free mints was previously possible but the owner was completely reliant on the implementation of rental marketplaces and their discretion. - -Decentralized applications can now differentiate between ownerOf and userOf while both statuses can coexist. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -**Every compliant contract MUST implement the `IERC5501` interface. This extension is OPTIONAL for [EIP-721](./eip-721.md) contracts.** - -```solidity -/** - * @title IERC5501: Rental & Delegation NFT - EIP-721 Extension - * @notice the EIP-165 identifier for this interface is 0xf808ec37. - */ -interface IERC5501 /* is IERC721 */ { - /** - * @dev Emitted when the user of an NFT is modified. - */ - event UpdateUser(uint256 indexed _tokenId, address indexed _user, uint64 _expires, bool _isBorrowed); - - /** - * @notice Set the user info of an NFT. - * @dev User address cannot be zero address. - * Only approved operator or NFT owner can set the user. - * If NFT is borrowed, the user info cannot be changed until user status expires. - * @param _tokenId uint256 ID of the token to set user info for - * @param _user address of the new user - * @param _expires Unix timestamp when user info expires - * @param _isBorrowed flag whether or not the NFT is borrowed - */ - function setUser(uint256 _tokenId, address _user, uint64 _expires, bool _isBorrowed) external; - - /** - * @notice Get the user address of an NFT. - * @dev Reverts if user is not set. - * @param _tokenId uint256 ID of the token to get the user address for - * @return address user address for this NFT - */ - function userOf(uint256 _tokenId) external view returns (address); - - /** - * @notice Get the user expires of an NFT. - * @param _tokenId uint256 ID of the token to get the user expires for - * @return uint64 user expires for this NFT - */ - function userExpires(uint256 _tokenId) external view returns (uint64); - - /** - * @notice Get the user isBorrowed of an NFT. - * @param _tokenId uint256 ID of the token to get the user isBorrowed for - * @return bool user isBorrowed for this NFT - */ - function userIsBorrowed(uint256 _tokenId) external view returns (bool); -} -``` - -Every contract implementing the `IERC5501` interface is free to define the permissions of a `user`. However, user MUST NOT be considered an `owner`. They MUST NOT be able to execute transfers and approvals. Furthermore, `setUser` MUST be blocked from executing if `userIsBorrowed` returns `true` and `userExpires` is larger than or equal to `block.timestamp`. - -The `UpdateUser` event MUST be emitted when a `user` is changed. -The `setUser(uint256 _tokenId, address _user, uint64 _expires, bool _isBorrowed)` function SHOULD `revert` unless the `msg.sender` is the `owner` or an approved operator. It MUST revert if a token is borrowed and status has not expired yet. It MAY be `public` or `external`. -The `userOf(uint256 _tokenId)` function SHOULD revert if `user` is not set or expired. -The `userExpires(uint256 _tokenId)` function returns a timestamp when user status expires. -The `userIsBorrowed(uint256 _tokenId)` function returns whether NFT is borrowed or not. -The `supportsInterface` function MUST return `true` when called with `0xf808ec37`. -On every `transfer`, the `user` MUST be reset if the token is not borrowed. If the token is borrowed the `user` MUST stay the same. - -**The Balance extension is OPTIONAL. This gives the option to query the number of tokens a `user` has.** - -```solidity -/** - * @title IERC5501Balance - * Extension for ERC5501 which adds userBalanceOf to query how many tokens address is userOf. - * @notice the EIP-165 identifier for this interface is 0x0cb22289. - */ -interface IERC5501Balance /* is IERC5501 */{ - /** - * @notice Count of all NFTs assigned to a user. - * @dev Reverts if user is zero address. - * @param _user an address for which to query the balance - * @return uint256 the number of NFTs the user has - */ - function userBalanceOf(address _user) external view returns (uint256); -} -``` - -The `userBalanceOf(address _user)` function SHOULD `revert` for zero address. - -**The Enumerable extension is OPTIONAL. This allows to iterate over user balance.** - -```solidity -/** - * @title IERC5501Enumerable - * This extension for ERC5501 adds the option to iterate over user tokens. - * @notice the EIP-165 identifier for this interface is 0x1d350ef8. - */ -interface IERC5501Enumerable /* is IERC5501Balance, IERC5501 */ { - /** - * @notice Enumerate NFTs assigned to a user. - * @dev Reverts if user is zero address or _index >= userBalanceOf(_owner). - * @param _user an address to iterate over its tokens - * @return uint256 the token ID for given index assigned to _user - */ - function tokenOfUserByIndex(address _user, uint256 _index) external view returns (uint256); -} -``` - -The `tokenOfUserByIndex(address _user, uint256 _index)` function SHOULD `revert` for zero address and `throw` if the index is larger than or equal to `user` balance. - -**The Terminable extension is OPTIONAL. This allows terminating the rent early if both parties agree.** - -```solidity -/** - * @title IERC5501Terminable - * This extension for ERC5501 adds the option to terminate borrowing if both parties agree. - * @notice the EIP-165 identifier for this interface is 0x6a26417e. - */ -interface IERC5501Terminable /* is IERC5501 */ { - /** - * @dev Emitted when one party from borrowing contract approves termination of agreement. - * @param _isLender true for lender, false for borrower - */ - event AgreeToTerminateBorrow(uint256 indexed _tokenId, address indexed _party, bool _isLender); - - /** - * @dev Emitted when agreements to terminate borrow are reset. - */ - event ResetTerminationAgreements(uint256 indexed _tokenId); - - /** - * @dev Emitted when borrow of token ID is terminated. - */ - event TerminateBorrow(uint256 indexed _tokenId, address indexed _lender, address indexed _borrower, address _caller); - - /** - * @notice Agree to terminate a borrowing. - * @dev Lender must be ownerOf token ID. Borrower must be userOf token ID. - * If lender and borrower are the same, set termination agreement for both at once. - * @param _tokenId uint256 ID of the token to set termination info for - */ - function setBorrowTermination(uint256 _tokenId) external; - - /** - * @notice Get if it is possible to terminate a borrow agreement. - * @param _tokenId uint256 ID of the token to get termination info for - * @return bool, bool first indicates lender agrees, second indicates borrower agrees - */ - function getBorrowTermination(uint256 _tokenId) external view returns (bool, bool); - - /** - * @notice Terminate a borrow if both parties agreed. - * @dev Both parties must have agreed, otherwise revert. - * @param _tokenId uint256 ID of the token to terminate borrow of - */ - function terminateBorrow(uint256 _tokenId) external; -} -``` - -The `AgreeToTerminateBorrow` event MUST be emitted when either the lender or borrower agrees to terminate the rent. -The `ResetTerminationAgreements` event MUST be emitted when a token is borrowed and transferred or `setUser` and `terminateBorrow` functions are called. -The `TerminateBorrow` event MUST be emitted when the rent is terminated. -The `setBorrowTermination(uint256 _tokenId)`. It MUST set an agreement from either party whichever calls the function. If the lender and borrower are the same address, it MUST assign an agreement for both parties at once. -The `getBorrowTermination(uint256 _tokenId)` returns if agreements from both parties are `true` or `false`. -The `terminateBorrow(uint256 _tokenId)` function MAY be called by anyone. It MUST `revert` if both agreements to terminate are not `true`. This function SHOULD change the `isBorrowed` flag from `true` to `false`. -On every `transfer`, the termination agreements from either party MUST be reset if the token is borrowed. - -## Rationale -The main factors influencing this standard are: - -* **[EIP-4400](./eip-4400.md) and [EIP-4907](./eip-4907.md)** -* **Allow lending and borrowing without the necessary stake or overcollateralization while owner retains ownership** -* **Leave the delegation option available** -* **Keep the number of functions in the interfaces to a minimum while achieving desired functionality** -* **Modularize additional extensions to let developers choose what they need for their project** - -### Name -The name for the additional role has been chosen to fit the purpose and to keep compatibility with EIP-4907. - -### Ownership retention -Many collections offer their owners airdrops or free minting of various tokens. This is essentially broken if the owner is lending a token by staking it into a contract (unless the contract is implementing a way to claim at least airdropped tokens). Applications can also provide different access and benefits to owner and user roles in their ecosystem. - -### Balance and Enumerable extensions -These have been chosen as OPTIONAL extensions due to the complexity of implementation based on the fact that balance is less once user status expires and there is no immediate on-chain transaction to evaluate that. In both `userBalanceOf` and `tokenOfUserByIndex` functions there must be a way to determine whether or not user status has expired. - -### Terminable extension -If the owner mistakenly sets a user with borrow status and expires to a large value they would essentially be blocked from setting the user ever again. The problem is addressed by this extension if both parties agree to terminate the user status. - -### Security -Once applications adopt the user role, it is possible to delegate ownership to hot wallet and interact with them with no fear of connecting to malicious websites. - -## Backwards Compatibility -This standard is compatible with current [EIP-721](./eip-721.md) by adding an extension function set. The new functions introduced are similar to existing functions in EIP-721 which guarantees easy adoption by developers and applications. This standard also shares similarities to [EIP-4907](./eip-4907.md) considering user role and its expiry which means applications will be able to determine the user if either of the standards is used. - -## Test Cases -Test cases can be found in the reference implementation: -* [Main contract](../assets/eip-5501/test/ERC5501Test.ts) -* [Balance extension](../assets/eip-5501/test/ERC5501BalanceTest.ts) -* [Enumerable extension](../assets/eip-5501/test/ERC5501EnumerableTest.ts) -* [Terminable extension](../assets/eip-5501/test/ERC5501TerminableTest.ts) -* [Scenario combined of all extensions](../assets/eip-5501/test/ERC5501CombinedTest.ts) - -## Reference Implementation -The reference implementation is available here: -* [Main contract](../assets/eip-5501/contracts/ERC5501.sol) -* [Balance extension](../assets/eip-5501/contracts/ERC5501Balance.sol) -* [Enumerable extension](../assets/eip-5501/contracts/ERC5501Enumerable.sol) -* [Terminable extension](../assets/eip-5501/contracts/ERC5501Terminable.sol) -* [Solution combined of all extensions](../assets/eip-5501/contracts/ERC5501Combined.sol) - -## Security Considerations -Developers implementing this standard and applications must consider all the permissions they give to users and owners. Since owner and user are both active roles at the same time, double-spending problem must be avoided. Balance extension must be implemented in such a way which will not cause any gas problems. Marketplaces should let users know if a token listed for sale is borrowed or not. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5501.md diff --git a/EIPS/eip-5505.md b/EIPS/eip-5505.md index a4a5a08e52c0f9..bc0fa7f9bfbe46 100644 --- a/EIPS/eip-5505.md +++ b/EIPS/eip-5505.md @@ -1,77 +1 @@ ---- -eip: 5505 -title: EIP-1155 asset backed NFT extension -description: Extends EIP-1155 to support crucial operations for asset-backed NFTs -author: liszechung (@liszechung) -discussions-to: https://ethereum-magicians.org/t/eip-draft-erc1155-asset-backed-nft-extension/10437 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-18 -requires: 1155 ---- - -## Abstract -To propose an extension of smart contract interfaces for asset-backed, fractionalized projects using the [EIP-1155](./eip-1155.md) standard such that total acquisition will become possible. This proposal focuses on physical asset, where total acquisition should be able to happen. - -## Motivation -Fractionalized, asset backed NFTs face difficulty when someone wants to acquire the whole asset. For example, if someone wants to bring home a fractionalized asset, he needs to buy all NFT pieces so he will become the 100% owner. However he could not do so as it is publicly visible that someone is trying to perform a total acquisition in an open environment like Ethereum. Sellers will take advantage to set unreasonable high prices which hinders the acquisition. Or in other cases, NFTs are owned by wallets with lost keys, such that the ownership will never be a complete one. We need a way to enable potential total acquisition. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -[EIP-1155](./eip-1155.md) compliant contracts MAY implement this EIP for adding functionalities to support total acquisition. - -```solidity -//set the percentage required for any acquirer to trigger a forced sale -//set also the payment token to settle for the acquisition - -function setForcedSaleRequirement( - uint128 requiredBP, - address erc20Token -) public onlyOwner - -//set the unit price to acquire the remaining NFTs (100% - requiredBP) -//suggest to use a Time Weighted Average Price for a certain period before reaching the requiredBP -//emit ForcedSaleSet - -function setForcedSaleTWAP( - uint256 amount -) public onlyOwner - -//acquirer deposit remainingQTY*TWAP -//emit ForcedSaleFinished -//after this point, the acquirer is the new owner of the whole asset - -function execForcedSale ( - uint256 amount -) public external payable - -//burn ALL NFTs and collect funds -//emit ForcedSaleClaimed - -function claimForcedSale() -public - -event ForcedSaleSet( - bool isSet -) -event ForceSaleClaimed( - uint256 qtyBurned, - uint256 amountClaimed, - address claimer -) -``` - - -## Rationale -Native ETH is supported by via Wrapped Ether [EIP-20](./eip-20.md). -After forcedSale is set, the remaining NFTs metadata should be updated to reflect the NFTs are at most valued at the previously set TWAP price. - -## Security Considerations -The major security risks considered include -- The execution of the forcedSale is only executed by the contract owner, after a governance proposal. If there is any governance attack, the forcedSale TWAP price might be manipulated on a specific timing. The governance structure for using this extension should consider adding a **council** to safeguard the fairness of the forcedSale. -- Payment tokens are deposited into the contract account when forcedSale is executed. These tokens will then await the minority holders to withdraw on burning the NFT. There might be a potential security risk. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5505.md diff --git a/EIPS/eip-5507.md b/EIPS/eip-5507.md index edf17d460286b8..f5a9e78ee4937a 100644 --- a/EIPS/eip-5507.md +++ b/EIPS/eip-5507.md @@ -1,332 +1 @@ ---- -eip: 5507 -title: Refundable Tokens -description: Adds refund functionality to ERC-20, ERC-721, and ERC-1155 tokens -author: elie222 (@elie222), Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/eip-5507-refundable-nfts/10451 -status: Final -type: Standards Track -category: ERC -created: 2022-08-19 -requires: 20, 165, 721, 1155 ---- - -## Abstract - -This ERC adds refund functionality for initial token offerings to [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), and [ERC-1155](./eip-1155.md). Funds are held in escrow until a predetermined time before they are claimable. Until that predetermined time passes, users can receive a refund for tokens they have purchased. - -## Motivation - -The NFT and token spaces lack accountability. For the health of the ecosystem as a whole, better mechanisms to prevent rugpulls from happening are needed. Offering refunds provides greater protection for buyers and increases legitimacy for creators. - -A standard interface for this particular use case allows for certain benefits: - -- Greater Compliance with EU "Distance Selling Regulations," which require a 14-day refund period for goods (such as tokens) purchased online -- Interoperability with various NFT-related applications, such as portfolio browsers, and marketplaces - - NFT marketplaces could place a badge indicating that the NFT is still refundable on listings, and offer to refund NFTs instead of listing them on the marketplace - - DExes could offer to refund tokens if doing so would give a higher yield -- Better wallet confirmation dialogs - - Wallets can better inform the user of the action that is being taken (tokens being refunded), similar to how transfers often have their own unique dialog - - DAOs can better display the functionality of smart proposals that include refunding tokens - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -All implementations MUST use and follow the directions of [ERC-165](./eip-165.md). - -### ERC-20 Refund Extension - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.17; - -import "ERC20.sol"; -import "ERC165.sol"; - -/// @notice Refundable ERC-20 tokens -/// @dev The ERC-165 identifier of this interface is `0xf0ca2917` -interface ERC20Refund is ERC20, ERC165 { - /// @notice Emitted when a token is refunded - /// @dev Emitted by `refund` - /// @param _from The account whose assets are refunded - /// @param _amount The amount of token (in terms of the indivisible unit) that was refunded - event Refund( - address indexed _from, - uint256 indexed _amount - ); - - /// @notice Emitted when a token is refunded - /// @dev Emitted by `refundFrom` - /// @param _sender The account that sent the refund - /// @param _from The account whose assets are refunded - /// @param _amount The amount of token (in terms of the indivisible unit) that was refunded - event RefundFrom( - address indexed _sender, - address indexed _from, - uint256 indexed _amount - ); - - /// @notice As long as the refund is active, refunds the user - /// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors - /// @param amount The `amount` to refund - function refund(uint256 amount) external; - - /// @notice As long as the refund is active and the sender has sufficient approval, refund the tokens and send the ether to the sender - /// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors - /// The ether goes to msg.sender. - /// @param from The user from which to refund the assets - /// @param amount The `amount` to refund - function refundFrom(address from, uint256 amount) external; - - /// @notice Gets the refund price - /// @return _wei The amount of ether (in wei) that would be refunded for a single token unit (10**decimals indivisible units) - function refundOf() external view returns (uint256 _wei); - - /// @notice Gets the first block for which the refund is not active - /// @return block The first block where the token cannot be refunded - function refundDeadlineOf() external view returns (uint256 block); -} -``` - -### ERC-721 Refund Extension - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.17; - -import "ERC721.sol"; -import "ERC165.sol"; - -/// @notice Refundable ERC-721 tokens -/// @dev The ERC-165 identifier of this interface is `0xe97f3c83` -interface ERC721Refund is ERC721 /* , ERC165 */ { - /// @notice Emitted when a token is refunded - /// @dev Emitted by `refund` - /// @param _from The account whose assets are refunded - /// @param _tokenId The `tokenId` that was refunded - event Refund( - address indexed _from, - uint256 indexed _tokenId - ); - - /// @notice Emitted when a token is refunded - /// @dev Emitted by `refundFrom` - /// @param _sender The account that sent the refund - /// @param _from The account whose assets are refunded - /// @param _tokenId The `tokenId` that was refunded - event RefundFrom( - address indexed _sender, - address indexed _from, - uint256 indexed _tokenId - ); - - /// @notice As long as the refund is active for the given `tokenId`, refunds the user - /// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors - /// @param tokenId The `tokenId` to refund - function refund(uint256 tokenId) external; - - /// @notice As long as the refund is active and the sender has sufficient approval, refund the token and send the ether to the sender - /// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors - /// The ether goes to msg.sender. - /// @param from The user from which to refund the token - /// @param tokenId The `tokenId` to refund - function refundFrom(address from, uint256 tokenId) external; - - /// @notice Gets the refund price of the specific `tokenId` - /// @param tokenId The `tokenId` to query - /// @return _wei The amount of ether (in wei) that would be refunded - function refundOf(uint256 tokenId) external view returns (uint256 _wei); - - /// @notice Gets the first block for which the refund is not active for a given `tokenId` - /// @param tokenId The `tokenId` to query - /// @return block The first block where token cannot be refunded - function refundDeadlineOf(uint256 tokenId) external view returns (uint256 block); -} -``` - -#### Optional ERC-721 Batch Refund Extension - -```solidity -// SPDX-License-Identifier: CC0-1.0; - -import "ERC721Refund.sol"; - -/// @notice Batch Refundable ERC-721 tokens -/// @dev The ERC-165 identifier of this interface is `` -contract ERC721BatchRefund is ERC721Refund { - /// @notice Emitted when one or more tokens are batch refunded - /// @dev Emitted by `refundBatch` - /// @param _from The account whose assets are refunded - /// @param _tokenId The `tokenIds` that were refunded - event RefundBatch( - address indexed _from, - uint256[] _tokenIds // This may or may not be indexed - ); - - /// @notice Emitted when one or more tokens are batch refunded - /// @dev Emitted by `refundFromBatch` - /// @param _sender The account that sent the refund - /// @param _from The account whose assets are refunded - /// @param _tokenId The `tokenId` that was refunded - event RefundFromBatch( - address indexed _sender, - address indexed _from, - uint256 indexed _tokenId - ); - - /// @notice As long as the refund is active for the given `tokenIds`, refunds the user - /// @dev Make sure to check that the user has the tokens, and be aware of potential re-entrancy vectors - /// These must either succeed or fail together; there are no partial refunds. - /// @param tokenIds The `tokenId`s to refund - function refundBatch(uint256[] tokenIds) external; - - /// @notice As long as the refund is active for the given `tokenIds` and the sender has sufficient approval, refund the tokens and send the ether to the sender - /// @dev Make sure to check that the user has the tokens, and be aware of potential re-entrancy vectors - /// The ether goes to msg.sender. - /// These must either succeed or fail together; there are no partial refunds. - /// @param from The user from which to refund the token - /// @param tokenIds The `tokenId`s to refund - function refundFromBatch(address from, uint256[] tokenIds) external; -} -``` - -### ERC-1155 Refund Extension - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.17; - -import "ERC1155.sol"; -import "ERC165.sol"; - -/// @notice Refundable ERC-1155 tokens -/// @dev The ERC-165 identifier of this interface is `0x94029f5c` -interface ERC1155Refund is ERC1155 /* , ERC165 */ { - /// @notice Emitted when a token is refunded - /// @dev Emitted by `refund` - /// @param _from The account that requested a refund - /// @param _tokenId The `tokenId` that was refunded - /// @param _amount The amount of `tokenId` that was refunded - event Refund( - address indexed _from, - uint256 indexed _tokenId, - uint256 _amount - ); - - /// @notice Emitted when a token is refunded - /// @dev Emitted by `refundFrom` - /// @param _sender The account that sent the refund - /// @param _from The account whose assets are refunded - /// @param _tokenId The `tokenId` that was refunded - /// @param _amount The amount of `tokenId` that was refunded - event RefundFrom( - address indexed _sender, - address indexed _from, - uint256 indexed _tokenId - ); - - /// @notice As long as the refund is active for the given `tokenId`, refunds the user - /// @dev Make sure to check that the user has enough tokens, and be aware of potential re-entrancy vectors - /// @param tokenId The `tokenId` to refund - /// @param amount The amount of `tokenId` to refund - function refund(uint256 tokenId, uint256 amount) external; - - /// @notice As long as the refund is active and the sender has sufficient approval, refund the tokens and send the ether to the sender - /// @dev Make sure to check that the user has enough tokens, and be aware of potential re-entrancy vectors - /// The ether goes to msg.sender. - /// @param from The user from which to refund the token - /// @param tokenId The `tokenId` to refund - /// @param amount The amount of `tokenId` to refund - function refundFrom(address from, uint256 tokenId, uint256 amount) external; - - /// @notice Gets the refund price of the specific `tokenId` - /// @param tokenId The `tokenId` to query - /// @return _wei The amount of ether (in wei) that would be refunded for a single token - function refundOf(uint256 tokenId) external view returns (uint256 _wei); - - /// @notice Gets the first block for which the refund is not active for a given `tokenId` - /// @param tokenId The `tokenId` to query - /// @return block The first block where the token cannot be refunded - function refundDeadlineOf(uint256 tokenId) external view returns (uint256 block); -} -``` - -#### Optional ERC-1155 Batch Refund Extension - -```solidity -// SPDX-License-Identifier: CC0-1.0; - -import "ERC1155Refund.sol"; - -/// @notice Batch Refundable ERC-1155 tokens -/// @dev The ERC-165 identifier of this interface is `` -contract ERC1155BatchRefund is ERC1155Refund { - /// @notice Emitted when one or more tokens are batch refunded - /// @dev Emitted by `refundBatch` - /// @param _from The account that requested a refund - /// @param _tokenIds The `tokenIds` that were refunded - /// @param _amounts The amount of each `tokenId` that was refunded - event RefundBatch( - address indexed _from, - uint256[] _tokenIds, // This may or may not be indexed - uint256[] _amounts - ); - - /// @notice Emitted when one or more tokens are batch refunded - /// @dev Emitted by `refundFromBatch` - /// @param _sender The account that sent the refund - /// @param _from The account whose assets are refunded - /// @param _tokenIds The `tokenIds` that was refunded - /// @param _amounts The amount of each `tokenId` that was refunded - event RefundFromBatch( - address indexed _sender, - address indexed _from, - uint256[] _tokenId, // This may or may not be indexed - uint256[] _amounts - ); - - /// @notice As long as the refund is active for the given `tokenIds`, refunds the user - /// @dev Make sure to check that the user has enough tokens, and be aware of potential re-entrancy vectors - /// These must either succeed or fail together; there are no partial refunds. - /// @param tokenIds The `tokenId`s to refund - /// @param amounts The amount of each `tokenId` to refund - function refundBatch(uint256[] tokenIds, uint256[] amounts) external; - - /// @notice As long as the refund is active for the given `tokenIds` and the sender has sufficient approval, refund the tokens and send the ether to the sender - /// @dev Make sure to check that the user has the tokens, and be aware of potential re-entrancy vectors - /// The ether goes to msg.sender. - /// These must either succeed or fail together; there are no partial refunds. - /// @param from The user from which to refund the token - /// @param tokenIds The `tokenId`s to refund - /// @param amounts The amount of each `tokenId` to refund - function refundFromBatch(address from, uint256[] tokenIds, uint256[] amounts external; -} -``` - -## Rationale - -`refundDeadlineOf` uses blocks instead of timestamps, as timestamps are less reliable than block numbers. - -The function names of `refund`, `refundOf`, and `refundDeadlineOf` were chosen to fit the naming style of ERC-20, ERC-721, and ERC-1155. - -[ERC-165](./eip-165.md) is required as introspection by DApps would be made significantly harder if it were not. - -Custom ERC-20 tokens are not supported, as it needlessly increases complexity, and the `refundFrom` function allows for this functionality when combined with a DEx. - -Batch refunds are optional, as account abstraction would make atomic operations like these significantly easier. However, they might still reduce gas costs if properly implemented. - -## Backwards Compatibility - -No backward compatibility issues were found. - -## Security Considerations - -There is a potential re-entrancy risk with the `refund` function. Make sure to perform the ether transfer **after** the tokens are destroyed (i.e. obey the checks, effects, interactions pattern). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5507.md diff --git a/EIPS/eip-5516.md b/EIPS/eip-5516.md index 857bfedcdcdbc6..b4594dc6088e00 100644 --- a/EIPS/eip-5516.md +++ b/EIPS/eip-5516.md @@ -1,186 +1 @@ ---- -eip: 5516 -title: Soulbound Multi-owner Tokens -description: An interface for non-transferable, Multi-owner NFTs binding to Ethereum accounts -author: Lucas Martín Grasso Ramos (@LucasGrasso), Matias Arazi (@MatiArazi) -discussions-to: https://ethereum-magicians.org/t/EIP-5516-soulbound-multi-token-standard/10485 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-19 -requires: 165, 1155 ---- - -## Abstract -This EIP proposes a standard interface for non-fungible double signature Soulbound multi-tokens. Previous account-bound token standards face the issue of users losing their account keys or having them rotated, thereby losing their tokens in the process. This EIP provides a solution to this issue that allows for the recycling of SBTs. - -## Motivation -This EIP was inspired by the main characteristics of the [EIP-1155](./eip-1155.md) token and by articles in which benefits and potential use cases of Soulbound/Accountbound Tokens (SBTs) were presented. -This design also allows for batch token transfers, saving on transaction costs. Trading of multiple tokens can be built on top of this standard and it removes the need to approve individual token contracts separately. It is also easy to describe and mix multiple fungible or non-fungible token types in a single contract. - -### Characteristics -- The NFT will be non-transferable after the initial transfer -- Partially compatible with [EIP-1155](./eip-1155.md) -- Double Signature -- Multi-Token -- Multi-Owner -- Semi-Fungible - -### Applications -- Academic Degrees -- Code audits -- POAPs (Proof of Attendance Protocol NFTs) - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -**Smart contracts implementing this EIP MUST implement all of the functions in the `EIP-5516` interface.** - -**Smart contracts implementing this EIP MUST implement the [EIP-165](./eip-165.md) `supportsInterface` function and and MUST return the constant value `true` if `0x8314f22b` is passed through the `interfaceID` argument. They also MUST implement the [EIP-1155](./eip-1155.md) Interface and MUST return the constant value `true` if `0xd9b67a26` is passed through the `interfaceID` argument. Furthermore, they MUST implement the [EIP-1155](./eip-1155.md) Metadata interface, and MUST return the constant value `true` if `0x0e89341c` is passed through the `interfaceID` argument.** - -_See [EIP-1155](./eip-1155.md#specification)_ - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.4; - -/** - @title Soulbound, Multi-Token standard. - @notice Interface of the EIP-5516 - Note: The ERC-165 identifier for this interface is 0x8314f22b. - */ - -interface IERC5516 { - /** - * @dev Emitted when `account` claims or rejects pending tokens under `ids[]`. - */ - event TokenClaimed( - address indexed operator, - address indexed account, - bool[] actions, - uint256[] ids - ); - - /** - * @dev Emitted when `from` transfers token under `id` to every address at `to[]`. - */ - event TransferMulti( - address indexed operator, - address indexed from, - address[] to, - uint256 amount, - uint256 id - ); - - /** - * @dev Get tokens owned by a given address. - */ - function tokensFrom(address from) external view returns (uint256[] memory); - - /** - * @dev Get tokens awaiting to be claimed by a given address. - */ - function pendingFrom(address from) external view returns (uint256[] memory); - - /** - * @dev Claims or Reject pending `id`. - * - * Requirements: - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function claimOrReject( - address account, - uint256 id, - bool action - ) external; - - /** - * @dev Claims or Reject pending tokens under `ids[]`. - * - * Requirements for each `id` `action` pair: - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function claimOrRejectBatch( - address account, - uint256[] memory ids, - bool[] memory actions - ) external; - - /** - * @dev Transfers `id` token from `from` to every address at `to[]`. - * - * Requirements: - * - * - `from` MUST be the creator(minter) of `id`. - * - All addresses in `to[]` MUST be non-zero. - * - All addresses in `to[]` MUST have the token `id` under `_pendings`. - * - All addresses in `to[]` MUST not own a token type under `id`. - * - * Emits a {TransfersMulti} event. - * - */ - function batchTransfer( - address from, - address[] memory to, - uint256 id, - uint256 amount, - bytes memory data - ) external; - -} - -``` - -## Rationale - -### SBT as an extension of EIP-1155 -We believe that Soulbound Tokens serve as a specialized subset of existing [EIP-1155](./eip-1155.md) tokens. The advantage of such a design is the seamless compatibility of SBTs with existing NFT services. Service providers can treat SBTs like NFTs and do not need to make drastic changes to their existing codebase. - -Making the standard mostly compatible with [EIP-1155](./eip-1155.md) also allows for SBTs to bind to multiple addresses and to Smart Contracts. - -### Double-Signature -The Double-Signature functionality was implemented to prevent the receipt of unwanted tokens. It symbolizes a handshake between the token receiver and sender, implying that **both** parties agree on the token transfer. - -### Metadata. -The [EIP-1155](./eip-1155.md#metadata) Metadata Interface was implemented for further compatibility with [EIP-1155](./eip-1155.md). - -### Guaranteed log trace -> As the Ethereum ecosystem continues to grow, many DApps are relying on traditional databases and explorer API services to retrieve and categorize data. The EIP-1155 standard guarantees that event logs emitted by the smart contract will provide enough data to create an accurate record of all current token balances. A database or explorer may listen to events and be able to provide indexed and categorized searches of every EIP-1155 token in the contract. - -_Quoted from [EIP-1155](./eip-1155.md#guaranteed-log-trace)_ - -This EIP extends this concept to the Double Signature functionality: The `{TokenClaimed}` event logs all the necessary information of a `ClaimOrReject(...)` or `ClaimOrRejectBatch(...)` function call, storing relevant information about the actions performed by the user. This also applies to the `batchTransfer(...)` function: It emits the `{TransferMulti}` event and logs necessary data. - -### Exception handling -Given the non-transferability property of SBTs, if a user's keys to an account get compromised or rotated, such user may lose the ability to associate themselves with the token. - -**Given the multi-owner characteristic of [EIP-1155](./eip-1155.md) compliant interfaces and contracts, SBTs will be able to bind to multiple accounts, providing a potential solution to the issue.** - -Multi-owner SBTs can also be issued to a contract account that implements a multi-signature functionality (As recommended in [EIP-4973](./eip-4973.md#exception-handling)); this can be achieved via the [EIP-1155](./eip-1155.md#erc-1155-token-receiver) Token Receiver interface. - -### Multi-token -The multi-token functionality permits the implementation of multiple token types in the same contract. Furthermore, all emitted tokens are stored in the same contract, preventing redundant bytecode from being deployed to the blockchain. It also facilitates transfer to token issuers, since all issued tokens are stored and can be accessed under the same contract address. - -### The `batchTransfer` function -This EIP supports transfers to multiple recipients. This eases token transfer to a large number of addresses, making it more gas-efficient and user-friendly. - -## Backwards Compatibility -This proposal is only partially compatible with EIP-1155, because it makes tokens non-transferable after the first transfer. - -## Reference Implementation -You can find an implementation of this standard in [../assets/EIP-5516](../assets/eip-5516/ERC5516.sol). - -## Security Considerations -Needs discussion. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5516.md diff --git a/EIPS/eip-5521.md b/EIPS/eip-5521.md index 4bf16cae954358..52f9748657d8ec 100644 --- a/EIPS/eip-5521.md +++ b/EIPS/eip-5521.md @@ -1,250 +1 @@ ---- -eip: 5521 -title: Referable NFT -description: An ERC-721 extension to construct reference relationships among NFTs -author: Saber Yu (@OniReimu), Qin Wang , Shange Fu , Yilin Sai , Shiping Chen , Sherry Xu , Jiangshan Yu -discussions-to: https://ethereum-magicians.org/t/eip-x-erc-721-referable-nft/10310 -status: Draft -type: Standards Track -category: ERC -created: 2022-08-10 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It proposes two referrable indicators, referring and referred, and a time-based indicator `createdTimestamp`. The relationship between each NFT forms a Directed acyclic graph (DAG). The standard allows users to query, track and analyze their relationships. - -## Motivation - -Many scenarios require inheritance, reference, and extension of NFTs. For instance, an artist may develop his NFT work based on a previous NFT, or a DJ may remix his record by referring to two pop songs, etc. Proposing a referable solution for existing NFTs and enabling efficient queries on cross-references make much sense. - -By adding the `referring` indicator, users can mint new NFTs (e.g., C, D, E) by referring to existing NFTs (e.g., A, B), while `referred` enables the referred NFTs (A, B) to be aware that who has quoted it (e.g., A ← D; C ← E; B ← E, and A ← E). The `createdTimestamp` is an indicator used to show the creation time of NFTs (A, B, C, D, E). - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -`Relationship`: a structure that contains `referring`, `referred`, `createdTimestamp`, and other customized attributes such as `mapping (uint256 => address) privityOfAgreement` recording the ownerships of referred NFTs at the time the rNFTs were being created. -`referring`: an out-degree indicator, used to show the users this NFT refers to; -`referred`: an in-degree indicator, used to show the users who have refereed this NFT; -`createdTimestamp`: a time-based indicator, used to compare the timestamp of mint. - -`safeMint`: mint a new rNFT; -`setNode`: set the referring list of an rNFT and update the referred list of each one in the referring list; -`setNodeReferring`: set the referring list of an rNFT; -`setNodeReferred`: set the referred list of the given rNFTs; -`setNodeReferredExternal`: set the referred list of the given rNFTs sourced from other contracts; -`referringOf`: Get the referring list of an rNFT; -`referredOf`: Get the referred list of an rNFT. - -## Rationale - -This standard is intended to establish the referable DAG for queries on cross-relationship and accordingly provide the simplest functions. It provides advantages as follows. - -*Clear ownership inheritance*: This standard extends the static NFT into a virtually extensible NFT network. Artists do not have to create work isolated from others. The ownership inheritance avoids reinventing the same wheel. - -*Incentive Compatibility*: This standard clarifies the referable relationship across different NFTs, helping to integrate multiple up-layer incentive models for both original NFT owners and new creators. - -*Easy Integration*: This standard makes it easier for the existing token standards or third-party protocols. For instance, the rNFT can be applied to rentable scenarios (cf. [ERC-5006](./eip-5006.md) to build a hierarchical rental market, where multiple users can rent the same NFT during the same time or one user can rent multiple NFTs during the same duration). - -*Scalable Interoperability* From March 26th 2023, this standard has been stepping forward by enabling cross-contract references, giving a scalable adoption for the broader public with stronger interoperability. - -## Backwards Compatibility - -This standard can be fully [ERC-721](./eip-721.md) compatible by adding an extension function set. - -## Test Cases - -Test cases are included in [ERC_5521.test.js](../assets/eip-5521/ERC_5521.test.js) - -## Reference Implementation - -```solidity - -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC_5521.sol"; - -contract ERC_5521 is ERC721, IERC_5521, TargetContract { - - struct Relationship { - mapping (address => uint256[]) referring; - mapping (address => uint256[]) referred; - uint256 createdTimestamp; // unix timestamp when the rNFT is being created - } - - mapping (uint256 => Relationship) internal _relationship; - address contractOwner = address(0); - - mapping (uint256 => address[]) private referringKeys; - mapping (uint256 => address[]) private referredKeys; - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) { - contractOwner = msg.sender; - } - - function safeMint(uint256 tokenId, address[] memory addresses, uint256[][] memory _tokenIds) public { - // require(msg.sender == contractOwner, "ERC_rNFT: Only contract owner can mint"); - _safeMint(msg.sender, tokenId); - setNode(tokenId, addresses, _tokenIds); - } - - /// @notice set the referred list of an rNFT associated with different contract addresses and update the referring list of each one in the referred list - /// @param tokenIds array of rNFTs, recommended to check duplication at the caller's end - function setNode(uint256 tokenId, address[] memory addresses, uint256[][] memory tokenIds) public virtual override { - require( - addresses.length == tokenIds.length, - "Addresses and TokenID arrays must have the same length" - ); - for (uint i = 0; i < tokenIds.length; i++) { - if (tokenIds[i].length == 0) { revert("ERC_5521: the referring list cannot be empty"); } - } - setNodeReferring(addresses, tokenId, tokenIds); - setNodeReferred(addresses, tokenId, tokenIds); - } - - /// @notice set the referring list of an rNFT associated with different contract addresses - /// @param _tokenIds array of rNFTs associated with addresses, recommended to check duplication at the caller's end - function setNodeReferring(address[] memory addresses, uint256 tokenId, uint256[][] memory _tokenIds) private { - require(_isApprovedOrOwner(msg.sender, tokenId), "ERC_5521: transfer caller is not owner nor approved"); - - Relationship storage relationship = _relationship[tokenId]; - - for (uint i = 0; i < addresses.length; i++) { - if (relationship.referring[addresses[i]].length == 0) { referringKeys[tokenId].push(addresses[i]); } // Add the address if it's a new entry - relationship.referring[addresses[i]] = _tokenIds[i]; - } - - relationship.createdTimestamp = block.timestamp; - emitEvents(tokenId, msg.sender); - } - - /// @notice set the referred list of an rNFT associated with different contract addresses - /// @param _tokenIds array of rNFTs associated with addresses, recommended to check duplication at the caller's end - function setNodeReferred(address[] memory addresses, uint256 tokenId, uint256[][] memory _tokenIds) private { - for (uint i = 0; i < addresses.length; i++) { - if (addresses[i] == address(this)) { - for (uint j = 0; j < _tokenIds[i].length; j++) { - if (_relationship[_tokenIds[i][j]].referred[addresses[i]].length == 0) { referredKeys[_tokenIds[i][j]].push(addresses[i]); } // Add the address if it's a new entry - Relationship storage relationship = _relationship[_tokenIds[i][j]]; - - require(tokenId != _tokenIds[i][j], "ERC_5521: self-reference not allowed"); - if (relationship.createdTimestamp >= block.timestamp) { revert("ERC_5521: the referred rNFT needs to be a predecessor"); } // Make sure the reference complies with the timing sequence - - relationship.referred[address(this)].push(tokenId); - emitEvents(_tokenIds[i][j], ownerOf(_tokenIds[i][j])); - } - } else { - TargetContract targetContractInstance = TargetContract(addresses[i]); - targetContractInstance.setNodeReferredExternal(address(this), tokenId, _tokenIds[i]); - } - } - } - - /// @notice set the referred list of an rNFT associated with different contract addresses - /// @param _tokenIds array of rNFTs associated with addresses, recommended to check duplication at the caller's end - function setNodeReferredExternal(address _address, uint256 tokenId, uint256[] memory _tokenIds) external { - for (uint i = 0; i < _tokenIds.length; i++) { - if (_relationship[_tokenIds[i]].referred[_address].length == 0) { referredKeys[_tokenIds[i]].push(_address); } // Add the address if it's a new entry - Relationship storage relationship = _relationship[_tokenIds[i]]; - - require(_address != address(this), "ERC_5521: this must be an external contract address"); - if (relationship.createdTimestamp >= block.timestamp) { revert("ERC_5521: the referred rNFT needs to be a predecessor"); } // Make sure the reference complies with the timing sequence - - relationship.referred[_address].push(tokenId); - emitEvents(_tokenIds[i], ownerOf(_tokenIds[i])); - } - } - - /// @notice Get the referring list of an rNFT - /// @param tokenId The considered rNFT, _address The corresponding contract address - /// @return The referring mapping of an rNFT - function referringOf(address _address, uint256 tokenId) external view virtual override(IERC_5521, TargetContract) returns (address[] memory, uint256[][] memory) { - address[] memory _referringKeys; - uint256[][] memory _referringValues; - - if (_address == address(this)) { - require(_exists(tokenId), "ERC_5521: token ID not existed"); - (_referringKeys, _referringValues) = convertMap(tokenId, true); - } else { - TargetContract targetContractInstance = TargetContract(_address); - (_referringKeys, _referringValues) = targetContractInstance.referringOf(_address, tokenId); - } - return (_referringKeys, _referringValues); - } - - /// @notice Get the referred list of an rNFT - /// @param tokenId The considered rNFT, _address The corresponding contract address - /// @return The referred mapping of an rNFT - function referredOf(address _address, uint256 tokenId) external view virtual override(IERC_5521, TargetContract) returns (address[] memory, uint256[][] memory) { - address[] memory _referredKeys; - uint256[][] memory _referredValues; - - if (_address == address(this)) { - require(_exists(tokenId), "ERC_5521: token ID not existed"); - (_referredKeys, _referredValues) = convertMap(tokenId, false); - } else { - TargetContract targetContractInstance = TargetContract(_address); - (_referredKeys, _referredValues) = targetContractInstance.referredOf(_address, tokenId); - } - return (_referredKeys, _referredValues); - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC_5521).interfaceId - || interfaceId == type(TargetContract).interfaceId - || super.supportsInterface(interfaceId); - } - - // @notice Emit an event of UpdateNode - function emitEvents(uint256 tokenId, address sender) private { - (address[] memory _referringKeys, uint256[][] memory _referringValues) = convertMap(tokenId, true); - (address[] memory _referredKeys, uint256[][] memory _referredValues) = convertMap(tokenId, false); - - emit UpdateNode(tokenId, sender, _referringKeys, _referringValues, _referredKeys, _referredValues); - } - - // @notice Convert a specific `local` token mapping to a key array and a value array - function convertMap(uint256 tokenId, bool isReferring) private view returns (address[] memory, uint256[][] memory) { - Relationship storage relationship = _relationship[tokenId]; - - address[] memory returnKeys; - uint256[][] memory returnValues; - - if (isReferring) { - returnKeys = referringKeys[tokenId]; - returnValues = new uint256[][](returnKeys.length); - for (uint i = 0; i < returnKeys.length; i++) { - returnValues[i] = relationship.referring[returnKeys[i]]; - } - } else { - returnKeys = referredKeys[tokenId]; - returnValues = new uint256[][](returnKeys.length); - for (uint i = 0; i < returnKeys.length; i++) { - returnValues[i] = relationship.referred[returnKeys[i]]; - } - } - return (returnKeys, returnValues); - } -} - -``` - -## Security Considerations - -The `createdTimestamp` only covers the block-level timestamp (based on block headers), which does not support fine-grained comparisons such as transaction-level. - -The change of ownership has nothing to do with the reference relationship. Normally, the distribution of profits complies to the aggreement when the NFT was being created regardless of the change of ownership unless specified in the agreement. - -Referring a token will not refer its descendants by default. In the case that only a specific child token gets referred, it means the privity of contract will involve nobody other than the owner of this specific child token. Alternatively, a chain-of-reference all the way from the root token to a specific very bottom child token (from root to leaf) can be constructured and recorded in the `referring` to explicitly define the distribution of profits. - -The `safeMint` function has been deliberately designed to allow unrestricted minting and relationship setting, akin to the open referencing system seen in platforms like Google Scholar. This decision facilitates strong flexibility, enabling any user to create and define relationships between tokens without centralized control. While this design aligns with the intended openness of the system, it inherently carries certain risks. Unauthorized or incorrect references can be created, mirroring the challenges faced in traditional scholarly referencing where erroneous citations may occur. Additionally, the open nature may expose the system to potential abuse by malicious actors, who might manipulate relationships or inflate token supply. It is important to recognize that these risks are not considered design flaws but intentional trade-offs, balancing the system's flexibility against potential reliability concerns. Stakeholders should be aware that the on-chain data integrity guarantees extend only to what has been recorded on the blockchain and do not preclude the possibility of off-chain errors or manipulations. Thus, users and integrators should exercise caution and judgment in interpreting and using the relationships and other data provided by this system. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5521.md diff --git a/EIPS/eip-5528.md b/EIPS/eip-5528.md index cb822cc106a584..dec62d5e6ec673 100644 --- a/EIPS/eip-5528.md +++ b/EIPS/eip-5528.md @@ -1,266 +1 @@ ---- -eip: 5528 -title: Refundable Fungible Token -description: Allows refunds for EIP-20 tokens by escrow smart contract -author: StartfundInc (@StartfundInc) -discussions-to: https://ethereum-magicians.org/t/eip-5528-refundable-token-standard/10494 -status: Final -type: Standards Track -category: ERC -created: 2022-08-16 -requires: 20 ---- - -## Abstract - -This standard is an extension of [EIP-20](./eip-20.md). This specification defines a type of escrow service with the following flow: - -- The seller issues tokens. -- The seller creates an escrow smart contract with detailed escrow information like contract addresses, lock period, exchange rate, additional escrow success conditions, etc. -- The seller funds seller tokens to the *Escrow Contract*. -- Buyers fund buyer tokens which are pre-defined in the *Escrow Contract*. -- When the escrow status meets success, the seller can withdraw buyer tokens, and buyers can withdraw seller tokens based on exchange rates. -- Buyers can withdraw (or refund) their funded token if the escrow process is failed or is in the middle of the escrow process. - -## Motivation - -Because of the pseudonymous nature of cryptocurrencies, there is no automatic recourse to recover funds that have already been paid. - -In traditional finance, trusted escrow services solve this problem. In the world of decentralized cryptocurrency, however, it is possible to implement an escrow service without a third-party arbitrator. This standard defines an interface for smart contracts to act as an escrow service with a function where tokens are sent back to the original wallet if the escrow is not completed. - -## Specification - -There are two types of contract for the escrow process: - -- *Payable Contract*: The sellers and buyers use this token to fund the *Escrow Contract*. This contract MUST override [EIP-20](./eip-20.md) interfaces. -- *Escrow Contract*: Defines the escrow policies and holds *Payable Contract*'s token for a certain period. This contract does not requires override [EIP-20](./eip-20.md) interfaces. - -### Methods - -#### `constructor` - -The *Escrow Contract* demonstrates details of escrow policies as none-mutable matter in constructor implementation. - -The *Escrow Contract* MUST define the following policies: - -- Seller token contract address -- Buyer token contract address - -The *Escrow Contract* MAY define the following policies: - -- Escrow period -- Maximum (or minimum) number of investors -- Maximum (or minimum) number of tokens to fund -- Exchange rates of seller/buyer token -- KYC verification of users - -#### `escrowFund` - -Funds `_value` amount of tokens to address `_to`. - -In the case of *Escrow Contract*: - - - `_to` MUST be the user address. - - `msg.sender` MUST be the *Payable Contract* address. - - MUST check policy validations. - -In the case of *Payable Contract*: - - - The address `_to` MUST be the *Escrow Contract* address. - - MUST call the same function of the *Escrow Contract* interface. The parameter `_to` MUST be `msg.sender` to recognize the user address in the *Escrow Contract*. - -```solidity -function escrowFund(address _to, uint256 _value) public returns (bool) -``` - -#### `escrowRefund` - -Refunds `_value` amount of tokens from address `_from`. - -In the case of *Escrow Contract*: - - - `_from` MUST be the user address. - - `msg.sender` MUST be the *Payable Contract* address. - - MUST check policy validations. - -In the case of *Payable Contract*: - - - The address `_from` MUST be the *Escrow Contract* address. - - MUST call the same function of the *Escrow Contract* interface. The parameter `_from` MUST be `msg.sender` to recognize the user address in the *Escrow Contract*. - -```solidity -function escrowRefund(address _from, uint256 _value) public returns (bool) -``` - -#### `escrowWithdraw` - -Withdraws funds from the escrow account. - -In the case of *Escrow Contract*: - - - MUST check the escrow process is completed. - - MUST send the remaining balance of seller and buyer tokens to `msg.sender`'s seller and buyer contract wallets. - -In the case of *Payable Contract*, it is optional. - -```solidity -function escrowWithdraw() public returns (bool) -``` - -### Example of interface - -This example demonstrates simple exchange of one seller and one buyer in one-to-one exchange rates. - -```solidity -pragma solidity ^0.4.20; - -interface IERC5528 { - - function escrowFund(address _to, uint256 _value) public returns (bool); - - function escrowRefund(address _from, uint256 _value) public returns (bool); - - function escrowWithdraw() public returns (bool); - -} - -contract PayableContract is IERC5528, IERC20 { - /* - General ERC20 implementations - */ - - function _transfer(address from, address to, uint256 amount) internal { - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - _balances[from] = fromBalance - amount; - _balances[to] += amount; - } - - function transfer(address to, uint256 amount) public returns (bool) { - address owner = msg.sender; - _transfer(owner, to, amount); - return true; - } - - function escrowFund(address _to, uint256 _value) public returns (bool){ - bool res = IERC5528(to).escrowFund(msg.sender, amount); - require(res, "Fund Failed"); - _transfer(msg.sender, to, amount); - return true; - } - - function escrowRefund(address _from, uint256 _value) public returns (bool){ - bool res = IERC5528(_from).escrowRefund(msg.sender, _value); - require(res, "Refund Failed"); - _transfer(_from, msg.sender, _value); - return true; - } -} - -contract EscrowContract is IERC5528 { - - enum State { Inited, Running, Success, Closed } - struct BalanceData { - address addr; - uint256 amount; - } - - address _addrSeller; - address _addrBuyer; - BalanceData _fundSeller; - BalanceData _fundBuyer; - EscrowStatus _status; - - constructor(address sellerContract, address buyerContract){ - _addrSeller = sellerContract; - _addrBuyer = buyerContract; - _status = State.Inited; - } - - function escrowFund(address _to, uint256 _value) public returns (bool){ - if(msg.sender == _addrSeller){ - require(_status.state == State.Running, "must be running state"); - _fundSeller.addr = _to; - _fundSeller.amount = _value; - _status = State.Success; - }else if(msg.sender == _addrBuyer){ - require(_status.state == State.Inited, "must be init state"); - _fundBuyer.addr = _to; - _fundBuyer.amount = _value; - _status = State.Running; - }else{ - require(false, "Invalid to address"); - } - return true; - } - - function escrowRefund(address _from, uint256 amount) public returns (bool){ - require(_status.state == State.Running, "refund is only available on running state"); - require(msg.sender == _addrBuyer, "invalid caller for refund"); - require(_fundBuyer.addr == _from, "only buyer can refund"); - require(_fundBuyer.amount >= amount, "buyer fund is not enough to refund"); - _fundBuyer.amount = _fundBuyer.amount - amount - return true; - } - - function escrowWithdraw() public returns (bool){ - require(_status.state == State.Success, "withdraw is only available on success state"); - uint256 common = MIN(_fundBuyer.amount, _fundSeller.amount); - - if(common > 0){ - _fundBuyer.amount = _fundBuyer.amount - common; - _fundSeller.amount = _fundSeller.amount - common; - - // Exchange - IERC5528(_addrSeller).transfer(_fundBuyer.addr, common); - IERC5528(_addrBuyer).transfer(_fundSeller.addr, common); - - // send back the remaining balances - if(_fundBuyer.amount > 0){ - IERC5528(_addrBuyer).transfer(_fundBuyer.addr, _fundBuyer.amount); - } - if(_fundSeller.amount > 0){ - IERC5528(_addrSeller).transfer(_fundSeller.addr, _fundSeller.amount); - } - } - - _status = State.Closed; - } - -} - -``` - -## Rationale - -The interfaces cover the escrow operation's refundable issue. - -The suggested 3 functions (`escrowFund`, `escrowRefund` and `escrowWithdraw`) are based on `transfer` function in EIP-20. - -`escrowFund` send tokens to the *Escrow Contract*. The *Escrow Contract* can hold the contract in the escrow process or reject tokens if the policy does not meet. - -`escrowRefund` can be invoked in the middle of the escrow process or when the escrow process fails. - -`escrowWithdraw` allows users (sellers and buyers) to transfer tokens from the escrow account. When the escrow process completes, the seller can get the buyer's token, and the buyers can get the seller's token. - -## Backwards Compatibility - -The *Payable Contract* which implements this EIP is fully backward compatible with the [EIP-20](./eip-20.md) specification. - -## Test Cases - -[Unit test example by truffle](../assets/eip-5528/truffule-test.js). - -This test case demonstrates the following conditions for exchanging seller/buyer tokens. - -- The exchange rate is one-to-one. -- If the number of buyers reaches 2, the escrow process will be terminated(success). -- Otherwise (not meeting success condition yet), buyers can refund (or withdraw) their funded tokens. - -## Security Considerations - -Since the *Escrow Contract* controls seller and buyer rights, flaws within the *Escrow Contract* will directly lead to unexpected behavior and potential loss of funds. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5528.md diff --git a/EIPS/eip-5539.md b/EIPS/eip-5539.md index 88349c7d2e7a9b..eee3cb25c7dfe0 100644 --- a/EIPS/eip-5539.md +++ b/EIPS/eip-5539.md @@ -1,252 +1 @@ ---- -eip: 5539 -title: Revocation List Registry -description: Registry of revocation lists for revoking arbitrary data. -author: Philipp Bolte (@strumswell), Lauritz Leifermann (@lleifermann), Dennis von der Bey (@DennisVonDerBey) -discussions-to: https://ethereum-magicians.org/t/eip-5539-revocation-list-registry/10573 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-26 -requires: 712 ---- - -## Abstract -This EIP proposes a set of methods and standards for a role-based registry of indicators aimed for usage in revocations. - -## Motivation -Revocation is a universally needed construct both in the traditional centralized and decentralized credential attestation. This EIP aims to provide an interface to standardize a decentralized approach to managing and resolving revocation states in a contract registry. - -The largest problem with traditional revocation lists is the centralized aspect of them. Most of the world's CRLs rely on HTTP servers as well as caching and are therefore vulnerable to known attack vectors in the traditional web space. This aspect severely weakens the underlying strong asymmetric key architecture in current PKI systems. - -In addition, issuers in existing CRL approaches are required to host an own instance of their public revocation list, as shared or centralized instances run the risk of misusage by the controlling entity. -This incentivizes issuers to shift this responsibility to a third party, imposing the risk of even more centralization of the ecosystem (see Cloudflare, AWS). -Ideally, issuers should be able to focus on their area of expertise, including ownership of their revocable material, instead of worrying about infrastructure. - -We see value in a future of the Internet where anyone can be an issuer of verifiable information. This proposal lays the groundwork for anyone to also own the lifecycle of this information to build trust in ecosystems. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -This EIP specifies a contract called `EthereumRevocationRegistry` that is deployed once and may then be commonly used by everyone. By default, an Ethereum address **MAY** own and manage a multitude of revocation lists in a namespace that **MUST** contain the revocation states for a set of revocation keys. - -An owner of a namespace **MAY** allow delegates to manage one or more of its revocation lists. Delegates **MUST** be removable by the respective list's owner. In certain situations, an owner **MAY** also want to transfer a revocation list in a namespace and its management rights to a new owner. - -### Definitions -- `namespace`: A namespace is a representation of an Ethereum address inside the registry that corresponds to its owners address. All revocation lists within a namespace are initially owned by the namespace's owner address. -- `revocation list`: A namespace can contain a number of revocation lists. Each revocation list is identified by a unique key of the type bytes32 that can be used to address it in combination with the namespace address. -- `revocation key`: A revocation list can contain a number of revocation keys of the type bytes32. In combination with the namespace address and the revocation list key, it resolves to a boolean value that indicates whether the revocation key is revoked or not. -- `owner`: An Ethereum address that has modifying rights to revocation lists within its own and possibly foreign namespaces. An owner can give up modifying rights of revocation lists within its namespace by transferring ownership to another address. -- `delegate`: An Ethereum address that received temporary access to a revocation list in a namespace. It has to be granted by the current owner of the revocation list in question. - -### Revocation Management - -#### isRevoked -**MUST** implement a function that returns the revocation status of a particular revocation key in a namespace's revocation list. It **MAY** also respect the revocation lists revocation status. -```solidity -function isRevoked(address namespace, bytes32 list, bytes32 key) public view returns (bool); -``` - -#### changeStatus -**MUST** implement a function to change the revocation status of a particular revocation key in a namespace's revocation list -```solidity -function changeStatus(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey) public; -``` - -#### changeStatusSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implements a function to change the revocation status of a particular revocation key in a namespace's revocation list with a raw signature. -```solidity -function changeStatusSigned(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey, address signer, bytes calldata signature) public; -``` - -#### changeStatusDelegated -**OPTIONAL** implements a function to change the revocation status of a particular revocation key in a namespace's revocation list by a revocation list's delegate. -```solidity -function changeStatusDelegated(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey) public; -``` - -#### changeStatusDelegatedSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implements a function to change the revocation status of a particular revocation key in a namespace's revocation list with a raw signature. -```solidity -function changeStatusDelegatedSigned(bool revoked, address namespace, bytes32 revocationList, bytes32 revocationKey, address signer, bytes calldata signature) public; -``` - -#### changeStatusesInList -**OPTIONAL** implements a function to change multiple revocation statuses in a namespace's revocation list at once. -```solidity -function changeStatusesInList(bool[] memory revoked, address namespace, bytes32 revocationList, bytes32[] memory revocationKeys) public; -``` - -#### changeStatusesInListSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implements a function to change multiple revocation statuses in a namespace's revocation list at once with a raw signature. -```solidity -function changeStatusesInListSigned(bool[] memory revoked, address namespace, bytes32 revocationList, bytes32[] memory revocationKeys, address signer, bytes calldata signature) public; -``` - -#### changeStatusesInListDelegated -**OPTIONAL** implements a function to change multiple revocation statuses in a namespace's revocation list at once by a revocation list's delegate. -```solidity -function changeStatusesInListDelegated(bool[] memory revoked, address namespace, bytes32 revocationList, bytes32[] memory revocationKeys) public; -``` - -#### changeStatusesInListDelegatedSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implements a function to change multiple revocation statuses in a namespace's revocation list at once with a raw signature generated by a revocation list's delegate. -```solidity -function changeStatusesInListDelegatedSigned(bool[] memory revoked, address namespace, bytes32 revocationList, bytes32[] memory revocationKeys, address signer, bytes calldata signature) public; -``` - -### Revocation List Management - -#### -**OPTIONAL** implements a function that returns the revocation status of a particular revocation list in a namespace. -```solidity -function listIsRevoked(address namespace, bytes32 revocationList) view public returns (bool); -``` - -#### changeListStatus -**OPTIONAL** implements a function to change the revocation of a revocation list itself. If a revocation list is revoked, all its keys are considered revoked as well. -```solidity -function changeListStatus(bool revoked, address namespace, bytes32 revocationList) public; -``` - -#### changeListStatusSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implements a function to change the revocation of a revocation list itself with a raw signature. If a revocation list is revoked, all its keys are considered revoked as well. -```solidity -function changeListStatusSigned(bool revoked, address namespace, bytes32 revocationList, address signer, bytes calldata signature) public; -``` - -### Owner management - -#### changeListOwner -**OPTIONAL** implement a function to change the revocation status of a revocation list. If a revocation list is revoked, all keys in it are considered revoked. -```solidity -function changeListOwner(address newOwner, address namespace, bytes32 revocationList) public; -``` - -#### changeListOwnerSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implement a function to change the revocation status of a revocation list with a raw signature. If a revocation list is revoked, all keys in it are considered revoked. -```solidity -function changeListOwnerSigned(address newOwner, address namespace, bytes32 revocationList, address signer, bytes calldata signature) public; -``` - -### Delegation management - -#### addListDelegate -**OPTIONAL** implements a function to add a delegate to an owner's revocation list in a namespace. -```solidity -function addListDelegate(address delegate, address namespace, bytes32 revocationList) public; -``` - -#### addListDelegateSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implements a function to add a delegate to an owner's revocation list in a namespace with a raw signature. -```solidity -function addListDelegateSigned(address delegate, address namespace, bytes32 revocationList, address signer, bytes calldata signature) public; -``` - -#### removeListDelegate -**OPTIONAL** implements a function to remove a delegate from an owner's revocation list in a namespace. -```solidity -function removeListDelegate(address delegate, address owner, bytes32 revocationList) public; -``` - -#### removeListDelegateSigned ([see Meta Transactions](#MetaTransactions)) -**OPTIONAL** implements a function to remove a delegate from an owner's revocation list in a namespace with a raw signature. -```solidity -function removeListDelegateSigned(address delegate, address namespace, bytes32 revocationList, address signer, bytes calldata signature) public; -``` - -### Events - -#### RevocationStatusChanged -**MUST** be emitted when `changeStatus`, `changeStatusSigned`, `changeStatusDelegated`, `changeStatusDelegatedSigned`, `changeStatusesInList`, `changeStatusesInListSigned`, `changeStatusesInListDelegated`, or `changeStatusesInListDelegatedSigned` was successfully executed. - -```solidity -event RevocationStatusChanged( - address indexed namespace, - bytes32 indexed revocationList, - bytes32 indexed revocationKey, - bool revoked -); -``` - -#### RevocationListOwnerChanged -**MUST** be emitted when `changeListOwner` or `changeListOwnerSigned` was successfully executed. - -```solidity -event RevocationListOwnerChanged( - address indexed namespace, - bytes32 indexed revocationList, - address indexed newOwner -); -``` - -#### RevocationListDelegateAdded -**MUST** be emitted when `addListDelegate` or `addListDelegateSigned` was successfully executed. - -```solidity -event RevocationListDelegateAdded( - address indexed namespace, - bytes32 indexed revocationList, - address indexed delegate -); -``` - -#### RevocationListDelegateRemoved -**MUST** be emitted when `removeListDelegate` or `removeListDelegateSigned` was successfully executed. - -```solidity -event RevocationListDelegateRemoved( - address indexed namespace, - bytes32 indexed revocationList, - address indexed delegate -); -``` - -#### RevocationListStatusChanged -**MUST** be emitted when `changeListStatus` or `changeListStatusSigned` was successfully executed. - -```solidity -event RevocationListStatusChanged( - address indexed namespace, - bytes32 indexed revocationlist, - bool revoked -); -``` - -### Meta Transactions - -This section uses the following terms: -- **`transaction signer`**: An Ethereum address that signs arbitrary data for the contract to execute **BUT** does not commit the transaction. -- **`transaction sender`**: An Ethereum address that takes signed data from a **transaction signer** and commits it wrapped with its own signature to the smart contract. - -An address (**transaction signer**) **MAY** be able to deliver a signed payload off-band to another address (**transaction sender**) that initiates the Ethereum interaction with the smart contract. The signed payload **MUST** be limited to be used only once ([Signed Hash](#SignedHash) + [nonces](#Nonce)). - -#### Signed Hash - -The signature of the **transaction signer** **MUST** conform [EIP-712](./eip-712.md). This helps users understand what the payload they're signing consists of & it improves the protection against replay attacks. - -#### Nonce - -This EIP **RECOMMENDS** the use of a **dedicated nonce mapping** for meta transactions. If the signature of the **transaction sender** and its meta contents are verified, the contract increases a nonce for this **transaction signer**. This effectively removes the possibility for any other sender to execute the same transaction again with another wallet. - -## Rationale - -### Why the concept of namespaces? -This provides every Ethereum address a reserved space, without the need to actively claim it in the contract. Initially addresses only have owner access in their own namespace. - -### Why does a namespace always represent the initial owner address? -The change of an owner of a list shouldn't break the link to a revocation key in it, as already existing off-chain data may depend on it. - -## Backwards Compatibility -No backward compatibility issues were found. - -## Security Considerations - -### Meta Transactions -The signature of signed transactions could potentially be replayed on different chains or deployed versions of the registry implementing this ERC. This security consideration is addressed by the usage of [EIP-712](./eip-712.md) - -### Rights Management -The different roles and their inherent permissions are meant to prevent changes from unauthorized entities. The revocation list owner should always be in complete control over its revocation list and who has writing access to it. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5539.md diff --git a/EIPS/eip-5553.md b/EIPS/eip-5553.md index 044b43c14d8f2b..aaac9ee45f002c 100644 --- a/EIPS/eip-5553.md +++ b/EIPS/eip-5553.md @@ -1,256 +1 @@ ---- -eip: 5553 -title: Representing IP and its Royalty Structure -description: A way of representing intellectual property and its respective royalty structure on chain -author: Roy Osherove (@royosherove) -discussions-to: https://ethereum-magicians.org/t/eip-5553-representing-intellectual-property-on-chain-with-royalty-rights/10551 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-17 -requires: 20, 721 ---- - -## Abstract -This proposal introduces a generic way to represent intellectual property on chain, along with a refined royalty representation mechanism and associated metadata link. This standard is not associated with a specific type of IP and could represent many types of IP, such as musical IP, videos, books, images, and more. -The standard is kept very generic to allow the industry to evolve new ecosystems that can all rely on the same basic standard at their core. - -This standard allows market participants to: -1) Observe the canonical on-chain representation of an intellectual property -2) Discover its attached metadata -3) Discover its related royalty structure -4) This will enable building registration, licensing, and payout mechanisms for intellectual property assets in the future. - -## Motivation - -There is no accepted standard mechanism to license intellectual property or to represent it, except using traditional NFTs. However, regular NFTs only represent a collectible item use case and cannot easily represent more complicated use cases of licensing IP for different types of uses. -We can enable such licensing mechanisms if we can: - -1) Declare that IP exists, SEPARATELY from its purchase ability -2) Declare possibly multiple interested parties to be paid for such IP - -For 1, no standard exists today. - -For 2, traditional split standards exist based on NFT purchases or through mechanisms like 0xsplits. While these solve the main problem, they do not contain the ability to name multiple types of collaboration participants. - - - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -**contracts that want to represent IP on chain MUST implement [EIP-721](./eip-721.md) AND this Proposal** - -This standard extends [EIP-721](./eip-721.md) with the following `IIPRepresentation` (IPR for short) interface. -Implementers of this standard **MUST** have all of the following functions: - -### royaltyPortionTokens() function -This function MUST return an array of addresses related to [EIP-20](./eip-20.md) tokens that MUST represent royalty portions to different types of interested parties. These royalty portion tokens represent a more granular and streamlined way to declare royalty splits for multiple collaboration participants for the creation of the IP. - -For example, for a musical IP, we might have two tokens representing the composition/writing/publishing royalty portion side and the recording/master side. These royalty portion tokens are distributed to the collaboration participants and can later be queried by the various holders to distribute royalties. I.e., if one holds 10% of a royalty portion token, that holder will get 10% of the financial distribution related to that type of royalty. - -### metadataURI() function -This function MUST return the URI to a metadata file containing any required metadata for the IP or an empty string. Each IP type MAY implement its metadata standard, defined separately. The file MUST be hosted in IPFS, Arweave, or other decentralized content-addressable systems in which the file's contents are not changeable without changing the URI. - -### changeMetadataURI() function -This function allows changing the metadata URI to point to a new version of the metadata file. Calling this function MUST trigger the event `MetadataChanged` in case of success. - -### ledger() function -This function MUST return the registry or registrar contract address or an EOA account that initialized the IP and associated royalty tokens. An IP representation MAY be registered in multiple places by different actors for different purposes. This function enables market participants to discover which registry mechanism is the parent of the IP and might have special access rights to manage the IP. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.9; -import '@openzeppelin/contracts/interfaces/IERC165.sol'; - - -/// -/// @dev Interface for Intellectual Property Representation -/// -interface IIPRepresentation is IERC165 { - - /// @notice Called with the new URI to an updated metadata file - /// @param _newUri - the URI pointing to a metadata file (file standard is up to the implementer) - /// @param _newFileHash - The hash of the new metadata file for future reference and verification - function changeMetadataURI(string memory _newUri, string memory _newFileHash) external ; - - /// @return array of addresses of ERC20 tokens representing royalty portion in the IP - /// @dev i.e implementing ERC5501 (IRoyaltyInterestToken interface) - function royaltyPortionTokens() external view returns (address[] memory) ; - - /// @return the address of the contract or EOA that initialized the IP registration - /// @dev i.e., a registry or registrar, to be implemented in the future - function ledger() external view returns (address) ; - - /// @return the URI of the current metadata file for the II P - function metadataURI() external view returns (string memory) ; - - /// @dev event to be triggered whenever metadata URI is changed - /// @param byAddress the addresses that triggered this operation - /// @param oldURI the URI to the old metadata file before the change - /// @param oldFileHash the hash of the old metadata file before the change - /// @param newURI the URI to the new metadata file - /// @param newFileHash the hash of the new metadata file - event MetadaDataChanged(address byAddress, string oldURI, string oldFileHash, string newURI, string newFileHash); -} -``` - - -## Rationale - -### Returning an array of EIP-20 tokens presents a more robust royalty portions structure/ - -Current royalty implementations deal only with a single type of royalty payment: NFT sales. They also only allow a single type of royalty - i.e., Music NFTs cannot pay different people in different scenarios. -In other words, currently, a royalty split works the same way no matter what type of purchase or license deal has happened for all parties involved. - -With this proposal, multiple **types** of royalty scenarios are allowed. A classic case is the music industry, in which we have writing/composition royalties and recording/master royalties. Different licensing types will pay different percentages to different parties based on context. - -In the case of a song cover, a license payment formula can be created so that that -a) Original IP's writers get paid for using the lyrics or composition of the song -b) recording artists of the original song do not get paid since their recording is not used -c) recording artists of the new IP will get paid -d) there are no writing royalties for the creators of the cover. - -Moreover, this EIP has a single structure that connects to all types of royalty types and allows finding them more easily. -Lastly, moving EIP-20 tokens around is much easier than managing an 0xsplits contract. - -### Separating the IP contract from the collectible and licensing NFTs enables scaling licensing types -By separating the canonical version of the IP from its various licensed uses (NFT purchase, streaming, usage of art and more.), this EIP introduces a path for an ecosystem of various license types and payment distributions to evolve. -In other words, when people use this scheme, they will not start by creating a music NFT or art NFT; they start by creating the IP Representation and then create types of licenses or collectibles for it, each as its own sellable NFT. - -### A single pointer to the IP's metadata -The IPR points to metadata housed in IPFS or Arweave and allows changing it and keeping track of the changes in a simple and standard way. Today the only metadata standard is NFT metadata extension, but it is impossible to know to which standard the document adheres. With different IP types, different metadata standards for different IP types can be formulated and have a simple, easy place to discover attached metadata. - -## Reference Implementation - -#### Implementing a Musical IP Representation (MIPR for short) based on IIPRepresentation -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.9; -import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; -import "./interfaces/IIPRepresentation.sol"; -import "./interfaces/Structs.sol"; - - -contract MusicalIP is ERC721, IIPRepresentation { - address public songLedger; - address public compToken; - address public recToken; - string public metadataURI; - string public fileHash; - uint256 public tokenId; - bool public activated =false; - - function supportsInterface(bytes4 interfaceId) public view virtual override( ERC721, IERC165) returns (bool) { - return - interfaceId == type(IIPRepresentation).interfaceId || - super.supportsInterface(interfaceId); - } - - function getInterfaceId() public pure returns (bytes4){ - return type(IIPRepresentation).interfaceId; - } - - constructor ( - uint256 _tokenId, - address _songLedger, - SongMintingParams memory _params, - address _compAddress, - address _recAddress - ) - ERC721(_params.shortName, _params.symbol){ - - songLedger = _songLedger; - compToken = _compAddress; - recToken = _recAddress; - metadataURI = _params.metadataUri; - fileHash = _params.fileHash; - tokenId = _tokenId; - - _safeMint(_songLedger, _tokenId); - emit Minted(_params.shortName,_songLedger,_compAddress,_recAddress,_msgSender(),tokenId,_params.metadataUri); - } - - function changeMetadataURI(string memory _newURI,string memory _newFileHash) public - { - string memory oldURI = metadataURI; - string memory oldHash = fileHash; - metadataURI = _newURI; - fileHash = _newFileHash; - - emit MetadataChanged(oldURI, oldHash,_newURI,_newFileHash); - } - - function royaltyPortionTokens() external view returns (address[] memory) { - address[] memory items = new address[](2); - items[0] = compToken; - items[1] = recToken; - return items; - } - function ledger() external view returns (address) { - return songLedger; - } - - event MetadataChanged( - string oldUri, string oldFileHash, - string newUri, string newFileHash - ); - event Minted( - string abbvName, - address ledger, - address compToken, - address recToken, - address creator, - uint256 tokenId, - string metadataUri - ); -} - - - -``` - -#### Deploying a new Musical IP using a simple song registry contract - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.9; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "./MusicalIP.sol"; -import "./CompositionRoyaltyToken.sol"; -import "./RecordingRoyaltyToken.sol"; - - -contract SimpleSongLedger is IERC721Receiver { - using Counters for Counters.Counter; - Counters.Counter private mipIds; - function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { - return IERC721Receiver.onERC721Received.selector; - } - - function mintSong(SongMintingParams memory _params) public { - CompositionRoyaltyToken comp = new CompositionRoyaltyToken(address(this),"SONGCOMP","COMP"); - RecordingRoyaltyToken rec = new RecordingRoyaltyToken(address(this),"SONGREC","REC"); - mipIds.increment(); - - MusicalIP mip = new MusicalIP( - mipIds.current(), - address(this), - _params, - address(comp), - address(rec) - ); - } -} - - -``` -## Security Considerations - -There might be potential security challenges of attackers persuading holders of royalty portion tokens to send them those tokens and gaining royalty portion in various IPRs. However, these are not specific to royalties and are a common issue with EIP-20 tokens. - -In the case of the IP registration ownership, it will be recommended that registry contracts own the IP registration, which will be non-transferrable (account bound to the registry that created it). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5553.md diff --git a/EIPS/eip-5554.md b/EIPS/eip-5554.md index c3c01db36e15f5..bdf7b106b75df6 100644 --- a/EIPS/eip-5554.md +++ b/EIPS/eip-5554.md @@ -1,213 +1 @@ ---- -eip: 5554 -title: NFT Legal Use, Repurposing, and Remixing -description: An interface for describing and enforcing the legal use and remix of an NFT. On-chain registry of rights, attribution and derivative links. -author: Isaac Patka (@ipatka), COALA Licensing Taskforce -discussions-to: https://ethereum-magicians.org/t/eip-5999-legal-use-sharing-repurposing-and-remixing-standard-compatible-with-creative-commons/10553 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-07-07 -requires: 5218 ---- - -## Abstract - -This EIP extends any other token standard to provide: - -* Explicit rights for the token holder related to commercial exploitation, derivative works, and reproduction; -* [EIP-5218](./eip-5218.md) interface for creating, viewing, and checking the status of licenses -* Standard format for extended license information in the token metadata; -* Standard events to track off chain creation of derivative works, commercial exploitation, and reproduction; -* On chain tracking of derivative works and reproductions -* Additional required fields in the smart contract to reference the copyright owner -* Function calls for commercial exploitation, derivative works and reproduction. - -## Motivation -NFTs still face legal uncertainty, and many now realize that the rights associated with an NFT are just as important as the NFT itself. Our goal is to help the ecosystem reach clear consensus and broad understanding of what purchasers of NFTs are acquiring in terms of copyright or other rights. - -Today, purchasing the NFT of a digital work is not the same as purchasing the copyright in that work. In most cases, the NFT does not even incorporate the digital work; it only references it via a hash. Hence, the NFT holder owns a unique digital copy of the work, but does not necessarily enjoy the right to reproduce, redistribute, or otherwise exploit that work—unless explicitly provided for by the copyright owner. It typically only includes the right to privately enjoy the work and display it publicly on social media or in virtual galleries. - -We aim to create a new set of licenses with modular terms and conditions—à la Creative Commons—in order to enable artists to increase the value of their NFT by associating additional rights to them (e.g. the right to create derivative works, or to allow for the commercial usage of the underlying works). Our solution will allow for any licensed rights to be granted, only and exclusively, to the current holders of an NFT, and to be transferred automatically to the new token holders every time the NFT is being transferred. - -An on chain registry of copyrighted material will help in discovery of the rights associated with the NFTs that have been created with this protocol. - -Our current work is drafting the legalese and technical specifications. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Every contract compliant with this EIP must implement the `IERC5554` interface: - -```solidity -pragma solidity ^0.8.0; - -interface IERC5554 is IERC5218 { - - event CommercialExploitation(uint256 _tokenId, uint256 _licenseId, string _externalUri); - event ReproductionCreated(uint256 _tokenId, uint256 _licenseId, uint256 _reproductionId, address _reproduction, uint256 _reproductionTokenId); - event DerivativeCreated(uint256 _tokenId, uint256 _licenseId, uint256 _derivativeId, address _derivative, uint256 _derivativeTokenId); - - /// @notice Retrieve the copyright owner address - /// @dev Throws unless the token exists - /// @param tokenId The identifier for the queried token - /// @return address of the copyright owner - function getCopyrightOwner(uint256 tokenId) - external - virtual - returns (address); - - /// @notice Requests to log an execution of a license - /// @dev Throws unless the token issuance conditions are met - /// @param tokenId The identifier for the queried token - /// @return uint256 tracking reproduction ID - function logReproduction(uint256 tokenId, address reproduction, uint256 reproductionTokenId) - external - virtual - returns (uint256); - - /// @notice Requests to log an executions of a license - /// @dev Throws unless the token issuance conditions are met - /// @param tokenId The identifier for the queried token - /// @return uint256 tracking derivative ID - function logDerivative(uint256 tokenId, address derivative, uint256 derivativeTokenId) - external - virtual - returns (uint256); - - /// @notice Requests to log an execution of a license - /// @dev Throws unless the commercial exploitation conditions are met - /// @param tokenId The identifier for the queried token - function logCommercialExploitation(uint256 tokenId, string calldata uri) - external; - - /// @notice Retrieve the token associated with a reproduction - /// @dev Throws unless the reproduction exists - /// @param _reproductionId The identifier for the reproduction - /// @return uint256 The identifier for the token used to generate the reproduction - function getReproductionTokenId(uint256 _reproductionId) - external - view - returns (uint256); - - /// @notice Retrieve the token associated with a reproduction - /// @dev Throws unless the reproduction exists - /// @param _reproductionId The identifier for the reproduction - /// @return uint256 The identifier for the license used to generate the reproduction - function getReproductionLicenseId(uint256 _reproductionId) - external - view - returns (uint256); - - /// @notice Retrieve the token associated with a reproduction - /// @dev Throws unless the reproduction exists - /// @param _reproductionId The identifier for the derivative work - /// @return address The address of the reproduction collection - function getReproductionCollection(uint256 _reproductionId) - external - view - returns (address); - - /// @notice Retrieve the token associated with a derivative - /// @dev Throws unless the derivative exists - /// @param _derivativeId The identifier for the derivative work - /// @return uint256 The identifier for the token used to generate the derivative work - function getDerivativeTokenId(uint256 _derivativeId) - external - view - returns (uint256); - - /// @notice Retrieve the token associated with a derivative - /// @dev Throws unless the derivative exists - /// @param _derivativeId The identifier for the derivative work - /// @return uint256 The identifier for the license used to generate the derivative work - function getDerivativeLicenseId(uint256 _derivativeId) - external - view - returns (uint256); - - /// @notice Retrieve the token associated with a derivative - /// @dev Throws unless the derivative exists - /// @param _derivativeId The identifier for the derivative work - /// @return address The address of the derivative collection - function getDerivativeCollection(uint256 _derivativeId) - external - view - returns (address); - -} -``` - - - -### Token based Attribution/ Remix -On chain derivative works and reproductions -* Reproductions and derivative works are tracked in the contract. - - -### Event based attribution -For commercial exploitation or other off-chain uses of a creative work, this EIP defines events to be emitted to track the use of the work. - -```solidity -event CommercialExploitation(uint256 tokenID, string uri) - -function logCommercialExploitation(uint256 tokenId, string calldata uri) external returns bool; -``` - -#### Example: -When a token holder uses an NFT for off-chain merchandise, log a reference to the off-chain work in the event uri - -### Required fields - -```solifity -function copyrightOwner(uint256 tokenId) external returns address; -``` - -Copyright owner per tokenID. Could just be the tokenID owner in a simple use case, or something else if desired by the creator. - -## Rationale -We expand here upon the Motivation section to justify every decision made with regard to the specs of the standard: - -The `getLicenseId()` function takes a tokenID as a parameter, making it possible for different tokenID to be associated with different licensing terms. - -LicenseURI links to a content-addressed file that stipulates the terms and conditions of the license in actual legal language, so that the license can be read and understood by those who want to understand which rights are associated with the work of authorship, and which additional rights are granted through the acquisition of the NFT. - -When the license allows for the reproduction and/or for the creation of a derivative work only to the token holders, there needs to be a way to verify that the new NFT or the derivative NFT was created legitimately. The standard ensures this by enabling the current token holder to call a function, e.g. logDerivative which checks that the caller has a valid license to execute - -For commercial exploitation or other off-chain uses of a creative work, the standard implements the `logCommercialExploitation()` that makes it possible to keep track of which commercial exploitations have been made, and when. This makes it possible to verify that all commercial exploitation were legitimately done. - -The standard introduces a new field, `copyrightOwner`, which indicates the address of the current holder of the copyright in the work. If multiple copyright owners exist, a multisig address (or DAO) can be used. - -The artist address is not registered as an on-chain variable, but rather as part of the metadata, because it is an immutable field. - -If any, the parents of the work (i.e. the works that it is derived upon) must be part of the metadata information, so that people can verify that the NFT has obtained a DerivativeWork for each one of its parents. - -This licensing framework is intended to create a system to facilitate the licensing of rights that “follow the token” through a public licensing framework. This is not meant to be used for cases in which an exclusive right is licensed through a personal license to a specific actor (e.g. the copyright owner providing a third-party with the right to commercially exploit the work, regardless of whether they hold the token). This also is not designed to account for the sub-licensing case (e.g. licensing the right to one party to license third parties to engage in commercial exploitation), since this should rather be done via a personal copyright licensing scheme. - - -### Examples - -#### Bored Koalas merchandising - -Vigdís creates a PFP collection of Bored Koalas, which is subject to standard copyright restrictions: no one has the right to reproduce, distribute, communicate, commercialize or remix these works. However, she wants to give specific permissions to those who hold a NFT from the collection. She mints the collection with this EIP, introducing a conditional license that allows for the current token holder to display the Bored Koala associated with each NFT and commercialize it for the purpose of merchandising only. - -Neža has purchased one of these Bored Koalas. She wants to produce merchandising to be distributed at his blockchain conference. She goes to a print shop and asks them to make t-shirts with the Bored Koala image of the NFT she has purchased. The print shop can verify that she has the right to commercially exploit the work by verifying that they are the holder of the Bored Koala NFT, and verifying the terms of the license associated with it. (NB: this does not require a sub-license to be granted to the print shop, because the commercial exploitation implies the right to commission third parties to engage in such commercial exploitation). Neža brings the t-shirts to her conference and puts them for sale. When doing so, she calls the `logCommercialExploitation()` function from the NFT smart contract in order to track that the commercial exploitation was done at a time while she was the token holder. - -#### Musical Remix - -Matti is an up and coming songwriter in the emerging web3 music ecosystem. For the upcoming crypto conference, he creates a hit song called “Degens in the Night”. Instead of listing the song on a web2 platform, Matti mints the song as an NFT using this EIP, with a dual licensing scheme: a general public licenses that allows for the free reproduction and redistribution of the work, given proper attribution (e.g. Creative Commons BY-NC-ND) and a conditional license which allows for the token holder to remix the song, in exchange of a particular lump sum (e.g. 1ETH) and under the condition that the derivative work is released under the same licensing terms as the original work Lyyli wants to create a cover of that song, which she calls “Degens in the Parisian Night”. She purchases the NFT and mints a new derivative NFT under a new smart contract using this EIP standard. She then calls the `requestDerivativeToken()` function and send 1ETH to the original NFT smart contract, in order to request that a DerivativeToken be assigned to the new smart contract she has created. The smart contract automatically approves the request to assign a Derivative Token to the new smart contract of Lyyli. This can be used as a proof that the derivative work is indeed a legitimate work, which has been approved by the copyright owner of the original work. During the conference hundreds of other web3 music creators host a side event with Degens in the Night remixes playing until 4am. - -#### Royalties Remix - -Alice created a 3D model of a motorcycle, which she wants everyone to remix, under the condition that she gets royalty from the commercial exploitation of all derivative works. She release her work as an NFT with this EIP, with a dual licensing scheme: a general public licenses that allows for the free reproduction and redistribution of the work, given proper attribution (e.g. Creative Commons BY-NC-ND) and a conditional license which allows for the token holder to remix the song, under the condition that the derivative work is released under the same licensing terms as the original work, and that there is a split of the royalties between himself and the remixer. - -Jane wants to create a derivative work of the motorcycle. She purchases the NFT and mints a new derivative NFT under a new smart contract that uses this EIP, which includes a royalty split for Alice. She then calls the `requestDerivativeToken()` function from the original NFT smart contract in order to request that a DerivativeToken be assigned to the new smart contract she has created. Alice decided that the smart contract shall not automate the approval or rejection of the request, but rather wait for her to validate or invalidate the request, after she has verified that the design and provisions of the new smart contract, namely that it does indeed replicate the same terms and conditions as the original work and that it incorporates the proper amount of royalties. She approves the request to assign a Derivative Token to the new smart contract of Jane. When people purchase Jane’s NFT, the royalties are split to ensure the proper redistribution of the generated profit to Alice. - -## Backwards Compatibility -The interface defined in this standard is backward compatible with most NFT standards used in the Ethereum ecosystem as of this writing. - -## Security Considerations -Needs discussion. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5554.md diff --git a/EIPS/eip-5559.md b/EIPS/eip-5559.md index 471ca9fbc95c71..4fe05df87ae439 100644 --- a/EIPS/eip-5559.md +++ b/EIPS/eip-5559.md @@ -1,471 +1 @@ ---- -eip: 5559 -title: "Cross Chain Write Deferral Protocol" -description: The cross chain write deferral protocol provides a mechanism to defer the storage & resolution of mutations to off-chain handlers -author: Paul Gauvreau (@0xpaulio), Nick Johnson (@arachnid) -discussions-to: https://ethereum-magicians.org/t/eip-cross-chain-write-deferral-protocol/10576 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-06-23 -requires: 712 ---- - -## Abstract -The following standard provides a mechanism in which smart contracts can request various tasks to be resolved by an external handler. This provides a mechanism in which protocols can reduce the gas fees associated with storing data on mainnet by deferring the handling of it to another system/network. These external handlers act as an extension to the core L1 contract. - -This standard outlines a set of handler types that can be used for managing the execution and storage of mutations (tasks), as well as their corresponding tradeoffs. Each handler type has associated operational costs, finality guarantees, and levels of decentralization. By further specifying the type of handler that the mutation is deferred to, the protocol can better define how to permission and secure their system. - -This standard can be implemented in conjunction with [EIP-3668](./eip-3668) to provide a mechanism in which protocols can reside on and be interfaced through an L1 contract on mainnet, while being able to resolve and mutate data stored in external systems. - -## Motivation -[EIP-3668](./eip-3668) provides a mechanism by which off-chain lookups can be defined inside smart contracts in a transparent manner. In addition, it provides a scheme in which the resolved data can be verified on-chain. However, there lacks a standard by which mutations can be requested through the native contract, to be performed on the off-chain data. Furthermore, with the increase in L2 solutions, smart contract engineers have additional tools that can be used to reduce the storage and transaction costs of performing mutations on the Ethereum mainnet. - -A specification that allows smart contracts to defer the storage and resolution of data to external handlers facilitates writing clients agnostic to the storage solution being used, enabling new applications that can operate without knowledge of the underlying handlers associated with the contracts they interact with. - -Examples of this include: - - Allowing the management of ENS domains externally resolved on an L2 solution or off-chain database as if they were native L1 tokens. - - Allowing the management of digital identities stored on external handlers as if they were in the stored in the native L1 smart contract. - -## Specification -### Overview -There are two main handler classifications: L2 Contract and Off-Chain Database. These are determined based off of where the handler is deployed. The handler classifications are used to better define the different security guarantees and requirements associated with its deployment. - -From a high level: -- Handlers hosted on an L2 solution are EVM compatible and can use attributes native to the Ethereum ecosystem (such as address) to permission access. -- Handlers hosted on an Off-Chain Database require additional parameters and signatures to correctly enforce the authenticity and check the validity of a request. - -A deferred mutation can be handled in as little as two steps. However, in some cases the mutation might be deferred multiple times. - -1. Querying or sending a transaction to the contract -2. Querying or sending a transaction to the handler using the parameters provided in step 1 - -In step 1, a standard blockchain call operation is made to the contract. The contract either performs the operation as intended or reverts with an error that specifies the type of handler that the mutation is being deferred to and the corresponding parameters required to perform the subsequent mutation. There are two types of errors that the contract can revert with, but more may be defined in other EIPs: - -- `StorageHandledByL2(chainId, contractAddress)` -- `StorageHandledByOffChainDatabase(sender, url, data)` - -In step 2, the client builds and performs a new request based off of the type of error received in (1). These handshakes are outlined in the sections below: - -- [StorageHandledByL2](#data-stored-in-an-l2) -- [StorageHandledByOffChainDatabase](#data-stored-in-an-off-chain-database) - -In some cases, the mutation may be deferred multiple times -- [Storage Deferred Twice L1 > L2 > Off-Chain](#data-stored-in-an-l2--an-off-chain-database) - -### Data Stored in an L1 -``` -┌──────┐ ┌───────────┐ -│Client│ │L1 Contract│ -└──┬───┘ └─────┬─────┘ - │ │ - │ somefunc(...) │ - ├─────────────────────────►│ - │ │ - │ response │ - │◄─────────────────────────┤ - │ │ -``` - -In the case in which no reversion occurs, data is stored in the L1 contract when the transaction is executed. - -### Data Stored in an L2 - -``` -┌──────┐ ┌───────────┐ ┌─────────────┐ -│Client│ │L1 Contract│ │ L2 Contract │ -└──┬───┘ └─────┬─────┘ └──────┬──────┘ - │ │ │ - │ somefunc(...) │ │ - ├────────────────────────────────────────────────────►│ │ - │ │ │ - │ revert StorageHandledByL2(chainId, contractAddress) │ │ - │◄────────────────────────────────────────────────────┤ │ - │ │ │ - │ Execute Tx [chainId] [contractAddress] [callData] │ │ - ├─────────────────────────────────────────────────────┼──────────────►│ - │ │ │ - │ response │ │ - │◄────────────────────────────────────────────────────┼───────────────┤ - │ │ │ -``` - -The call or transaction to the L1 contract reverts with the `StorageHandledByL2(chainId, contractAddress)` error. - -In this case, the client builds a new transaction for `contractAddress` with the original `callData` and sends it to a RPC of their choice for the corresponding `chainId`. The `chainId` parameter corresponds to an L2 Solution that is EVM compatible. - -#### Example - -Suppose a contract has the following method: - -```solidity -function setAddr(bytes32 node, address a) external; -``` - -Data for this mutations is stored and tracked on an EVM compatible L2. The contract author wants to reduce the gas fees associated with the contract, while maintaining the interoperability and decentralization of the protocol. Therefore, the mutation is deferred to a off-chain handler by reverting with the `StorageHandledByL2(chainId, contractAddress)` error. - -One example of a valid implementation of `setAddr` would be: - -```solidity -function setAddr(bytes32 node, address a) external { - revert StorageHandledByL2( - 10, - _l2HandlerContractAddress - ); -} -``` - -For example, if a contract returns the following data in an `StorageHandledByL2`: - -```text -chainId = 10 -contractAddress = 0x0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff -``` - -The user, receiving this error, creates a new transaction for the corresponding `chainId`, and builds a transaction with the original `callData` to send to `contractAddress`. The user will have to choose an RPC of their choice to send the transaction to for the corresponding `chainId`. - -### Data Stored in an Off-Chain Database -``` -┌──────┐ ┌───────────┐ ┌────────────────────┐ -│Client│ │L1 Contract│ │ Off-Chain Database │ -└──┬───┘ └─────┬─────┘ └──────────┬─────────┘ - │ │ │ - │ somefunc(...) │ │ - ├────────────────────────────────────────────────────►│ │ - │ │ │ - │ revert StorageHandledByOffChainDatabase(sender, | │ - │ urls, requestParams) │ │ - │◄────────────────────────────────────────────────────┤ │ - │ │ │ - │ HTTP Request [requestParams, signature] │ │ - ├─────────────────────────────────────────────────────┼──────────────────►│ - │ │ │ - │ response │ │ - │◄────────────────────────────────────────────────────┼───────────────────┤ - │ │ │ -``` - -The call or transaction to the L1 contract reverts with the `StorageHandledByOffChainDatabase(sender, url, data)` error. - -In this case, the client performs a HTTP POST request to the gateway service. The gateway service is defined by `url`. The body attached to the request is a JSON object that includes `sender`, `data`, and a signed copy of `data` denoted `signature`. The signature is generated according to a [EIP-712](./eip-712), in which a typed data signature is generated using domain definition, `sender`, and the message context, `data`. - -`sender` ia an ABI-encoded struct defined as: - -```solidity -/** -* @notice Struct used to define the domain of the typed data signature, defined in EIP-712. -* @param name The user friendly name of the contract that the signature corresponds to. -* @param version The version of domain object being used. -* @param chainId The ID of the chain that the signature corresponds to (ie Ethereum mainnet: 1, Goerli testnet: 5, ...). -* @param verifyingContract The address of the contract that the signature pertains to. -*/ -struct domainData { - string name; - string version; - uint64 chainId; - address verifyingContract; -} -``` - -`data` ia an abi encoded struct defined as: - -```solidity -/** -* @notice Struct used to define the message context used to construct a typed data signature, defined in EIP-712, -* to authorize and define the deferred mutation being performed. -* @param functionSelector The function selector of the corresponding mutation. -* @param sender The address of the user performing the mutation (msg.sender). -* @param parameter[] A list of pairs defining the inputs used to perform the deferred mutation. -*/ -struct messageData { - bytes4 functionSelector; - address sender; - parameter[] parameters; - uint256 expirationTimestamp; -} - -/** -* @notice Struct used to define a parameter for Off-Chain Database Handler deferral. -* @param name The variable name of the parameter. -* @param value The string encoded value representation of the parameter. -*/ -struct parameter { - string name; - string value; -} -``` - -`signature` is generated by using the `sender` & `data` parameters to construct an [EIP-712](./eip-712) typed data signature. - -The body used in the HTTP POST request is defined as: - -```json -{ - "sender": "", - "data": "", - "signature": "" -} -``` - -#### Example - -Suppose a contract has the following method: - -```solidity -function setAddr(bytes32 node, address a) external; -``` - -Data for this mutations is stored and tracked in some kind of off-chain database. The contract author wants the user to be able to authorize and make modifications to their `Addr` without having to pay a gas fee. Therefore, the mutation is deferred to a off-chain handler by reverting with the `StorageHandledByOffChainDatabase(sender, url, data)` error. - -One example of a valid implementation of `setAddr` would be: - -```solidity -function setAddr(bytes32 node, address a) external { - IWriteDeferral.parameter[] memory params = new IWriteDeferral.parameter[](3); - - params[0].name = "node"; - params[0].value = BytesToString.bytes32ToString(node); - - params[1].name = "coin_type"; - params[1].value = Strings.toString(coinType); - - params[2].name = "address"; - params[2].value = BytesToString.bytesToString(a); - - revert StorageHandledByOffChainDatabase( - IWriteDeferral.domainData( - { - name: WRITE_DEFERRAL_DOMAIN_NAME, - version: WRITE_DEFERRAL_DOMAIN_VERSION, - chainId: 1, - verifyingContract: address(this) - } - ), - _offChainDatabaseUrl, - IWriteDeferral.messageData( - { - functionSelector: msg.sig, - sender: msg.sender, - parameters: params, - expirationTimestamp: block.timestamp + _offChainDatabaseTimeoutDuration - } - ) - ); -} -``` - -For example, if a contract reverts with the following: - -```text -StorageHandledByOffChainDatabase( - ( - "CoinbaseResolver", - "1", - 1, - 0x32f94e75cde5fa48b6469323742e6004d701409b - ), - "https://example.com/r/{sender}", - ( - 0xd5fa2b00, - 0x727f366727d3c9cc87f05d549ee2068f254b267c, - [ - ("node", "0x418ae76a9d04818c7a8001095ad01a78b9cd173ee66fe33af2d289b5dc5f4cba"), - ("coin_type", "60"), - ("address", "0x727f366727d3c9cc87f05d549ee2068f254b267c") - ], - 181 - ) -) -``` - -The user, receiving this error, constructs the typed data signature, signs it, and performs that request via a HTTP POST to `url`. - -Example HTTP POST request body including `requestParams` and `signature`: - -```json -{ - "sender": "", - "data": "", - "signature": "" -} -``` - -Note that the message could be altered could be altered in any way, shape, or form prior to signature and request. It is the backend's responsibility to correctly permission and process these mutations. From a security standpoint, this is no different then a user being able to call a smart contract with any params they want, as it is the smart contract's responsibility to permission and handle those requests. - - -### Data Stored in an L2 & an Off-Chain Database - -```text -┌──────┐ ┌───────────┐ ┌─────────────┐ ┌────────────────────┐ -│Client│ │L1 Contract│ │ L2 Contract │ │ Off-Chain Database │ -└──┬───┘ └─────┬─────┘ └──────┬──────┘ └──────────┬─────────┘ - │ │ │ │ - │ somefunc(...) │ │ │ - ├────────────────────────────────────────────────────►│ │ │ - │ │ │ │ - │ revert StorageHandledByL2(chainId, contractAddress) │ │ │ - │◄────────────────────────────────────────────────────┤ │ │ - │ │ │ │ - │ Execute Tx [chainId] [contractAddress] [callData] │ │ │ - ├─────────────────────────────────────────────────────┼──────────────►│ │ - │ │ │ │ - │ revert StorageHandledByOffChainDatabase(sender, url, data) │ │ - │◄────────────────────────────────────────────────────┼───────────────┤ │ - │ │ │ │ - │ HTTP Request {requestParams, signature} │ │ │ - ├─────────────────────────────────────────────────────┼───────────────┼───────────────────►│ - │ │ │ │ - │ response │ │ │ - │◄────────────────────────────────────────────────────┼───────────────┼────────────────────┤ - │ │ │ │ -``` - -The call or transaction to the L1 contract reverts with the `StorageHandledByL2(chainId, contractAddress)` error. - -In this case, the client builds a new transaction for `contractAddress` with the original `callData` and sends it to a RPC of their choice for the corresponding `chainId`. - -That call or transaction to the L2 contract then reverts with the `StorageHandledByOffChainDatabase(sender, url, data)` error. - -In this case, the client then performs a HTTP POST request against the gateway service. The gateway service is defined by `url`. The body attached to the request is a JSON object that includes `sender`, `data`, and `signature` -- a typed data signature corresponding to [EIP-712](./eip-712). - -### Events - -When making changes to core variables of the handler, the corresponding event MUST be emitted. This increases the transparency associated with different managerial actions. Core variables include `chainId` and `contractAddress` for L2 solutions and `url` for Off-Chain Database solutions. The events are outlined below in the WriteDeferral Interface. - -### Write Deferral Interface - -Below is a basic interface that defines and describes all of the reversion types and their corresponding parameters. - -```solidity -pragma solidity ^0.8.13; - -interface IWriteDeferral { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /// @notice Event raised when the default chainId is changed for the corresponding L2 handler. - event L2HandlerDefaultChainIdChanged(uint256 indexed previousChainId, uint256 indexed newChainId); - /// @notice Event raised when the contractAddress is changed for the L2 handler corresponding to chainId. - event L2HandlerContractAddressChanged(uint256 indexed chainId, address indexed previousContractAddress, address indexed newContractAddress); - - /// @notice Event raised when the url is changed for the corresponding Off-Chain Database handler. - event OffChainDatabaseHandlerURLChanged(string indexed previousUrl, string indexed newUrl); - - /*////////////////////////////////////////////////////////////// - STRUCTS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Struct used to define the domain of the typed data signature, defined in EIP-712. - * @param name The user friendly name of the contract that the signature corresponds to. - * @param version The version of domain object being used. - * @param chainId The ID of the chain that the signature corresponds to (ie Ethereum mainnet: 1, Goerli testnet: 5, ...). - * @param verifyingContract The address of the contract that the signature pertains to. - */ - struct domainData { - string name; - string version; - uint64 chainId; - address verifyingContract; - } - - /** - * @notice Struct used to define the message context used to construct a typed data signature, defined in EIP-712, - * to authorize and define the deferred mutation being performed. - * @param functionSelector The function selector of the corresponding mutation. - * @param sender The address of the user performing the mutation (msg.sender). - * @param parameter[] A list of pairs defining the inputs used to perform the deferred mutation. - */ - struct messageData { - bytes4 functionSelector; - address sender; - parameter[] parameters; - uint256 expirationTimestamp; - } - - /** - * @notice Struct used to define a parameter for off-chain Database Handler deferral. - * @param name The variable name of the parameter. - * @param value The string encoded value representation of the parameter. - */ - struct parameter { - string name; - string value; - } - - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Error to raise when mutations are being deferred to an L2. - * @param chainId Chain ID to perform the deferred mutation to. - * @param contractAddress Contract Address at which the deferred mutation should transact with. - */ - error StorageHandledByL2( - uint256 chainId, - address contractAddress - ); - - /** - * @dev Error to raise when mutations are being deferred to an Off-Chain Database. - * @param sender the EIP-712 domain definition of the corresponding contract performing the off-chain database, write - * deferral reversion. - * @param url URL to request to perform the off-chain mutation. - * @param data the EIP-712 message signing data context used to authorize and instruct the mutation deferred to the - * off-chain database handler. - * In order to authorize the deferred mutation to be performed, the user must use the domain definition (sender) and message data - * (data) to construct a type data signature request defined in EIP-712. This signature, message data (data), and domainData (sender) - * are then included in the HTTP POST request, denoted sender, data, and signature. - * - * Example HTTP POST request: - * { - * "sender": , - * "data": , - * "signature": - * } - * - */ - error StorageHandledByOffChainDatabase( - domainData sender, - string url, - messageData data - ); -} -``` - -### Use of transactions with storage-deferral reversions -In some cases the contract might conditionally defer and handle mutations, in which case a transaction may be required. It is simple to use this method for sending transactions that may result in deferral reversions, as a client should receive the corresponding reversion while `preflighting` the transaction. - -This functionality is ideal for applications that want to allow their users to define the security guarantees and costs associated with their actions. For example, in the case of a decentralized identity profile, a user might not care if their data is decentralized and chooses to defer the handling of their records to the off-chain handler to reduce gas fees and on-chain transactions. - -## Rationale -### Use of `revert` to convey call information -[EIP-3668](./eip-3668) adopted the idea of using a `revert` to convey call information. It was proposed as a simple mechanism in which any pre-existing interface or function signature could be satisfied while maintain a mechanism to instruct and trigger an off-chain lookup. - -This is very similar for the write deferral protocol, defined in this EIP; without any modifications to the ABI or underlying EVM, `revert` provides a clean mechanism in which we can "return" a typed instruction - and the corresponding elements to complete that action - without modifying the signature of the corresponding function. This makes it easy to comply with pre-existing interfaces and infrastructure. - -### Use of multiple reversion & handler types to better define security guarantees -By further defining the class of the handler, it gives the developer increased granularity to define the characteristics and different guarantees associated storing the data off-chain. In addition, different handlers require different parameters and verification mechanisms. This is very important for the transparency of the protocol, as they store data outside of the native ethereum ecosystem. Common implementations of this protocol could include storing non-operational data in L2 solutions and off-chain databases to reduce gas fees, while maintaining open interoperability. - - -## Backwards Compatibility -Existing contracts that do not wish to use this specification are unaffected. Clients can add support for Cross Chain Write Deferrals to all contract calls without introducing any new overhead or incompatibilities. - -Contracts that require Cross Chain Write Deferrals will not function in conjunction with clients that do not implement this specification. Attempts to call these contracts from non-compliant clients will result in the contract throwing an exception that is propagated to the user. - -## Security Considerations -Deferred mutations should never resolve to mainnet ethereum. Such attempts to defer the mutation back to ETH could include hijacking attempts in which the contract developer is trying to get the user to sign and send a malicious transaction. Furthermore, when a transaction is deferred to an L2 system, it must use the original `calldata`, this prevents against potentially malicious contextual changes in the transaction. - -### Fingerprinting attacks -As all deferred mutations will include the `msg.sender` parameter in `data`, it is possible that `StorageHandledByOffChainDatabase` reversions could fingerprint wallet addresses and the corresponding IP address used to make the HTTP request. The impact of this is application-specific and something the user should understand is a risk associated with off-chain handlers. To minimize the security impact of this, we make the following recommendations: - -1. Smart contract developers should provide users with the option to resolve data directly on the network. Allowing them to enable on-chain storage provides the user with a simple cost-benefit analysis of where they would like their data to resolve and different guarantees / risks associated with the resolution location. -2. Client libraries should provide clients with a hook to override Cross Chain Write Deferral `StorageHandledByOffChainDatabase` calls - either by rewriting them to use a proxy service, or by denying them entirely. This mechanism or another should be written so as to easily facilitate adding domains to allowlists or blocklists. - -We encourage applications to be as transparent as possible with their setup and different precautions put in place. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5559.md diff --git a/EIPS/eip-5560.md b/EIPS/eip-5560.md index a0ba5a481c5c48..b64e47b4684bba 100644 --- a/EIPS/eip-5560.md +++ b/EIPS/eip-5560.md @@ -1,125 +1 @@ ---- -eip: 5560 -title: Redeemable NFTs -description: Makes an NFT redeemable for a physical object -author: Olivier Fernandez (@fernandezOli), Frédéric Le Coidic (@FredLC29), Julien Béranger (@julienbrg) -discussions-to: https://ethereum-magicians.org/t/eip-redeemable-nft-extension/10589 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-30 -requires: 165, 721 ---- - -## Abstract - -The EIP is a Redeemable NFT extension which adds a `redeem` function to [EIP-721](./eip-721.md). It can be implemented when an NFT issuer wants his/her NFT to be redeemed for a physical object. - -## Motivation - -An increasing amount of NFT issuers such as artists, fine art galeries, auction houses, brands and others want to offer a physical object to the holder of a given NFT. This standard allows EIP-721 NFTs to signal reedemability. - -## Specification - -_The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119._ - -`EIP-721` compliant contracts MAY implement this EIP to provide a standard method of receiving information on redeemability. - -The NFT issuer **MUST** decide who is allowed to redeem the NFT, and restrict access to the `redeem()` function accordingly. - -Anyone **MAY** access the `isRedeemable()` function to check the redeemability status: it returns `true` when the NFT redeemable, and `false` when already redeemed. - -Third-party services that support this standard **MAY** use the `Redeem` event to listen to changes on the redeemable status of the NFT. - -Implementers of this standard **MUST** have all of the following functions: - -```solidity -import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; - -/** - * @dev Implementation of Redeemable for ERC-721s - * - */ - -interface IRedeemable is ERC165 { - /* - * ERC165 bytes to add to interface array - set in parent contract implementing this standard - * - * bytes4 private constant _INTERFACE_ID_ERC721REDEEM = 0x2f8ca953; - */ - - /// @dev This event emits when a token is redeemed. - event Redeem(address indexed from, uint256 indexed tokenId); - - /// @notice Returns the redeem status of a token - /// @param tokenId Identifier of the token. - function isRedeemable(uint256 _tokenId) external view returns (bool); - - /// @notice Redeeem a token - /// @param tokenId Identifier of the token to redeeem - function redeem(uint256 _tokenId) external; -} -``` - -The `Redeem` event is emitted when the `redeem()` function is called. - -The `supportsInterface` method **MUST** return `true` when called with `0x2f8ca953`. - -## Rationale - -When the NFT contract is deployed, the `isRedeemable()` function returns `true` by default. - -By default, the `redeem()` function visibility is public, so anyone can trigger it. It is **RECOMMENDED** to add a `require` to restrict the access: - -```solidity -require(ownerOf(tokenId) == msg.sender, "ERC721Redeemable: You are not the owner of this token"); -``` - -After the `redeem()` function is triggered, `isRedeemable()` function returns `false`. - -### `Redeem` event - -When the `redeem()` function is triggered, the following event **MUST** be emitted: - -```solidity -event Redeem(address indexed from, uint256 indexed tokenId); -``` - -## Backwards Compatibility - -This standard is compatible with EIP-721. - -## Reference Implementation - -Here's an example of an EIP-721 that includes the Redeemable extension: - -```solidity -contract ERC721Redeemable is ERC721, Redeemable { - - constructor(string memory name, string memory symbol) ERC721(name, symbol) { - } - - function isRedeemable(uint256 tokenId) public view virtual override returns (bool) { - require(_exists(tokenId), "ERC721Redeemable: Redeem query for nonexistent token"); - return super.isRedeemable(tokenId); - } - - function redeem(uint256 tokenId) public virtual override { - require(_exists(tokenId), "ERC721Redeemable: Redeem query for nonexistent token"); - require(ownerOf(tokenId) == msg.sender, "ERC721Redeemable: You are not the owner of this token"); - super.redeem(tokenId); - } - - function supportsInterface(bytes4 interfaceId) public view override(ERC721, Redeemable) returns (bool) { - return super.supportsInterface(interfaceId); - } -} -``` - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5560.md diff --git a/EIPS/eip-5564.md b/EIPS/eip-5564.md index f01ff65a046eac..c78b0953b62f0e 100644 --- a/EIPS/eip-5564.md +++ b/EIPS/eip-5564.md @@ -1,283 +1 @@ ---- -eip: 5564 -title: Stealth Addresses -description: Private, non-interactive transfers and interactions -author: Toni Wahrstätter (@nerolation), Matt Solomon (@mds1), Ben DiFrancesco (@apbendi), Vitalik Buterin (@vbuterin) -discussions-to: https://ethereum-magicians.org/t/eip-5566-stealth-addresses-for-smart-contract-wallets/10614 -status: Review -type: Standards Track -category: ERC -created: 2022-08-13 ---- - -## Abstract - -This specification establishes a standardized method for interacting with stealth addresses, which allow senders of transactions or transfers to non-interactively generate private accounts exclusively accessible by their recipients. Moreover, this specification enables developers to create stealth address protocols based on the foundational implementation outlined in this EIP, utilizing a singleton contract to emit the necessary information for recipients. In addition to the base implementation, this ERC also outlines the first implementation of a cryptographic scheme, specifically the SECP256k1 curve. - - -## Motivation - -The standardization of non-interactive stealth address generation presents the potential to significantly improve the privacy capabilities of the Ethereum network and other EVM-compatible chains by allowing recipients to remain private when receiving assets. This is accomplished through the sender generating a stealth address based on a shared secret known exclusively to the sender and recipient. The recipients alone can access the funds stored at their stealth addresses, as they are the sole possessors of the necessary private key. As a result, observers are unable to associate the recipient's stealth address with their identity, thereby preserving the recipient's privacy and leaving the sender as the only party privy to this information. By offering a foundational implementation in the form of a single contract that is compatible with multiple cryptographic schemes, recipients are granted a centralized location to monitor, ensuring they do not overlook any incoming transactions. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Definitions: - -- A "stealth meta-address" is a set of one or two public keys that can be used to compute a stealth address for a given recipient. -- A "spending key" is a private key that can be used to spend funds sent to a stealth address. A "spending public key" is the corresponding public key. -- A "viewing key" is a private key that can be used to determine if funds sent to a stealth address belong to the recipient who controls the corresponding spending key. A "viewing public key" is the corresponding public key. - -Different stealth address schemes will have different expected stealth meta-address lengths. A scheme that uses public keys of length `n` bytes MUST define stealth meta-addresses as follows: - -- A stealth meta-address of length `n` uses the same stealth meta-address for the spending public key and viewing public key. -- A stealth meta-address of length `2n` uses the first `n` bytes as the spending public key and the last `n` bytes as the viewing public key. - -Given a recipient's stealth meta-address, a sender MUST be able generate a stealth address for the recipient by calling a method with the following signature: - -```solidity -/// @notice Generates a stealth address from a stealth meta address. -/// @param stealthMetaAddress The recipient's stealth meta-address. -/// @return stealthAddress The recipient's stealth address. -/// @return ephemeralPubKey The ephemeral public key used to generate the stealth address. -/// @return viewTag The view tag derived from the shared secret. -function generateStealthAddress(bytes memory stealthMetaAddress) - external - view - returns (address stealthAddress, bytes memory ephemeralPubKey, bytes1 viewTag); -``` - -A recipient MUST be able to check if a stealth address belongs to them by calling a method with the following signature: - -```solidity -/// @notice Returns true if funds sent to a stealth address belong to the recipient who controls -/// the corresponding spending key. -/// @param stealthAddress The recipient's stealth address. -/// @param ephemeralPubKey The ephemeral public key used to generate the stealth address. -/// @param viewingKey The recipient's viewing private key. -/// @param spendingPubKey The recipient's spending public key. -/// @return True if funds sent to the stealth address belong to the recipient. -function checkStealthAddress( - address stealthAddress, - bytes memory ephemeralPubKey, - bytes memory viewingKey, - bytes memory spendingPubKey -) external view returns (bool); -``` - -A recipient MUST be able to compute the private key for a stealth address by calling a method with the following signature: - -```solidity -/// @notice Computes the stealth private key for a stealth address. -/// @param stealthAddress The expected stealth address. -/// @param ephemeralPubKey The ephemeral public key used to generate the stealth address. -/// @param spendingKey The recipient's spending private key. -/// @return stealthKey The stealth private key corresponding to the stealth address. -/// @dev The stealth address input is not strictly necessary, but it is included so the method -/// can validate that the stealth private key was generated correctly. -function computeStealthKey( - address stealthAddress, - bytes memory ephemeralPubKey, - bytes memory spendingKey -) external view returns (bytes memory); -``` - -The implementation of these methods is scheme-specific. The specification of a new stealth address scheme MUST specify the implementation for each of these methods. Additionally, although these function interfaces are specified in Solidity, they do not necessarily ever need to be implemented in Solidity, but any library or SDK conforming to this specification MUST implement these methods with compatible function interfaces. - -A 256 bit integer (`schemeId`) is used to identify stealth address schemes. A mapping from the schemeId to its specification MUST be declared in the EIP that proposes to standardize a new stealth address scheme. It is RECOMMENDED that `schemeId`s are chosen to be monotonically incrementing integers for simplicity, but arbitrary or meaningful `schemeId`s may be chosen. Furthermore, the schemeId MUST be added to [this overview](../assets/eip-5564/scheme_ids.md). These extensions MUST specify: - -- The integer identifier for the scheme. - -- The algorithm for encoding a stealth meta-address (i.e. the spending public key and viewing public key) into a `bytes` array, and decoding it from `bytes` to the native key types of that scheme. - -- The algorithm for the `generateStealthAddress` method. - -- The algorithm for the `checkStealthAddress` method. - -- The algorithm for the `computeStealthKey` method. - -This specification additionally defines a singleton `ERC5564Announcer` contract that emits events to announce when something is sent to a stealth address. This MUST be a singleton contract, with one instance per chain. The contract is specified as follows: - -```solidity -/// @notice Interface for announcing when something is sent to a stealth address. -contract IERC5564Announcer { - /// @dev Emitted when sending something to a stealth address. - /// @dev See the `announce` method for documentation on the parameters. - event Announcement ( - uint256 indexed schemeId, - address indexed stealthAddress, - address indexed caller, - bytes ephemeralPubKey, - bytes metadata - ); - - /// @dev Called by integrators to emit an `Announcement` event. - /// @param schemeId The integer specifying the applied stealth address scheme. - /// @param stealthAddress The computed stealth address for the recipient. - /// @param ephemeralPubKey Ephemeral public key used by the sender. - /// @param metadata An arbitrary field MUST include the view tag in the first byte. - /// Besides the view tag, the metadata can be used by the senders however they like, - /// but the below guidelines are recommended: - /// The first byte of the metadata MUST be the view tag. - /// - When sending/interacting with the native token of the blockchain (cf. ETH), the metadata SHOULD be structured as follows: - /// - Byte 1 MUST be the view tag, as specified above. - /// - Bytes 2-5 are `0xeeeeeeee` - /// - Bytes 6-25 are the address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE. - /// - Bytes 26-57 are the amount of ETH being sent. - /// - When interacting with ERC-20/ERC-721/etc. tokens, the metadata SHOULD be structured as follows: - /// - Byte 1 MUST be the view tag, as specified above. - /// - Bytes 2-5 are a function identifier. When a function selector (e.g. - /// the first (left, high-order in big-endian) four bytes of the Keccak-256 - /// hash of the signature of the function, like Solidity and Vyper use) is - /// available, it MUST be used. - /// - Bytes 6-25 are the token contract address. - /// - Bytes 26-57 are the amount of tokens being sent/interacted with for fungible tokens, or - /// the token ID for non-fungible tokens. - function announce ( - uint256 schemeId, - address stealthAddress, - bytes memory ephemeralPubKey, - bytes memory metadata - ) - external - { - emit Announcement(schemeId, stealthAddress, msg.sender, ephemeralPubKey, metadata); - } -} -``` - - -### Stealth meta-address format - -The new address format for the stealth meta-address is based on [ERC-3770](./eip-3770.md) and extends it by adding a `st:` (*stealth*) prefix. -Thus, a stealth meta-address on Ethereum has the following format: - -``` -st:eth:0x -``` - -Stealth meta-addresses may be managed by the user and/or registered within a publicly available `Registry` contract, as delineated in [ERC-6538](./eip-6538). This provides users with a centralized location for identifying stealth meta-addresses associated with other individuals while simultaneously enabling recipients to express their openness to engage via stealth addresses. - -*Notably, the address format is used only to differentiate stealth addresses from standard addresses, as the prefix is removed before performing any computations on the stealth meta-address.* - ---- - -### Initial Implementation of SECP256k1 with View Tags - -This EIP provides a foundation that is not tied to any specific cryptographic system through the `IERC5564Announcer` contract. In addition, it introduces the first implementation of a [stealth address scheme](../assets/eip-5564/scheme_ids.md) that utilizes the SECP256k1 elliptic curve and view tags. The SECP256k1 elliptic curve is defined with the equation $y^2 = x^3 + 7 \pmod{p}$, where $p = 2^{256} - 2^{32} - 977$. - -The following reference is divided into three sections: - -1. Stealth address generation - -2. Parsing announcements - -3. Stealth private key derivation - - Definitions: - -- $G$ represents the generator point of the curve. - -#### Generation - Generate stealth address from stealth meta-address: - -- Recipient has access to the private keys $p_{spend}$, $p_{view}$ from which public keys $P_{spend}$ and $P_{view}$ are derived. - -- Recipient has published a stealth meta-address that consists of the public keys $P_{spend}$ and $P_{view}$. - -- Sender passes the stealth meta-address to the `generateStealthAddress` function. - -- The `generateStealthAddress` function performs the following computations: - - Generate a random 32-byte entropy ephemeral private key $p_{ephemeral}$. - - Derive the ephemeral public key $P_{ephemeral}$ from $p_{ephemeral}$. - - Parse the spending and viewing public keys, $P_{spend}$ and $P_{view}$, from the stealth meta-address. - - A shared secret $s$ is computed as $s = p_{ephemeral} \cdot P_{view}$. - - The secret is hashed $s_{h} = \textrm{h}(s)$. - - The view tag $v$ is extracted by taking the most significant byte $s_{h}[0]$, - - Multiply the hashed shared secret with the generator point $S_h = s_h \cdot G$. - - The recipient's stealth public key is computed as $P_{stealth} = P_{spend} + S_h$. - - The recipient's stealth address $a_{stealth}$ is computed as $\textrm{pubkeyToAddress}(P_{stealth})$. - - The function returns the stealth address $a_{stealth}$, the ephemeral public key $P_{ephemeral}$ and the view tag $v$. - - -#### Parsing - Locate one's own stealth address(es): - -- User has access to the viewing private key $p_{view}$ and the spending public key $P_{spend}$. - -- User has access to a set of `Announcement` events and applies the `checkStealthAddress` function to each of them. - -- The `checkStealthAddress` function performs the following computations: - - Shared secret $s$ is computed by multiplying the viewing private key with the ephemeral public key of the announcement $s = p_{view}$ * $P_{ephemeral}$. - - The secret is hashed $s_{h} = h(s)$. - - The view tag $v$ is extracted by taking the most significant byte $s_{h}[0]$ and can be compared to the given view tag. If the view tags do not match, this `Announcement` is not for the user and the remaining steps can be skipped. If the view tags match, continue on. - - Multiply the hashed shared secret with the generator point $S_h = s_h \cdot G$. - - The stealth public key is computed as $P_{stealth} = P_{spend} + S_h$. - - The derived stealth address $a_{stealth}$ is computed as $\textrm{pubkeyToAddress}(P_{stealth})$. - - Return `true` if the stealth address of the announcement matches the derived stealth address, else return `false`. - -#### Private key derivation - Generate the stealth address private key from the hashed shared secret and the spending private key. - -- User has access to the viewing private key $p_{view}$ and spending private key $p_{spend}$. - -- User has access to a set of `Announcement` events for which the `checkStealthAddress` function returns `true`. - -- The `computeStealthKey` function performs the following computations: - - Shared secret $s$ is computed by multiplying the viewing private key with the ephemeral public key of the announcement $s = p_{view}$ * $P_{ephemeral}$. - - The secret is hashed $s_{h} = h(s)$. - - The stealth private key is computed as $p_{stealth} = p_{spend} + s_h$. - - - -### Parsing considerations - -Usually, the recipient of a stealth address transaction has to perform the following operations to check whether he was the recipient of a certain transaction: - -- 2x ecMUL, - -- 2x HASH, - -- 1x ecADD, - -The view tags approach is introduced to reduce the parsing time by around 6x. Users only need to perform 1x ecMUL and 1x HASH (skipping 1x ecMUL, 1x ecADD and 1x HASH) for every parsed announcement. The 1-byte view tag length is based on the maximum required space to reliably filter non-matching announcements. With a 1-byte `viewTag`, the probability for users to skip the remaining computations after hashing the shared secret $h(s)$ is $255/256$. This means that users can almost certainly skip the above three operations for any announcements that do not involve them. Since the view tag reveals one byte of the shared secret, the security margin is reduced from 128 bits to 124 bits. Notably, this only affects the privacy and not the secure generation of a stealth address. - ---- - -## Rationale - -This EIP emerged from the need for privacy-preserving ways to transfer ownership without disclosing any information about the recipients' identities. Token ownership can expose sensitive personal information. While individuals may wish to donate to a specific organization or country, they might prefer not to disclose a link between themselves and the recipient simultaneously. Standardizing stealth address generation represents a significant step towards unlinkable interactions, since such privacy-enhancing solutions require standards to achieve widespread adoption. Consequently, it is crucial to focus on developing generalizable approaches for implementing related solutions. - -The stealth address specification standardizes a protocol for generating and locating stealth addresses, facilitating the transfer of assets without requiring prior interaction with the recipient. This enables recipients to verify the receipt of a transfer without the need to interact with the blockchain and query account balances. Importantly, stealth addresses enable token transfer recipients to verify receipt while maintaining their privacy, as only the recipient can recognize themselves as the recipient of the transfer. - -The authors recognize the trade-off between on- and off-chain efficiency. Although incorporating a Monero-like view tags mechanism enables recipients to parse announcements more efficiently, it adds complexity to the announcement event. - -The recipient's address and the `viewTag` MUST be included in the announcement event, allowing users to quickly verify ownership without querying the chain for positive account balances. - -## Backwards Compatibility - -This EIP is fully backward compatible. - -## Reference Implementation - -You can find an implementation of this standard in TBD. - -## Security Considerations - -### DoS Countermeasures - -There are potential denial of service (DoS) attack vectors that are not mitigated by network transaction fees. Stealth transfer senders cause an externality for recipients, as parsing announcement events consumes computational resources that are not compensated with gas. Therefore, spamming announcement events *can* be a detriment to the user experience, as it *can* lead to longer parsing times. -We consider the incentives to carry out such an attack to be low because **no monetary benefit can be obtained** -However, to tackle potential spam, parsing providers may adopt their own anti-DoS attack methods. These may include ignoring the spamming users when serving announcements to users or, less harsh, de-prioritizing them when ordering the announcements. The indexed `caller` keyword may help parsing providers to effectively filter known spammers. - -Furthermore, parsing providers have a few options to counter spam, such as introducing staking mechanisms or requiring senders to pay a `toll` before including their `Announcement`. Moreover, a Staking mechanism may allow users to stake an unslashable amount of ETH (similarly to [ERC-4337](./eip-4337)), to help mitigate potential spam through *sybil attacks* and enable parsing providers filtering spam more effectively. -Introducing a `toll`, paid by sending users, would simply put a cost on each stealth address transaction, making spamming economically unattractive. - -### Recipients' transaction costs - -The funding of the stealth address wallet represents a known issue that might breach privacy. The wallet that funds the stealth address MUST NOT have any physical connection to the stealth address owner in order to fully leverage the privacy improvements. - -Thus, the sender may attach a small amount of ETH to each stealth address transaction, thereby sponsoring subsequent transactions of the recipient. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5564.md diff --git a/EIPS/eip-5568.md b/EIPS/eip-5568.md index 26c415392eedd7..7b831429dee745 100644 --- a/EIPS/eip-5568.md +++ b/EIPS/eip-5568.md @@ -1,82 +1 @@ ---- -eip: 5568 -title: Well-Known Format for Required Actions -description: Signal to wallets that an action is needed through a well-known function and revert reason -author: Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/eip-5568-revert-signals/10622 -status: Review -type: Standards Track -category: ERC -created: 2022-08-31 -requires: 140 ---- - -## Abstract - -This ERC introduces a minimalistic machine-readable (binary) format to signal to wallets that an action needs to be taken by the user using a well-known function and revert reason. It provides just enough data to be extendable by future ERCs and to take in arbitrary parameters (up to 64 kB of data). Example use cases could include approving a token for an exchange, sending an HTTP request, or requesting the user to rotate their keys after a certain period of time to enforce good hygiene. - -## Motivation - -Oftentimes, a smart contract needs to signal to a wallet that an action needs to be taken, such as to sign a transaction or send an HTTP request to a URL. Traditionally, this has been done by hard-coding the logic into the frontend, but this ERC allows the smart contract itself to request the action. - -This means that, for example, an exchange or a market can directly tell the wallet to approve the smart contract to spend the token, vastly simplifying front-end code. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Action Detection - -```solidity -interface IERC5568 { - function walletSignal24(bytes32 selector, bytes function_data) view returns (uint24 instruction_id, bytes instruction_data); -} -``` - -The `instruction_id` of an instruction defined by an ERC MUST be its ERC number unless there are exceptional circumstances (be reasonable). An ERC MUST define exactly zero or one `instruction_id`. The structure of the instruction data for any `instruction_id` MUST be defined by the ERC that defines the `instruction_id`. - -To indicate that an action needs to be taken, return the `instruction_id` and `instruction_data`. To indicate no actions need to be taken, set `instruction_id` to be `0` and `instruction_data` to any value. - -### Custom Revert Reason - -To signal an action was not taken, a compliant smart contract MUST revert with the following error: - -```solidity -error WalletSignal24(uint24 instruction_id, bytes instruction_data) -``` - -The `instruction_id` of an instruction defined by an ERC MUST be its ERC number unless there are exceptional circumstances (be reasonable). An ERC MUST define exactly zero or one `instruction_id`. The structure of the instruction data for any `instruction_id` MUST be defined by the ERC that defines the `instruction_id`. - -### Responding to a Revert - -Before submitting a transaction to the mempool, the `walletSignal24` function MUST be simulated locally. It MUST be treated as if it were a non-`view` function capable of making state changes (e.g. `CALLS` to non-`view` functions are allowed). If the resulting `instruction_id` is nonzero, an action needs to be taken. - -The `instruction_id`, and `instruction_data` MUST be taken from the `walletSignal24` simulation. The instruction SHOULD be evaluated as per the relevant ERC. If the instruction is not supported by the wallet, it MUST display an error to the user indicating that is the case. The wallet MUST then re-evaluate the transaction, except if an instruction explicitly states that the transaction MUST NOT be re-evaluated. - -If an instruction is invalid, or the `instruction_id`, and `instruction_data` cannot be parsed, then an error MUST be displayed to the user indicating that is the case. The transaction MUST NOT be re-evaluated. - -## Rationale - -This ERC was explicitly optimized for deployment gas cost and simplicity. It is expected that libraries will eventually be developed that makes this more developer-friendly. - -[ERC-165](./eip-165.md) is not used, since the interface is simple enough that it can be detected simply by calling the function. - -## Backwards Compatibility - -### Human-Readable Revert Messages - -See [Revert Reason Collisions](#revert-reason-collisions). - -### [ERC-3668](./eip-3668.md) - -ERC-3668 can be used alongside this ERC, but it uses a different mechanism than this ERC. - -## Security Considerations - -### Revert Reason Collisions - -It is unlikely that the signature of the custom error matches any custom errors in the wild. In the case that it does, no harm is caused unless the data happen to be a valid instruction, which is even more unlikely. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5568.md diff --git a/EIPS/eip-5570.md b/EIPS/eip-5570.md index 90e4c547d7d849..575d131cf8aea5 100644 --- a/EIPS/eip-5570.md +++ b/EIPS/eip-5570.md @@ -1,291 +1 @@ ---- -eip: 5570 -title: Digital Receipt Non-Fungible Tokens -description: Non-Fungible Tokens as digital receipts for physical purchases, where the metadata represents a JSON receipt -author: Sean Darcy (@darcys22) -discussions-to: https://ethereum-magicians.org/t/idea-standard-digital-receipts-using-erc-721/9908 -status: Final -type: Standards Track -category: ERC -created: 2022-09-01 -requires: 721 ---- - -## Abstract - -This ERC proposes a standard schema for digital receipts of transactions. Digital Receipt Non-Fungible Tokens are issued by a vendor when a customer makes a purchase from their store and contains transaction details necessary for record keeping. Digital Receipt Non-Fungible Tokens extend [ERC-721](./eip-721.md) which allows for the management and ownership of unique tokens. - -## Motivation - -Purchases from online retailers include a receipt that is emailed and/or physically provided to the customer. These receipts are critical for many reasons but are provided in an analogue form which is difficult to parse by financial systems. Digital receipts have never gained traction dispite the fact that point of sales systems are already digital and the customers often want this information in their own digital systems. So we are left with a redundant Digital -> Analogue -> Digital process which requires unnecessary data entry or the use of clunky receipt-scanning applications. - -Digital receipts are relatively simple and can be specified with a schema that can be parsed into JSON or other structured formats. In addition we can prove the receipts validity by digitally signing the receipt using the vendors private keys. - -As Ethereum scales tooling will need to be developed to provide end users with features (such as receipts) already available to fiat transactions. NFTs provide a unique opportunity to link an on chain purchase with its transaction details directly through the transaction state update. If we conceptually think of a transaction as funds provided to one participant and goods provided to another, then our real life state includes two sides of a transaction, 1) Funds changing ownership and 2) goods changing ownership. NFT receipts are first class citizens of a transaction reflecting the goods changing ownership as part of the transaction state. They will bring our on chain transaction state in line with the changes happening in the real world. - -The convenience of a direct link to the transaction receipt via the transaction state is significant, other methods of distributing receipts either off chain or through smart contracts separate to the initial transaction lose this link and force the end user to manually locate the transaction details when needed. -The benefit can be demonstrated by comparing a wallet that allows a user to click through a transaction to its receipt (available immediately after purchase without any further action) verses a user needing to search through a datastore to locate a receipt for a transaction that they can see in their wallet history. - -Digital receipt as NFTs can also conceptually include other important information such as item serial numbers and delivery tracking etc. - -One of the major roadblocks to fully automating our finance world has been the difficulty in tracking transaction details. Human beings physically tracking paper receipts is archaic and NFTs on the blockchain provide a pathway for these systems to be significantly improved. - -## Specification - -Transaction Flow: - - - A customer purchases an item from an online retailer, checking out leads the customer to an option to mint a NFT. - - The smart contract provides the user with a Digital Receipt Non-Fungible Token. - - When fulfilling the order, the retailer will upload the digital receipt specified in in the JSON schema below as the metadata to the previously minted NFT. - -### Digital Receipt JSON Schema - -The JSON schema is composed of 2 parts. The root schema contains high level details of the receipt (for example Date and Vendor) and another schema for the optionally recurring line items contained in the receipt. - -#### Root Schema - -```json -{ - "id": "receipt.json#", - "description": "Receipt Schema for Digital Receipt Non-Fungible Tokens", - "type": "object", - "required": ["name", "description", "image", "receipt"], - "properties": { - "name": { - "title": "Name", - "description": "Identifies the token as a digital receipt", - "type": "string" - }, - "description": { - "title": "Description", - "description": "Brief description of a digital receipt", - "type": "string" - }, - "receipt": { - "title": "Receipt", - "description": "Details of the receipt", - "type": "object", - "required": ["id", "date", "vendor", "items"], - "properties": { - "id": { - "title": "ID", - "description": "Unique ID for the receipt generated by the vendor", - "type": "string" - }, - "date": { - "title": "Date", - "description": "Date Receipt Issued", - "type": "string", - "format": "date" - }, - "vendor": { - "title": "Vendor", - "description": "Details of the entity issuing the receipt", - "type": "object", - "required": ["name", "website"], - "properties": { - "name": { - "title": "Name", - "description": "Name of the vendor. E.g. Acme Corp", - "type": "string" - }, - "logo": { - "title": "Logo", - "description": "URL of the issuer's logo", - "type": "string", - "format": "uri" - }, - "address": { - "title": "Address", - "description": "List of strings comprising the address of the issuer", - "type": "array", - "items": { "type": "string" }, - "minItems": 2, - "maxItems": 6 - }, - "website": { - "title": "Website", - "description": "URL of the issuer's website", - "type": "string", - "format": "uri" - }, - "contact": { - "title": "Contact Details", - "description": "Details of the person to contact", - "type": "object", - "required": [], - "properties": { - "name": { - "title": "Name", - "description": "Name of the contact person", - "type": "string" - }, - "position": { - "title": "Position", - "description": "Position / Role of the contact person", - "type": "string" - }, - "tel": { - "title": "Telephone Number", - "description": "Telephone number of the contact person", - "type": "string" - }, - "email": { - "title": "Email", - "description": "Email of the contact person", - "type": "string", - "format": "email" - }, - "address": { - "title": "Address", - "description": "List of strings comprising the address of the contact person", - "type": "array", - "items": { "type": "string" }, - "minItems": 2, - "maxItems": 6 - } - } - } - } - }, - "items": { - "title": "Items", - "description": "Items included into the receipt", - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "$ref": "item.json#" - } - }, - "comments": { - "title": "Comments", - "description": "Any messages/comments the issuer wishes to convey to the customer", - "type": "string" - } - } - }, - "image": { - "title": "Image", - "description": "Viewable/Printable Image of the Digital Receipt", - "type": "string" - }, - "signature": { - "title": "Signature", - "description": "Digital signature by the vendor of receipts data", - "type": "string" - }, - "extra": { - "title": "Extra", - "description": "Extra information about the business/receipt as needed", - "type": "string" - } - } -} -``` - -#### Line Items Schema - -```json -{ - "type": "object", - "id": "item.json#", - "required": ["id", "title", "date", "amount", "tax", "quantity"], - "properties": { - "id": { - "title": "ID", - "description": "Unique identifier of the goods or service", - "type": "string" - }, - "title": { - "title": "Title", - "description": "Title of the goods or service", - "type": "string" - }, - "description": { - "title": "Description", - "description": "Description of the goods or service", - "type": "string" - }, - "link": { - "title": "Link", - "description": "URL link to the web page for the product or sevice", - "type": "string", - "format": "uri" - }, - "contract": { - "title": "Contract", - "description": "URL link or hash to an external contract for this product or service", - "type": "string" - }, - "serial_number": { - "title": "Serial Number", - "description": "Serial number of the item", - "type": "string" - }, - "date": { - "title": "Supply Date", - "description": "The date the goods or service were provided", - "type": "string", - "format": "date" - }, - "amount": { - "title": "Unit Price", - "description": "Unit Price per item (excluding tax)", - "type": "number" - }, - "tax": { - "title": "Tax", - "description": "Amount of tax charged for unit", - "type": "array", - "items": { - "type": "object", - "required": ["name", "rate", "amount"], - "properties": { - "name": { - "title": "Name of Tax", - "description": "GST/PST etc", - "type": "string" - }, - "rate": { - "title": "Tax Rate", - "description": "Tax rate as a percentage", - "type": "number" - }, - "amount": { - "title": "Tax Amount", - "description": "Total amount of tax charged", - "type": "number" - } - } - } - }, - "quantity": { - "title": "Quantity", - "description": "Number of units", - "type": "integer" - } - } -} -``` - -## Rationale - -The schema introduced complies with ERC-721's metadata extension, conveniently allowing previous tools for viewing NFTs to show our receipts. The new property "receipt" contains our newly provided receipt structure and the signature property optionally allows the vendor to digitally sign the receipt structure. - -## Backwards Compatibility - -This standard is an extension of ERC-721. It is compatible with both optional extensions, Metadata and Enumerable, mentioned in ERC-721. - -## Security Considerations - -The data stored in the digital receipt includes various types of personally identifying information (PII), such as the vendor's name, contact details, and the items purchased. PII is sensitive information that can be used to identify, locate, or contact an individual. Protecting the privacy of the customer is of utmost importance, as unauthorized access to PII can lead to identity theft, fraud, or other malicious activities. - -To ensure the privacy of the customer, it is crucial to encrypt the PII contained within the digital receipt. By encrypting the PII, only authorized parties with the appropriate decryption keys can access and read the information stored in the digital receipt. This ensures that the customer's privacy is maintained, and their data is protected from potential misuse. - -While encrypting PII is essential, it is important to note that defining a specific encryption standard is beyond the scope of this ERC. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5570.md diff --git a/EIPS/eip-5573.md b/EIPS/eip-5573.md index ceb08d504b3fe0..2eaa29ef938d13 100644 --- a/EIPS/eip-5573.md +++ b/EIPS/eip-5573.md @@ -1,330 +1 @@ ---- -eip: 5573 -title: Sign-In with Ethereum Capabilities, ReCaps -description: Mechanism on top of Sign-In with Ethereum for informed consent to delegate capabilities with an extensible scope mechanism -author: Oliver Terbu (@awoie), Jacob Ward (@cobward), Charles Lehner (@clehner), Sam Gbafa (@skgbafa), Wayne Chang (@wyc), Charles Cunningham (@chunningham) -discussions-to: https://ethereum-magicians.org/t/eip-5573-siwe-recap/10627 -status: Draft -type: Standards Track -category: ERC -created: 2021-07-20 -requires: 4361 ---- - -## Abstract - -[ERC-4361](./eip-4361.md), or Sign-In with Ethereum (SIWE), describes how Ethereum accounts authenticate with off-chain services. This proposal, known as ReCaps, describes a mechanism on top of SIWE to give informed consent to authorize a Relying Party to exercise certain scoped capabilities. How a Relying Party authenticates against the target resource is out of scope for this specification and depends on the implementation of the target resource. - -## Motivation - -SIWE ReCaps unlock integration of protocols and/or APIs for developers by reducing user friction, onchain state and increasing security by introducing informed consent and deterministic capability objects on top of Sign-In With Ethereum (ERC-4361). - -While SIWE focuses on authenticating the Ethereum account against the service (relying party or SIWE client) initiating the SIWE flow, there is no canonical way for the authenticated Ethereum account to authorize a relying party to interact with a third-party service (resource service) on behalf of the Ethereum account. A relying party may want to interact with another service on behalf of the Ethereum account, for example a service that provides data storage for the Ethereum account. This specification introduces a mechanism that allows the service (or more generally a Relying Party) to combine authentication and authorization of such while preserving security and optimizing UX. - -Note, this approach is a similar mechanism to combining OpenID Connect (SIWE auth) and OAuth2 (SIWE ReCap) where SIWE ReCap implements capabilities-based authorization on top of the authentication provided by SIWE. - -## Specification - -This specification has three different audiences: - -- Web3 application developers that want to integrate ReCaps to authenticate with any protocols and APIs that support object capabilities. -- Protocol or API developers that want to learn how to define their own ReCaps. -- Wallet implementers that want to improve the UI for ReCaps. - -### Terms and Definitions - -- ReCap - A SIWE Message complying with this specification, i.e. containing at least one ReCap URI in the `Resources` section and the corresponding human-readable ReCap Statement appended to the SIWE `statement`. -- ReCap URI - A type of URI that resolves to a ReCap Details Object. -- ReCap Details Object - A JSON object describing the actions and optionally the resources associated with a ReCap Capability. -- Resource Service (RS) - The entity that is providing third-party services for the Ethereum account. -- SIWE Client (SC) - The entity initiating the authorization (SIWE authentication and ReCap flow). -- Relying Party (RP) - same as SC in the context of authorization. - -### Overview - -This specification defines the following: - -- ReCap SIWE Extension -- ReCap Capability - - ReCap URI Scheme - - ReCap Details Object Schema -- ReCap Translation Algorithm -- ReCap Verification - -### ReCap SIWE Extension - -A ReCap is an ERC-4361 message following a specific format that allows an Ethereum account to delegate a set of ReCap Capabilities to a Relying Party through informed consent. ReCap Capabilities MUST be represented by the final entry in the `Resources` array of the SIWE message that MUST deterministically translate the ReCap Capability in human-readable form to the `statement` field in the SIWE message using the ReCap Translation Algorithm. - -The following SIWE message fields are used to further define (or limit) the scope of all ReCap Capabilities: - -- The `URI` field MUST specify the intended Relying Party, e.g., `https://example.com`, `did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK`. It is expected that the RS authenticates the Relying Party before invoking an action for the ReCap Capability. -- The `Issued At` field MUST be used to specify the issuance date of the ReCap Capabilities. -- If present, the `Expiration Time` field MUST be used as the expiration time of the ReCap Capabilities, i.e. the time at which the RS will no longer accept an invocation of the capabilities expressed in this form. -- If present, the `Not Before` field MUST be used as the time that has to expire before the RS starts accepting invocations of the capabilities expressed in the message. - -The following is a non-normative example of a SIWE message with the SIWE ReCap Extension: - -```text -example.com wants you to sign in with your Ethereum account: -0x0000000000000000000000000000000000000000 - -I further authorize the stated URI to perform the following actions on my behalf: (1) 'example': 'append', 'read' for 'https://example.com'. (2) 'other': 'action' for 'https://example.com'. (3) 'example': 'append', 'delete' for 'my:resource:uri.1'. (4) 'example': 'append' for 'my:resource:uri.2'. (5) 'example': 'append' for 'my:resource:uri.3'. - -URI: did:key:example -Version: 1 -Chain ID: 1 -Nonce: mynonce1 -Issued At: 2022-06-21T12:00:00.000Z -Resources: -- urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlLmNvbSI6eyJleGFtcGxlL2FwcGVuZCI6W10sImV4YW1wbGUvcmVhZCI6W10sIm90aGVyL2FjdGlvbiI6W119LCJteTpyZXNvdXJjZTp1cmkuMSI6eyJleGFtcGxlL2FwcGVuZCI6W10sImV4YW1wbGUvZGVsZXRlIjpbXX0sIm15OnJlc291cmNlOnVyaS4yIjp7ImV4YW1wbGUvYXBwZW5kIjpbXX0sIm15OnJlc291cmNlOnVyaS4zIjp7ImV4YW1wbGUvYXBwZW5kIjpbXX19LCJwcmYiOltdfQ -``` - -#### ReCap Capability - -A ReCap Capability is identified by their ReCap URI that resolves to a ReCap Details Object which defines the associated actions and optional target resources. The scope of each ReCap Capability is attenuated by common fields in the SIWE message as described in the previous chapter, e.g., `URI`, `Issued At`, `Expiration Time`, `Not Before`. - -##### ReCap URI Scheme - -A ReCap URI starts with `urn:recap:` followed by `:` and the unpadded base64url-encoded payload of the ReCap Details Object. Note, the term base64url is defined in RFC4648 - Base 64 Encoding with URL and Filename Safe Alphabet. If present, a Recap URI MUST occupy the final entry of the SIWE resource list. - -The following is a non-normative example of a ReCap Capability: - -```text -urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlLmNvbS9waWN0dXJlcy8iOnsiY3J1ZC9kZWxldGUiOlt7fV0sImNydWQvdXBkYXRlIjpbe31dLCJvdGhlci9hY3Rpb24iOlt7fV19LCJtYWlsdG86dXNlcm5hbWVAZXhhbXBsZS5jb20iOnsibXNnL3JlY2VpdmUiOlt7Im1heF9jb3VudCI6NSwidGVtcGxhdGVzIjpbIm5ld3NsZXR0ZXIiLCJtYXJrZXRpbmciXX1dLCJtc2cvc2VuZCI6W3sidG8iOiJzb21lb25lQGVtYWlsLmNvbSJ9LHsidG8iOiJqb2VAZW1haWwuY29tIn1dfX0sInByZiI6WyJ6ZGo3V2o2Rk5TNHJVVWJzaUp2amp4Y3NOcVpkRENTaVlSOHNLUVhmb1BmcFNadUF3Il19 -``` - -##### Ability Strings - -Ability Strings identify an action or Ability within a Namespace. They are serialized as `/`. Namespaces and Abilities MUST contain only alphanumeric characters as well as the characters `.`, `*`, `_`, `+`, `-`, conforming to the regex `^[a-zA-Z0-9.*_+-]$`. The ability string as a whole MUST conform to `^[a-zA-Z0-9.*_+-]+\/[a-zA-z0-9.*_+-]+$`. For example, `crud/update` has an ability-namespace of `crud` and an ability-name of `update`. - -##### ReCap Details Object Schema - -The ReCap Details Object denotes which actions on which resources the Relying Party is authorized to invoke on behalf of the Ethereum account for the validity period defined in the SIWE message. It can also contain additional information that the RS may require to verify a capability invocation. A ReCap Details Object MUST follow the following JSON Schema: - -```jsonc -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "att": { - "type": "object", - "propertyNames": { - "format": "uri" - }, - "patternProperties": { - "^.+:.*$": { - "type": "object", - "patternProperties": { - "^[a-zA-Z0-9.*_+-]+\/[a-zA-z0-9.*_+-]+$": { - "type": "array", - "items": { - "type": "object" - } - } - }, - "additionalProperties": false, - "minProperties": 1 - } - }, - "additionalProperties": false, - "minProperties": 1 - }, - "prf": { - "type": "array", - "items": { - "type": "string", - "format": "CID" - }, - "minItems": 1 - } - } -} -``` - -A ReCap Details Object defines the following properties: - -- `att`: (CONDITIONAL) If present, `att` MUST be a JSON object where each key is a URI and each value is an object containing Ability Strings as keys and a corresponding value which is an array of qualifications to the action (i.e. a restriction or requirement). The keys of the object MUST be ordered lexicographically. -- `prf`: (CONDITIONAL) If present, `prf` MUST be a JSON array of string values with at least one entry where each value is a valid Base58-encoded CID which identifies a parent capability, authorizing the Ethereum account for one or more of the entries in `att` if the SIWE `address` does not identify the controller of the `att` entries. - -Objects in the `att` field (including nested objects) MUST NOT contain duplicate keys and MUST have their keys ordered lexicographically with two steps: - -1. Sort by byte value. -2. If a string starts with another, the shorter string comes first (e.g. `msg/send` comes before `msg/send-to`) - -This is the same as the `Array.sort()` method in Javascript. In the example below, `crud/delete` must appear before `crud/update` and `other/action`, similarly `msg/receive` must appear before `msg/send`. - -The following is a non-normative example of a ReCap Capability Object with `att` and `prf`: - -```jsonc -{ - "att":{ - "https://example.com/pictures/":{ - "crud/delete": [{}], - "crud/update": [{}], - "other/action": [{}] - }, - "mailto:username@example.com":{ - "msg/receive": [{ - "max_count": 5, - "templates": ["newsletter", "marketing"] - }], - "msg/send": [{ "to": "someone@email.com" }, { "to": "joe@email.com" }] - } - }, - "prf":["bafybeigk7ly3pog6uupxku3b6bubirr434ib6tfaymvox6gotaaaaaaaaa"] -} -``` - -In the example above, the Relying Party is authorized to perform the actions `crud/update`, `crud/delete` and `other/action` on resource `https://example.com/pictures` without limitations for any. Additionally the Relying Party is authorized to perform actions `msg/send` and `msg/recieve` on resource `mailto:username@example.com`, where `msg/send` is limited to sending to `someone@email.com` or `joe@email.com` and `msg/recieve` is limited to a maximum of 5 and templates `newsletter` or `marketing`. Note, the Relying Party can invoke each action individually and independently from each other in the RS. Additionally the ReCap Capability Object contains some additional information that the RS will need during verification. The responsibility for defining the structure and semantics of this data lies with the RS. These action and restriction semantics are examples not intended to be universally understood. The Nota Bene objects appearing in the array associated with ability strings represent restrictions on use of an ability. An empty object implies that the action can be performed with no restrictions, but an empty array with no objects implies that there is no way to use this ability in a valid way. - -It is expected that RS implementers define which resources they want to expose through ReCap Details Objects and which actions they want to allow users to invoke on them. - -This example is expected to transform into the following `recap-transformed-statement` (for `URI` of `https://example.com`): - -```text -I further authorize the stated URI to perform the following actions on my behalf: (1) 'crud': 'delete', 'update' for 'https://example.com/pictures'. (2) 'other': 'action' for 'https://example.com/pictures'. (3) 'msg': 'recieve', 'send' for 'mailto:username@example.com'. -``` - -This example is also expected to transform into the following `recap-uri`: - -```text -urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlLmNvbS9waWN0dXJlcy8iOnsiY3J1ZC9kZWxldGUiOlt7fV0sImNydWQvdXBkYXRlIjpbe31dLCJvdGhlci9hY3Rpb24iOlt7fV19LCJtYWlsdG86dXNlcm5hbWVAZXhhbXBsZS5jb20iOnsibXNnL3JlY2VpdmUiOlt7Im1heF9jb3VudCI6NSwidGVtcGxhdGVzIjpbIm5ld3NsZXR0ZXIiLCJtYXJrZXRpbmciXX1dLCJtc2cvc2VuZCI6W3sidG8iOiJzb21lb25lQGVtYWlsLmNvbSJ9LHsidG8iOiJqb2VAZW1haWwuY29tIn1dfX0sInByZiI6WyJ6ZGo3V2o2Rk5TNHJVVWJzaUp2amp4Y3NOcVpkRENTaVlSOHNLUVhmb1BmcFNadUF3Il19 -``` - -##### Merging Capability Objects - -Any two Recap objects can be merged together by recursive concatenation of their field elements as long as the ordering rules of the field contents is followed. For example, two recap objects: - -```jsonc -{ - "att": { - "https://example1.com": { - "crud/read": [{}] - } - }, - "prf": ["bafyexample1"] -} - -{ - "att": { - "https://example1.com": { - "crud/update": [{ - "max_times": 1 - }] - }, - "https://example2.com": { - "crud/delete": [{}] - } - }, - "prf": ["bafyexample2"] -} -``` - -combine into: - -```jsonc -{ - "att": { - "https://example1.com": { - "crud/read": [{}], - "crud/update": [{ - "max_times": 1 - }] - }, - "https://example2.com": { - "crud/delete": [{}] - } - }, - "prf": ["bafyexample1", "bafyexample2"] -} -``` - -#### ReCap Translation Algorithm - -After applying the ReCap Translation Algorithm on a given SIWE message that MAY include a pre-defined `statement`, the `recap-transformed-statement` in a ReCap SIWE message MUST conform to the following ABNF: - -```text -recap-transformed-statement = statement recap-preamble 1*(" " recap-statement-entry ".") - ; see ERC-4361 for definition of input-statement -recap-preamble = "I further authorize the stated URI to perform the following actions on my behalf:" -recap-statement-entry = "(" number ") " action-namespace ": " - action-name *("," action-name) "for" - recap-resource - ; see RFC8259 for definition of number -ability-namespace = string - ; see RFC8259 for definition of string -ability-name = string - ; see RFC8259 for definition of string -recap-resource = string - ; see RFC8259 for definition of string -``` - -The following algorithm or an algorithm that produces the same output MUST be performed to generate the SIWE ReCap Transformed Statement. - -Inputs: - -- Let `recap-uri` be a ReCap URI, which represents the ReCap Capabilities that are to be encoded in the SIWE message, and which contains a ReCap Details Object which conforms to the ReCap Details Object Schema. -- [Optional] Let `statement` be the statement field of the input SIWE message conforming to ERC-4361. -Algorithm: -- Let `recap-transformed-statement` be an empty string value. -- If `statement` is present, do the following: - - Append the value of the `statement` field of `siwe` to `recap-transformed-statement`. - - Append a single space character `" "` to `recap-transformed-statement`. -- Append the following string to `recap-transformed-statement`: `"I further authorize the stated URI to perform the following actions on my behalf:"`. -- Let `numbering` be an integer starting with 1. -- Let `attenuations` be the `att` field of the ReCap Details Object -- For each key and value pair in `attenuations` (starting with the first entry), perform the following: - - Let `resource` be the key and `abilities` be the value - - Group the keys of the `abilities` object by their `ability-namespace` - - For each `ability-namespace`, perform the following: - - Append the string concatenation of `" ("`, `numbering`, `")"` to `recap-transformed-statement`. - - Append the string concatenation of `'`, `ability-namespace`, `':` to `recap-transformed-statement`. - - For each `ability-name` in the `ability-namespace` group, perform the following: - - Append the string concatenation of `'`, `ability-name`, `'` to `recap-transformed-statement` - - If not the final `ability-name`, append `,` to `recap-transformed-statement` - - Append `for '`, `resource`, `'.` to `recap-transformed-statement` - - Increase `numbering` by 1 -- Return `recap-transformed-statement`. - -#### ReCap Verification Algorithm - -The following algorithm or an algorithm that produces the same output MUST be performed to verify a SIWE ReCap. - -Inputs: - -- Let `recap-siwe` be the input SIWE message conforming to ERC-4361 and this EIP. -- Let `siwe-signature` be the output of signing `recap-siwe`, as defined in ERC-4361. -Algorithm: -- Perform ERC-4361 signature verification with `recap-siwe` and `siwe-signature` as inputs. -- Let `uri` be the uri field of `recap-siwe`. -- Let `recap-uri` be a recap URI taken from the last entry of the resources field of `recap-siwe`. -- Let `recap-transformed-statement` be the result of performing the above `ReCap Translation Algorithm` with `uri` and `recap-uri` as input. -- Assert that the statement field of `recap-siwe` ends with `recap-transformed-statement`. - -### Implementer's Guide - -TBD - -#### Web3 Application Implementers - -TBD - -#### Wallet Implementers - -TBD - -#### Protocol or API Implementers - -TBD - -## Rationale - -TBD - -## Security Considerations - -Resource service implementer's should not consider ReCaps as bearer tokens but instead require to authenticate the Relying Party in addition. The process of authenticating the Relying Party against the resource service is out of scope of this specification and can be done in various different ways. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5573.md diff --git a/EIPS/eip-5585.md b/EIPS/eip-5585.md index ee67ef6c0dfa6f..0042abda2bd6aa 100644 --- a/EIPS/eip-5585.md +++ b/EIPS/eip-5585.md @@ -1,177 +1 @@ ---- -eip: 5585 -title: ERC-721 NFT Authorization -description: Allows NFT owners to authorize other users to use their NFTs. -author: Veega Labs (@VeegaLabsOfficial), Sean NG (@ngveega), Tiger (@tiger0x), Fred (@apan), Fov Cao (@fovcao) -discussions-to: https://ethereum-magicians.org/t/nft-authorization-erc721-extension/10661 -status: Last Call -last-call-deadline: 2023-10-10 -type: Standards Track -category: ERC -created: 2022-08-15 -requires: 721 ---- - -## Abstract - -This EIP separates the [ERC-721](./eip-721.md) NFT's commercial usage rights from its ownership to allow for the independent management of those rights. - -## Motivation - -Most NFTs have a simplified ownership verification mechanism, with a sole owner of an NFT. Under this model, other rights, such as display, or creating derivative works or distribution, are not possible to grant, limiting the value and commercialization of NFTs. Therefore, the separation of an NFT's ownership and user rights can enhance its commercial value. - -Commercial right is a broad concept based on the copyright, including the rights of copy, display, distribution, renting, commercial use, modify, reproduce and sublicense etc. With the development of the Metaverse, NFTs are becoming more diverse, with new use cases such as digital collections, virtual real estate, music, art, social media, and digital asset of all kinds. The copyright and authorization based on NFTs are becoming a potential business form. - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Contract Interface - -```solidity -interface IERC5585 { - - struct UserRecord { - address user; - string[] rights; - uint256 expires - } - - /// @notice Get all available rights of this NFT project - /// @return All the rights that can be authorized to the user - function getRights() external view returns(string[]); - - /// @notice NFT holder authorizes all the rights of the NFT to a user for a specified period of time - /// @dev The zero address indicates there is no user - /// @param tokenId The NFT which is authorized - /// @param user The user to whom the NFT is authorized - /// @param duration The period of time the authorization lasts - function authorizeUser(uint256 tokenId, address user, uint duration) external; - - /// @notice NFT holder authorizes specific rights to a user for a specified period of time - /// @dev The zero address indicates there is no user. It will throw exception when the rights are not defined by this NFT project - /// @param tokenId The NFT which is authorized - /// @param user The user to whom the NFT is authorized - /// @param rights Rights autorised to the user, such as renting, distribution or display etc - /// @param duration The period of time the authorization lasts - function authorizeUser(uint256 tokenId, address user, string[] rights, uint duration) external; - - /// @notice The user of the NFT transfers his rights to the new user - /// @dev The zero address indicates there is no user - /// @param tokenId The rights of this NFT is transferred to the new user - /// @param newUser The new user - function transferUserRights(uint256 tokenId, address newUser) external; - - /// @notice NFT holder extends the duration of authorization - /// @dev The zero address indicates there is no user. It will throw exception when the rights are not defined by this NFT project - /// @param tokenId The NFT which has been authorized - /// @param user The user to whom the NFT has been authorized - /// @param duration The new duration of the authorization - function extendDuration(uint256 tokenId, address user, uint duration) external; - - /// @notice NFT holder updates the rights of authorization - /// @dev The zero address indicates there is no user - /// @param tokenId The NFT which has been authorized - /// @param user The user to whom the NFT has been authorized - /// @param rights New rights autorised to the user - function updateUserRights(uint256 tokenId, address user, string[] rights) external; - - /// @notice Get the authorization expired time of the specified NFT and user - /// @dev The zero address indicates there is no user - /// @param tokenId The NFT to get the user expires for - /// @param user The user who has been authorized - /// @return The authorization expired time - function getExpires(uint256 tokenId, address user) external view returns(uint); - - /// @notice Get the rights of the specified NFT and user - /// @dev The zero address indicates there is no user - /// @param tokenId The NFT to get the rights - /// @param user The user who has been authorized - /// @return The rights has been authorized - function getUserRights(uint256 tokenId, address user) external view returns(string[]); - - /// @notice The contract owner can update the number of users that can be authorized per NFT - /// @param userLimit The number of users set by operators only - function updateUserLimit(unit256 userLimit) external onlyOwner; - - /// @notice resetAllowed flag can be updated by contract owner to control whether the authorization can be revoked or not - /// @param resetAllowed It is the boolean flag - function updateResetAllowed(bool resetAllowed) external onlyOwner; - - /// @notice Check if the token is available for authorization - /// @dev Throws if tokenId is not a valid NFT - /// @param tokenId The NFT to be checked the availability - /// @return true or false whether the NFT is available for authorization or not - function checkAuthorizationAvailability(uint256 tokenId) public view returns(bool); - - /// @notice Clear authorization of a specified user - /// @dev The zero address indicates there is no user. The function works when resetAllowed is true and it will throw exception when false - /// @param tokenId The NFT on which the authorization based - /// @param user The user whose authorization will be cleared - function resetUser(uint256 tokenId, address user) external; - - - /// @notice Emitted when the user of a NFT is changed or the authorization expires time is updated - /// param tokenId The NFT on which the authorization based - /// param indexed user The user to whom the NFT authorized - /// @param rights Rights autorised to the user - /// param expires The expires time of the authorization - event authorizeUser(uint256 indexed tokenId, address indexed user, string[] rights, uint expires); - - /// @notice Emitted when the number of users that can be authorized per NFT is updated - /// @param userLimit The number of users set by operators only - event updateUserLimit(unit256 userLimit); -} -``` - -The `getRights()` function MAY be implemented as pure and view. - -The `authorizeUser(uint256 tokenId, address user, uint duration)` function MAY be implemented as `public` or `external`. - -The `authorizeUser(uint256 tokenId, address user, string[] rights; uint duration)` function MAY be implemented as `public` or `external`. - -The `transferUserRights(uint256 tokenId, address newUser)` function MAY be implemented as `public` or `external`. - -The `extendDuration(uint256 tokenId, address user, uint duration)` function MAY be implemented as `public` or `external`. - -The `updateUserRights(uint256 tokenId, address user, string[] rights)` function MAY be implemented as `public` or `external`. - -The `getExpires(uint256 tokenId, address user)` function MAY be implemented as `pure` or `view`. - -The `getUserRights(uint256 tokenId, address user)` function MAY be implemented as pure and view. - -The `updateUserLimit(unit256 userLimit)` function MAY be implemented as`public` or `external`. - -The `updateResetAllowed(bool resetAllowed)` function MAY be implemented as `public` or `external`. - -The `checkAuthorizationAvailability(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `resetUser(uint256 tokenId, address user)` function MAY be implemented as `public` or `external`. - -The `authorizeUser` event MUST be emitted when the user of a NFT is changed or the authorization expires time is updated. - -The `updateUserLimit` event MUST be emitted when the number of users that can be authorized per NFT is updated. - -## Rationale - -First of all, NFT contract owner can set the maximum number of authorized users to each NFT and whether the NFT owner can cancel the authorization at any time to protect the interests of the parties involved. - -Secondly, there is a `resetAllowed` flag to control the rights between the NFT owner and the users for the contract owner. If the flag is set to true, then the NFT owner can disable usage rights of all authorized users at any time. - -Thirdly, the rights within the user record struct is used to store what rights has been authorized to a user by the NFT owner, in other words, the NFT owner can authorize a user with specific rights and update it when necessary. - -Finally, this design can be seamlessly integrated with third parties. It is an extension of ERC-721, therefore it can be easily integrated into a new NFT project. Other projects can directly interact with these interfaces and functions to implement their own types of transactions. For example, an announcement platform could use this EIP to allow all NFT owners to make authorization or deauthorization at any time. - -## Backwards Compatibility - -This standard is compatible with [ERC-721](./eip-721.md) since it is an extension of it. - -## Security Considerations - -When the `resetAllowed` flag is false, which means the authorization can not be revoked by NFT owner during the period of authorization, users of the EIP need to make sure the authorization fee can be fairly assigned if the NFT was sold to a new holder. - -Here is a solution for taking reference: the authorization fee paid by the users can be held in an escrow contract for a period of time depending on the duration of the authorization. For example, if the authorization duration is 12 months and the fee in total is 10 ETH, then if the NFT is transferred after 3 months, then only 2.5 ETH would be sent and the remaining 7.5 ETH would be refunded. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5585.md diff --git a/EIPS/eip-5604.md b/EIPS/eip-5604.md index 671d115e324cea..3d5c6c7945af7d 100644 --- a/EIPS/eip-5604.md +++ b/EIPS/eip-5604.md @@ -1,95 +1 @@ ---- -eip: 5604 -title: NFT Lien -description: Extend EIP-721 to support putting liens on NFT -author: Allen Zhou , Alex Qin , Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/creating-a-new-erc-proposal-for-nft-lien/10683 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-05 -requires: 165, 721 ---- - -## Abstract - -This EIP introduces NFT liens, a form of security interest over an item of property to secure the recovery of liability or performance of some other obligation. It introduces an interface to place and removes a lien, plus an event. - -## Motivation - -Liens are widely used for finance use cases, such as car and property liens. An example use case for an NFT lien is for a deed. -This EIP provides an interface to implement an interface that performs the lien holding relationships. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -1. Any compliant contract MUST implement [EIP-721](./eip-721.md), and [EIP-165](./eip-165.md). - -2. Any compliant contract MUST implement the following interface: - -```solidity -interface IERC_LIEN is EIP721, EIP165 { - - /// === Events === - - /// @notice MUST be emitted when new lien is successfully placed. - /// @param tokenId the token a lien is placed on. - /// @param holder the holder of the lien. - /// @param extraParams of the original request to add the lien. - event OnLienPlaced(uint256 tokenId, address holder, bytes calldata extraParams); - - /// @notice MUST be emitted when an existing lien is successfully removed. - /// @param tokenId the token a lien was removed from. - /// @param holder the holder of the lien. - /// @param extraParams of the original request to remove the lien. - event OnLienRemoved(uint256 tokenId, address holder, bytes calldata extraParams); - - /// === CRUD === - - /// @notice The method to place a lien on a token - /// it MUST throw an error if the same holder already has a lien on the same token. - /// @param tokenId the token a lien is placed on. - /// @param holder the holder of the lien - /// @param extraParams extra data for future extension. - function addLienHolder(uint256 tokenId, address holder, bytes calldata extraParams) public; - - /// @notice The method to remove a lien on a token - /// it MUST throw an error if the holder already has a lien. - /// @param tokenId the token a lien is being removed from. - /// @param holder the holder of the lien - /// @param extraParams extra data for future extension. - function removeLienHolder(uint256 tokenId, address holder, bytes calldata extraParams) public; - - /// @notice The method to query if an active lien exists on a token. - /// it MUST throw an error if the tokenId doesn't exist or is not owned. - /// @param tokenId the token a lien is being queried for - /// @param holder the holder about whom the method is querying about lien holding. - /// @param extraParams extra data for future extension. - function hasLien(uint256 tokenId, address holder, bytes calldata extraParams) public view returns (bool); -} -``` - -## Rationale - -1. We only support [EIP-721](./eip-721.md) NFTs for simplicity and gas efficiency. We have not considered other EIPs, which can be left for future extensions. For example, [EIP-20](./eip-20.md) and [EIP-1155](./eip-1155.md) were not considered. - -2. We choose separate "addLienHolder" and "removeLienHolder" instead of use a single `changeLienholder` with amount because we believe -the add or remove action are significantly different and usually require different Access Control, -for example, the token holder shall be able to add someone else as a lien holder but the lien holder of that token. - -3. We have not specified the "amount of debt" in this interface. We believe this is complex enough and worthy of an individual EIP by itself. - -4. We have not specified how endorsement can be applied to allow holder to signal their approval for transfer or swapping. We believe this is complex enough and worthy of an individual EIP by itself. - -## Backwards Compatibility - -The EIP is designed as an extension of EIP-721 and therefore compliant contracts need to fully comply with EIP-721. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5604.md diff --git a/EIPS/eip-5606.md b/EIPS/eip-5606.md index aee18b4916818e..0db15720cfa52d 100644 --- a/EIPS/eip-5606.md +++ b/EIPS/eip-5606.md @@ -1,113 +1 @@ ---- -eip: 5606 -title: Multiverse NFTs -description: A universal representation of multiple related NFTs as a single digital asset across various platforms -author: Gaurang Torvekar (@gaurangtorvekar), Khemraj Adhawade (@akhemraj), Nikhil Asrani (@nikhilasrani) -discussions-to: https://ethereum-magicians.org/t/eip-5606-multiverse-nfts-for-digital-asset-interoperability/10698 -status: Final -type: Standards Track -category: ERC -created: 2022-09-06 -requires: 721, 1155 ---- - -## Abstract - -This specification defines a minimal interface to create a multiverse NFT standard for digital assets such as wearables and in-game items that, in turn, index the delegate NFTs on each platform where this asset exists. These platforms could be metaverses, play-to-earn games or NFT marketplaces. This proposal depends on and extends [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md). The standard also allows for the ‘bundling’ and ‘unbundling’ of these delegate NFTs within the multiverse NFT so holders can trade them individually or as a bundle. - -## Motivation - -Several metaverses and blockchain games ("platforms") exist that use NFT standards such as ERC-721 and ERC-1155 for creating in-universe assets like avatar wearables, in-game items including weapons, shields, potions and much more. The biggest shortcoming while using these standards is that there is no interoperability between these platforms. As a publisher, you must publish the same digital asset (for example, a shirt) on various platforms as separate ERC-721 or ERC-1155 tokens. Moreover, there is no relationship between these, although they represent the same digital asset in reality. Hence, it is very difficult to prove the scarcity of these items on-chain. - -Since their inception, NFTs were meant to be interoperable and prove the scarcity of digital assets. Although NFTs can arguably prove the scarcity of items, the interoperability aspect hasn’t been addressed yet. Creating a multiverse NFT standard that allows for indexing and ownership of a digital asset across various platforms would be the first step towards interoperability and true ownership across platforms. - -In the web3 ecosystem, NFTs have evolved to represent multiple types of unique and non-fungible assets. One type of asset includes a set of NFTs related to one another. For instance, if a brand releases a new sneaker across various metaverses, it would be minted as a separate NFT on each platform. However, it is, in reality, the same sneaker. -There is a need to represent the relationship and transferability of these types of NFTs as metaverses and blockchain games gain more mainstream adoption. The ecosystem needs a better framework to address this issue rather than relying on the application level. This framework should define the relationship between these assets and the nature of their association. There is more value in the combined recognition, use and transferability of these individual NFTs as a bundle rather than their selves. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -A multiverse NFT contract represents a digital asset across multiple platforms. This contract can own one or more delegate NFT tokens of the digital asset on the various platforms through bundling or unbundling. - -``` -/** -* @dev Interface of the Multiverse NFT standard as defined in the EIP. -*/ -interface IMultiverseNFT { - - /** - * @dev struct to store delegate token details - * - */ - struct DelegateData { - address contractAddress; - uint256 tokenId; - uint256 quantity; - } - - /** - * @dev Emitted when one or more new delegate NFTs are added to a Multiverse NFT - */ - event Bundled(uint256 multiverseTokenID, DelegateData[] delegateData, address ownerAddress); - - - /** - * @dev Emitted when one or more delegate NFTs are removed from a Multiverse NFT - */ - event Unbundled(uint256 multiverseTokenID, DelegateData[] delegateData); - - /** - * @dev Accepts the tokenId of the Multiverse NFT and returns an array of delegate token data - */ - function delegateTokens(uint256 multiverseTokenID) external view returns (DelegateData[] memory); - - /** - * @dev Removes one or more delegate NFTs from a Multiverse NFT - * This function accepts the delegate NFT details and transfers those NFTs out of the Multiverse NFT contract to the owner's wallet - */ - function unbundle(DelegateData[] memory delegateData, uint256 multiverseTokenID) external; - - /** - * @dev Adds one or more delegate NFTs to a Multiverse NFT - * This function accepts the delegate NFT details and transfers those NFTs to the Multiverse NFT contract - * Need to ensure that approval is given to this Multiverse NFT contract for the delegate NFTs so that they can be transferred programmatically - */ - function bundle(DelegateData[] memory delegateData, uint256 multiverseTokenID) external; - - /** - * @dev Initialises a new bundle, mints a Multiverse NFT and assigns it to msg.sender - * Returns the token ID of a new Multiverse NFT - * Note - When a new Multiverse NFT is initialised, it is empty; it does not contain any delegate NFTs - */ - function initBundle(DelegateData[] memory delegateData) external; -} -``` - -Any dapp implementing this standard would initialise a bundle by calling the function `initBundle`. This mints a new multiverse NFT and assigns it to msg.sender. While creating a bundle, the delegate token contract addresses and the token IDs are set during the initialisation and cannot be changed after that. This avoids unintended edge cases where non-related NFTs could be bundled together by mistake. - -Once a bundle is initialised, the delegate NFT tokens can then be transferred to this Multiverse NFT contract by calling the function `bundle` and passing the token ID of the multiverse NFT. It is essential for a dapp to get the delegate NFTs ‘approved’ from the owner to this Multiverse NFT contract before calling the bundle function. After that, the Multiverse NFT owns one or more versions of this digital asset across the various platforms. - -If the owner of the multiverse NFT wants to sell or use the individual delegate NFTs across any of the platforms, they can do so by calling the function `unbundle`. This function transfers the particular delegate NFT token(s) to msg.sender (only if `msg.sender` is the owner of the multiverse NFT). - -## Rationale - -The `delegateData` struct contains information about the delegate NFT tokens on each platform. It contains variables such as `contractAddress`, `tokenId`, `quantity` to differentiate the NFTs. These NFTs could be following either the ERC-721 standard or the ERC-1155 standard. - -The `bundle` and `unbundle` functions accept an array of DelegateData struct because of the need to cater to partial bundling and unbundling. For instance, a user could initialise a bundle with three delegate NFTs, but they should be able to bundle and unbundle less than three at any time. They can never bundle or unbundle more than three. They also need the individual token IDs of the delegate NFTs to bundle and unbundle selectively. - -## Backwards Compatibility - -This standard is fully compatible with ERC-721 and ERC-1155. Third-party applications that don’t support this EIP will still be able to use the original NFT standards without any problems. - -## Reference Implementation - -[MultiverseNFT.sol](../assets/eip-5606/contracts/MultiverseNFT.sol) - -## Security Considerations - -The bundle function involves calling an external contract(s). So reentrancy prevention measures should be applied while implementing this function. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5606.md diff --git a/EIPS/eip-5615.md b/EIPS/eip-5615.md index 8bc5f4be5a090a..ffb605e7eebd17 100644 --- a/EIPS/eip-5615.md +++ b/EIPS/eip-5615.md @@ -1,58 +1 @@ ---- -eip: 5615 -title: ERC-1155 Supply Extension -description: A simple mechanism to fetch token supply data from ERC-1155 tokens -author: Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/eip-5615-eip-1155-supply-extension/10732 -status: Final -type: Standards Track -category: ERC -created: 2023-05-25 -requires: 1155 ---- - -## Abstract - -This ERC standardizes an existing mechanism to fetch token supply data from [ERC-1155](./eip-1155.md) tokens. It adds a `totalSupply` function, which fetches the number of tokens with a given `id`, and an `exists` function, which checks for the existence of a given `id`. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -interface ERC1155Supply is ERC1155 { - // @notice This function MUST return whether the given token id exists, previously existed, or may exist - // @param id The token id of which to check the existence - // @return Whether the given token id exists, previously existed, or may exist - function exists(uint256 id) external view returns (bool); - - // @notice This function MUST return the number of tokens with a given id. If the token id does not exist, it MUST return 0. - // @param id The token id of which fetch the total supply - // @return The total supply of the given token id - function totalSupply(uint256 id) external view returns (uint256); -} -``` - -Implementations MAY support [ERC-165](./eip-165.md) interface discovery, but consumers MUST NOT rely on it. - -## Rationale - -This ERC does not implement [ERC-165](./eip-165.md), as this interface is simple enough that the extra complexity is unnecessary and would cause incompatibilities with pre-existing implementations. - -The `totalSupply` and `exists` functions were modeled after [ERC-721](./eip-721.md) and [ERC-20](./eip-20.md). - -`totalSupply` does not revert if the token ID does not exist, since contracts that care about that case should use `exists` instead (which might return false even if `totalSupply` is zero). - -`exists` is included to differentiate between the two ways that `totalSupply` could equal zero (either no tokens with the given ID have been minted yet, or no tokens with the given ID will ever be minted). - -## Backwards Compatibility - -This ERC is designed to be backward compatible with the OpenZeppelin `ERC1155Supply`. - -## Security Considerations - -None. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5615.md diff --git a/EIPS/eip-5625.md b/EIPS/eip-5625.md index d12d6cae8aab6d..f01f859b5fec24 100644 --- a/EIPS/eip-5625.md +++ b/EIPS/eip-5625.md @@ -1,185 +1 @@ ---- -eip: 5625 -title: NFT Metadata JSON Schema dStorage Extension -description: Add a dStorage property to non-fungible tokens (NFTs) metadata JSON schema to provide decentralized storage information of NFT assets -author: Gavin Fu (@gavfu) -discussions-to: https://ethereum-magicians.org/t/eip-5625-nft-metadata-json-schema-dstorage-extension/10754 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-08 -requires: 721, 1155 ---- - -## Abstract - -This EIP extends the NFT metadata JSON schema defined in [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md), adding a `dStorage` key that provides information about how the NFT data is stored. - -## Motivation - -As highly valuable crypto properties, NFT assets intrinsically demand guaranteed storage to assure their **immutability**, **reliability**, and **durability**. NFT ownership is tracked by [EIP-721](./eip-721.md) or [EIP-1155](./eip-1155.md) smart contracts, hence persisted in blockchain, which is not a problem. But how about the mime-type assets that NFT tokens represent? Ideally, they should also be stored in some reliable and verifiable decentralized storage system that is designed to store larger amounts of data than the blockchain itself. As an effort to promote **decentralized storage** adoption in NFT world, we propose to add additional **dStorage** information into NFT metadata JSON schema. - -As a refresher, let's review existing NFT metadata JSON schema standards. [EIP-721](./eip-721.md) defines a standard contract method `tokenURI` to return a given NFT's metadata JSON file, conforming to the *[EIP-721](./eip-721.md) Metadata JSON Schema*, which defines three properties: `name`, `description` and `image`. - -Similarly, [EIP-1155](./eip-1155.md) also defines a standard contract method `uri` to return NFT metadata JSON files conforming to the *[EIP-1155](./eip-1155.md) Metadata JSON Schema*, which defines properties like `name`, `decimals`, `description`, `image`, `properties`, `localization`, etc. - -Besides, as the world's largest NFT marketplace nowadays, OpenSea defines their own *Metadata Standards*, including a few more properties like `image_data`, `external_url`, `attributes`, `background_color`, `animation_url`, `youtube_url`, etc. This standard is de facto respected and followed by other NFT marketplaces like LooksRare. - -None of these standards conveys storage information about the mime-type asset that the NFT token represents. This proposal is an effort to fill the missing part. - - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -In addition to the existing properties, the Metadata JSON file returned by [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) smart contracts (via `tokenURI` and `uri` methods, respectively), should OPTIONALLY contains one more `dStorage` property. - -For [EIP-721](./eip-721.md) smart contracts, the Metadata JSON file schema is: - -```json -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "dStorage": { - "type": "object", - "required": ["platform", "description", "persistence_mechanism", "challenge_mechanism", "consensus", "dstorage_note"], - "properties": { - "platform": { - "type": "string", - "description": "dStorage platform name like Swarm, Arweave, Filecoin, Crust, etc" - }, - "description": { - "type": "string", - "description": "A brief description of the dStorage platform" - }, - "persistence_mechanism": { - "type": "string", - "description": "Persistence mechanism or incentive structure of the dStorage platform, like 'blockchain-based', 'contract-based', etc" - }, - "challenge_mechanism": { - "type": "string", - "description": "Challenge mechanism of the dStorage platform, like Arweave's proof-of-access, etc" - }, - "consensus": { - "type": "string", - "description": "Consensus mechanism of the dStorage platform, like PoW, PoS, etc" - }, - "dstorage_note": { - "type": "string", - "description": "A note to prove the storage of the NFT asset on the dStorage platform, like a Filecoin deal id, a Crust place_storage_order transaction hash, etc" - } - } - } - } -} -``` - -For [EIP-1155](./eip-1155.md) smart contracts, the Metadata JSON file schema is: - -```json -{ - "title": "Token Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this token represents", - }, - "decimals": { - "type": "integer", - "description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation." - }, - "description": { - "type": "string", - "description": "Describes the asset to which this token represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "properties": { - "type": "object", - "description": "Arbitrary properties. Values may be strings, numbers, object or arrays.", - }, - "localization": { - "type": "object", - "required": ["uri", "default", "locales"], - "properties": { - "uri": { - "type": "string", - "description": "The URI pattern to fetch localized data from. This URI should contain the substring `{locale}` which will be replaced with the appropriate locale value before sending the request." - }, - "default": { - "type": "string", - "description": "The locale of the default data within the base JSON" - }, - "locales": { - "type": "array", - "description": "The list of locales for which data is available. These locales should conform to those defined in the Unicode Common Locale Data Repository (http://cldr.unicode.org/)." - } - } - }, - "dStorage": { - "type": "object", - "required": ["platform", "description", "persistence_mechanism", "challenge_mechanism", "consensus", "dstorage_note"], - "properties": { - "platform": { - "type": "string", - "description": "dStorage platform name like Swarm, Arweave, Filecoin, Crust, etc" - }, - "description": { - "type": "string", - "description": "A brief description of the dStorage platform" - }, - "persistence_mechanism": { - "type": "string", - "description": "Persistence mechanism or incentive structure of the dStorage platform, like 'blockchain-based', 'contract-based', etc" - }, - "challenge_mechanism": { - "type": "string", - "description": "Challenge mechanism of the dStorage platform, like Arweave's proof-of-access, etc" - }, - "consensus": { - "type": "string", - "description": "Consensus mechanism of the dStorage platform, like PoW, PoS, etc" - }, - "dstorage_note": { - "type": "string", - "description": "A note to prove the storage of the NFT asset on the dStorage platform, like a Filecoin deal id, a Crust place_storage_order transaction hash, etc" - } - } - } - } -} -``` - -## Rationale - -### Choice between Interface and JSON Schema Extension - -An extension of the EIP-721 or EIP-1155 contract interfaces would unnecessarily require additional code to implement, and would not be available for use by NFT projects that already have their NFT smart contracts finalized and deployed. An optional JSON schema extension is noninvasive, and more easily adopted. - -# Backwards Compatibility - -This EIP is backward compatible with [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md). - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). \ No newline at end of file +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5625.md diff --git a/EIPS/eip-5630.md b/EIPS/eip-5630.md index 70003c83c2a822..309a6f293f6aae 100644 --- a/EIPS/eip-5630.md +++ b/EIPS/eip-5630.md @@ -1,205 +1 @@ ---- -eip: 5630 -title: New approach for encryption / decryption -description: defines a specification for encryption and decryption using Ethereum wallets. -author: Firn Protocol (@firnprotocol), Fried L. Trout, Weiji Guo (@weijiguo) -discussions-to: https://ethereum-magicians.org/t/eip-5630-encryption-and-decryption/10761 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-07 ---- - - -## Abstract - -This EIP proposes a new way to encrypt and decrypt using Ethereum keys. This EIP uses _only_ the `secp256k1` curve, and proposes two new RPC methods: `eth_getEncryptionPublicKey` and `eth_performECDH`. These two methods, in conjunction, allow users to receive encryptions and perform decryptions (respectively). We require that the wallet _only_ perform the core ECDH operation, leaving the ECIES operations up to implementers (we do suggest a standardized version of ECIES, however). In contrast, a previous EIPs used the same secret key, in both signing and encryption, on two _different_ curves (namely, `secp256k1` and `ec25519`), and hardcoded a particular version of ECIES. - -## Motivation - -We discuss a few motivating examples. One key motivation is direct-to-address encryption on Ethereum. Using our EIP, one can directly send encrypted messages to some desired recipient on-chain, without having a prior direct channel to that recipient. (Note that in this EIP, we standardize _only_ the encryption procedure—that is, the generation of the ciphertext—and _not_ how exactly the on-chain message should be sent. In practice, ideally, smart-contract infrastructure will be set up for this purpose; barring this, encryptors could make use of the raw `data` field available in each standard transfer.) - -We discuss a second sort of example. In a certain common design pattern, a dApp generates a fresh secret on behalf of a user. It is of interest if, instead of forcing this user to independently store, safeguard, and back up this latter secret, the dApp may instead encrypt this secret to a public key which the user controls—and whose secret key, crucially, resides within the user's HD wallet hierarchy—and then post the resulting ciphertext to secure storage (e.g., on-chain). This design pattern allows the dApp/user to bootstrap the security of the _fresh_ secret onto the security of the user's existing HD wallet seed phrase, which the user has already gone through the trouble of safeguarding and storing. This represents a far lower UX burden than forcing the user to store and manage fresh keys directly (which can, and often does, lead to loss of funds). We note that this design pattern described above is used today by, various dApps (e.g., Tornado Cash). - -## Specification - -We describe our approach here; we compare our approach to prior EIPs in the **Rationale** section below. Throughout, we make reference to SEC 1: Elliptic Curve Cryptography, by Daniel R. L. Brown. - -We use the `secp256k1` curve for both signing and encryption. -For encryption, we use ECIES. We specify that the wallet _only_ perform the sensitive ECDH operation. This lets implementers select their own ECIES variants at will. - -We propose that all binary data be serialized to and from `0x`-prefixed hex strings. We moreover use `0x`-prefixed hex strings to specify private keys and public keys, and represent public keys in compressed form. We represent Ethereum accounts in the usual way (`0x`-prefixed, 20-byte hex strings). Specifically, to serialize and deserialize elliptic curve points, implementers MUST use the following standard: - -- to serialize a point: use [SEC 1, §2.3.3], with point compression. -- to deserialize a point: use [SEC 1, §2.3.3], while _requiring_ point compression; that is: - - - the input byte string MUST have length ⌈log₂q / 8⌉ + 1 = `33`. - - the first byte MUST be `0x02` or `0x03`. - - the integer represented by the remaining 32 bytes (as in [SEC 1, §2.3.8]) MUST reside in {0, ..., _p_ - 1}, and moreover MUST yield a quadratic residue modulo _p_ under the Weierstrass expression X^3 + 7 (modulo _p_). - -For application-level implementers actually implementing ECIES, we propose the following variant. Unless they have a reason to do otherwise, implementers SHOULD use the following standardized choices: - -- the KDF `ANSI-X9.63-KDF`, where the hash function `SHA-512` is used, -- the HMAC `HMAC–SHA-256–256 with 32 octet or 256 bit keys`, -- the symmetric encryption scheme `AES–256 in CBC mode`. - -We propose that the binary, _concatenated_ serialization mode for ECIES ciphertexts be used, both for encryption and decryption, where moreover elliptic curve points are _compressed_. - -Thus, on the request: - -```javascript -request({ - method: 'eth_getEncryptionPublicKey', - params: [account] -}) -``` - -where `account` is a standard 20-byte, `0x`-prefixed, hex-encoded Ethereum account, the client should operate as follows: - -- find the secret signing key `sk` corresponding to the Ethereum account `account`, or else return an error if none exists. -- compute the `secp256k1` public key corresponding to `sk`. -- return this public key in compressed, `0x`-prefixed, hex-encoded form, following [SEC 1, §2.3.3]. - -On the request - -```javascript -request({ - method: 'eth_performECDH', - params: [account, ephemeralKey] -}) -``` - -where `account` is as above, and `ephemeralKey` is an elliptic curve point encoded as above: - -- find the secret key `sk` corresponding to the Ethereum account `account`, or else return an error if none exists. -- deserialize `ephemeralKey` to an elliptic curve point using [SEC 1, §2.3.3] (where compression is required), throwing an error if deserialization fails. -- compute the elliptic curve Diffie–Hellman secret, following [SEC 1, §3.3.1]. -- return the resulting field element as an 0x-prefixed, hex-encoded, 32-byte string, using [SEC 1, §2.3.5]. - -Test vectors are given below. - -### Encrypting to a smart contract - -In light of account abstraction, [EIP-4337](eip-4337.md), and the advent of smart-contract wallets, we moreover specify a way to encrypt to a contract. -More precisely, we specify a way for a contract to _advertise_ how it would like encryptions to it to be constructed. This should be viewed as an analogue of [EIP-1271](eip-1271.md), but for encryption, as opposed to signing. - -Our specification is as follows. - -```solidity -pragma solidity ^0.8.0; - -contract ERC5630 { - /** - * @dev Should return an encryption of the provided plaintext, using the provided randomness. - * @param plaintext Plaintext to be encrypted - * @param randomness Entropy to be used during encryption - */ - function encryptTo(bytes memory plaintext, bytes32 randomness) - public - view - returns (bytes memory ciphertext); -} -``` - -Each contract MAY implement `encryptTo` as it desires. Unless it has a good reason to do otherwise, it SHOULD use the ECIES variant we propose above. - -## Rationale - -There is _no security proof_ for a scheme which simultaneously invokes signing on the `secp256k1` curve and encryption on the `ec25519` curve, and where _the same secret key is moreover used in both cases_. Though no attacks are known, it is not desirable to use a scheme which lacks a proof in this way. -We, instead, propose the reuse of the same key in signing and encryption, but where _the same curve is used in both_. This very setting has been studied in prior work; see, e.g., Degabriele, Lehmann, Paterson, Smart and Strefler, _On the Joint Security of Encryption and Signature in EMV_, 2011. That work found this joint scheme to be secure in the generic group model. -We note that this very joint scheme (i.e., using ECDSA and ECIES on the same curve) is used live in production in EMV payments. - -We now discuss a few further aspects of our approach. - -**On-chain public key discovery.** Our proposal has an important feature whereby an encryption _to_ some account can be constructed whenever that account has signed at least one transaction. -Indeed, it is possible to recover an account's `secp256k1` public key directly from any signature on behalf of that account. - -**ECDH vs. ECIES.** We specify that the wallet _only_ perform the sensitive ECDH operation, and let application-level implementers perform the remaining steps of ECIES. This has two distinct advantages: - -- **Flexibility.** It allows implementers to select arbitrary variants of ECIES, without having to update what the wallet does. -- **Bandwidth.** Our approach requires that only small messages (on the order of 32 bytes) be exchanged between the client and the wallet. This could be material in settings in which the plaintexts and ciphertexts at play are large, and when the client and the wallet are separated by an internet connection. - -**Twist attacks.** A certain GitHub post by Christian Lundkvist warns against "twist attacks" on the `secp256k1` curve. These attacks are not applicable to this EIP, for multiple _distinct_ reasons, which we itemize: - -- **Only applies to classical ECDH, not ECIES.** This attack only applies to classical ECDH (i.e., in which both parties use persistent, authenticated public keys), and not to ECIES (in which one party, the encryptor, uses an ephemeral key). Indeed, it only applies to a scenario in which an attacker can induce a victim to exponentiate an attacker-supplied point by a sensitive scalar, and then moreover send the result back to the attacker. But this pattern only happens in classical Diffie–Hellman, and never in ECIES. Indeed, in ECIES, we recall that the only sensitive Diffie–Hellman operation happens during decryption, but in this case, the victim (who would be the decryptor) never sends the resulting DH point back to the attacker (rather, the victim merely uses it locally to attempt an AES decryption). During _encryption_, the exponentiation is done by the encryptor, who has no secret at all (sure enough, the exponentiation is by an ephemeral scalar), so here there would be nothing for the attacker to learn. -- **Only applies to uncompressed points.** Indeed, we use compressed points in this EIP. When compressed points are used, each 33-byte string _necessarily_ either resolves to a point on the correct curve, or else has no reasonable interpretation. There is no such thing as "a point not on the curve" (which, in particular, can pass undetectedly as such). -- **Only applies when you fail to check a point is on the curve.** But this is inapplicable for us anyway, since we use compressed points (see above). We also require that all validations be performed. - -## Backwards Compatibility - -Our `eth_performECDH` method is new, and so doesn't raise any backwards compatibility issues. - -A previous proposal proposed an `eth_getEncryptionPublicKey` method (together with an `eth_decrypt` method unrelated to this EIP). Our proposal overwrites the previous behavior of `eth_getEncryptionPublicKey`. -It is unlikely that this will be an issue, since encryption keys need be newly retrieved _only_ upon the time of encryption; on the other hand, _new_ ciphertexts will be generated using our new approach. -(In particular, our modification will not affect the ability of ciphertexts generated using the old EIP to be `eth_decrypt`ed.) - -In any case, the previous EIP was never standardized, and is _not_ (to our knowledge) implemented in a non-deprecated manner in _any_ production code today. - -### Test Cases - -The secret _signing key_ - -``` - 0x439047a312c8502d7dd276540e89fe6639d39da1d8466f79be390579d7eaa3b2 -``` - -with Ethereum address `0x72682F2A3c160947696ac3c9CC48d290aa89549c`, has `secp256k1` public key - -``` - 0x03ff5763a2d3113229f2eda8305fae5cc1729e89037532a42df357437532770010 -``` - -Thus, the request: - -```javascript -request({ - method: 'eth_getEncryptionPublicKey', - params: ["0x72682F2A3c160947696ac3c9CC48d290aa89549c"] -}) -``` - -should return: - -```javascript -"0x03ff5763a2d3113229f2eda8305fae5cc1729e89037532a42df357437532770010" -``` - -If an encryptor were to encrypt a message—say, `I use Firn Protocol to gain privacy on Ethereum.`—under the above public key, using the above ECIES variant, he could obtain, for example: - -```javascript -"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf850e6c2af8fb38e3e31d679deac82bd12148332fa0e34aecb31981bd4fe8f7ac1b74866ce65cbe848ee7a9d39093e0de0bd8523a615af8d6a83bbd8541bf174f47b1ea2bd57396b4a950a0a2eb77af09e36bd5832b8841848a8b302bd816c41ce" -``` - -Upon obtaining this ciphertext, the decryptor would extract the relevant ephemeral public key, namely: - -```javascript -"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8" -``` - -And submit the request: - -```javascript -request({ - method: 'eth_performECDH', - params: [ - "0x72682F2A3c160947696ac3c9CC48d290aa89549c", - "0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8" - ] -}) -``` - -which in turn would return the Diffie–Hellman secret: - -```javascript -"0x4ad782e7409702101abe6d0279f242a2c545c46dd50a6704a4b9e3ae2730522e" -``` - -Upon proceeding with the above ECIES variant, the decryptor would then obtain the string `I use Firn Protocol to gain privacy on Ethereum.`. - -## Security Considerations - -Our proposal uses heavily standardized algorithms and follows all best practices. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5630.md diff --git a/EIPS/eip-5633.md b/EIPS/eip-5633.md index 99576c2e540d3e..55e9b317345fc3 100644 --- a/EIPS/eip-5633.md +++ b/EIPS/eip-5633.md @@ -1,78 +1 @@ ---- -eip: 5633 -title: Composable Soulbound NFT, EIP-1155 Extension -description: Add composable soulbound property to EIP-1155 tokens -author: HonorLabs (@honorworldio) -discussions-to: https://ethereum-magicians.org/t/composable-soulbound-nft-eip-1155-extension/10773 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-09 -requires: 165, 1155 ---- - -## Abstract - -This standard is an extension of [EIP-1155](./eip-1155.md). It proposes a smart contract interface that can represent any number of soulbound and non-soulbound NFT types. Soulbound is the property of a token that prevents it from being transferred between accounts. This standard allows for each token ID to have its own soulbound property. - -## Motivation - -The soulbound NFTs similar to World of Warcraft’s soulbound items are attracting more and more attention in the Ethereum community. In a real world game like World of Warcraft, there are thousands of items, and each item has its own soulbound property. For example, the amulate Necklace of Calisea is of soulbound property, but another low level amulate is not. This proposal provides a standard way to represent soulbound NFTs that can coexist with non-soulbound ones. It is easy to design a composable NFTs for an entire collection in a single contract. - -This standard outline a interface to EIP-1155 that allows wallet implementers and developers to check for soulbound property of token ID using [EIP-165](./eip-165.md). the soulbound property can be checked in advance, and the transfer function can be called only when the token is not soulbound. - -## Specification -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -A token type with a `uint256 id` is soulbound if function `isSoulbound(uint256 id)` returning true. In this case, all EIP-1155 functions of the contract that transfer the token from one account to another MUST throw, except for mint and burn. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5633 { - /** - * @dev Emitted when a token type `id` is set or cancel to soulbound, according to `bounded`. - */ - event Soulbound(uint256 indexed id, bool bounded); - - /** - * @dev Returns true if a token type `id` is soulbound. - */ - function isSoulbound(uint256 id) external view returns (bool); -} -``` -Smart contracts implementing this standard MUST implement the EIP-165 supportsInterface function and MUST return the constant value true if 0x911ec470 is passed through the interfaceID argument. - -## Rationale - -If all tokens in a contract are soulbound by default, `isSoulbound(uint256 id)` should return true by default during implementation. - -## Backwards Compatibility - -This standard is fully EIP-1155 compatible. - -## Test Cases - -Test cases are included in [test.js](../assets/eip-5633/test/test.js). - -Run in terminal: - -```shell -cd ../assets/eip-5633 -npm install -npx hardhat test -``` - -Test contract are included in [`ERC5633Demo.sol`](../assets/eip-5633/contracts/ERC5633Demo.sol). - -## Reference Implementation - -See [`ERC5633.sol`](../assets/eip-5633/contracts/ERC5633.sol). - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5633.md diff --git a/EIPS/eip-5635.md b/EIPS/eip-5635.md index 8329711264534d..2ac067831e1af0 100644 --- a/EIPS/eip-5635.md +++ b/EIPS/eip-5635.md @@ -1,265 +1 @@ ---- -eip: 5635 -title: NFT Licensing Agreements -description: An oracle for retrieving NFT licensing agreements -author: Timi (@0xTimi), 0xTriple7 (@ysqi) -discussions-to: https://ethereum-magicians.org/t/eip-5635-discussion-nft-licensing-agreement-standard/10779 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-08-10 -requires: 165, 721, 1155, 2981 ---- - -## Abstract - -This EIP standardizes an NFT licensing oracle to store (register) and retrieve (discover) granted licensing agreements for non-fungible token (NFT) derivative works, which are also NFTs but are created using properties of some other underlying NFTs. - -In this standard, an NFT derivative work is referred to as a **dNFT**, while the original underlying NFT is referred to as an **oNFT**. - -The NFT owner, known as the `licensor`, may authorize another creator, known as the `licensee`, to create a derivative works (dNFTs), in exchange for an agreed payment, known as a `Royalty`. A licensing agreement outlines terms and conditions related to the deal between the licensor and licensee. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -In general, there are three important roles in this standard: - -- oNFT: An original underlying NFT. The holder of an oNFT is a licensor. An oNFT can be any NFT. -- dNFT: A derivative work based on one or more oNFTs. The holder of a dNFT is a licensee. -- Registry: A trusted smart contract able to verify whether a credential is signed or released by the holder of oNFT. - -Every **dNFT** contract must implement the `IERC5635NFT` and `IERC165` inferfaces. - -```solidity -pragma solidity ^0.6.0; -import "./IERC165.sol"; - -/// -/// @notice Interface of NFT derivatives (dNFT) for the NFT Licensing Standard -/// @dev The ERC-165 identifier for this interface is 0xd584841c. -interface IERC5635DNFT is IERC165 { - - /// ERC165 bytes to add to interface array - set in parent contract - /// implementing this standard - /// - /// bytes4(keccak256("IERC5635DNFT{}")) == 0xd584841c - /// bytes4 private constant _INTERFACE_ID_IERC5635DNFT = 0xd584841c; - /// _registerInterface(_INTERFACE_ID_IERC5635XDNFT); - - /// @notice Get the number of credentials. - /// @param _tokenId - ID of the dNFT asset queried - /// @return _number - the number of credentials - function numberOfCredentials( - uint256 _tokenId - ) external view returns ( - uint256 _number - ); - - /// @notice Called with the sale price to determine how much royalty is owed and to whom. - /// @param _tokenId - ID of the dNFT asset queried - /// @param _credentialId - ID of the licensing agreement credential, the max id is numberOfCredentials(_tokenId)-1 - /// @return _oNFT - the oNFT address where the licensing from - /// @return _tokenID - the oNFT ID where the licensing from - /// @return _registry - the address of registry which can verify this credential - function authorizedBy( - uint256 _tokenId, - uint256 _credentialId - ) external view returns ( - address _oNFT, - uint256 _tokenId, - address _registry - ); - -} - -interface IERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -Every **Registry** contract must implement the `IERC5635Registry` and `IERC165` inferfaces. - -```solidity -pragma solidity ^0.6.0; -import "./IERC165.sol"; - -/// -/// @dev Interface of NFT derivatives (dNFT) for the NFT Licensing Standard -/// Note: the ERC-165 identifier for this interface is 0xb5065e9f -interface IERC5635Registry is IERC165 { - - /// ERC165 bytes to add to interface array - set in parent contract - /// implementing this standard - /// - /// bytes4(keccak256("IERC5635Registry{}")) == 0xb5065e9f - /// bytes4 private constant _INTERFACE_ID_IERC5635Registry = 0xb5065e9f; - /// _registerInterface(_INTERFACE_ID_IERC5635Registry); - - // TODO: Is the syntax correct? - enum LicensingAgreementType { - NonExclusive, - Exclusive, - Sole - } - - - /// @notice - /// @param _dNFT - - /// @param _dNFT_Id - - /// @param _oNFT - - /// @param _oNFT_Id - - /// @return _licensed - - /// @return _tokenID - the oNFT ID where the licensing from - /// @return _registry - the address of registry which can verify this credential - function isLicensed( - address _dNFT, - uint256 _dNFT_Id, - address _oNFT, - uint256 _oNFT_Id - ) external view returns ( - bool _licensed - ); - - /// @return _licenseIdentifier - the identifier, e.g. `MIT` or `Apache`, similar to `SPDX-License-Identifier: MIT` in SPDX. - function licensingInfo( - address _dNFT, - uint256 _dNFT_Id, - address _oNFT, - uint256 _oNFT_Id - ) external view returns ( - bool _licensed, - address _licensor, - uint64 _timeOfSignature, - uint64 _expiryTime, - LicensingAgreementType _type, - string _licenseName, - string _licenseUri // - ); - - function royaltyRate( - address _dNFT, - uint256 _dNFT_Id, - address _oNFT, - uint256 _oNFT_Id - ) external view returns ( - address beneficiary, - uint256 rate // The decimals is 9, means to divide the rate by 1,000,000,000 - ); -} -``` - -The **Registry** contract MAY implement the `IERC5635Licensing` and `IERC165` inferfaces. - -```solidity -pragma solidity ^0.6.0; -import "./IERC165.sol"; - -/// -/// -interface IERC5635Licensing is IERC165, IERC5635Registry { - - event Licence(address indexed _oNFT, uint256 indexed _oNFT_Id, address indexed _dNFT, uint256 indexed _dNFT_Id, uint64 _expiryTime, LicensingAgreementType _type, string _licenseName, string _licenseUri); - - event Approval(address indexed _oNFT, address indexed _owner, address indexed _approved, uint256 indexed _tokenId); - - event ApprovalForAll(address indexed _oNFT, address indexed _owner, address indexed _operator, bool _approved); - - function licence(address indexed _oNFT, uint256 indexed _oNFT_Id, address indexed _dNFT, uint256 indexed _dNFT_Id, uint64 _expiryTime, LicensingAgreementType _type, string _licenseName, string _licenseUri) external payable; //TODO: mortgages or not? - - function approve(address indexed _oNFT, address _approved, uint256 _tokenId) external payable; //TODO: why payable? - - function setApprovalForAll(address indexed _oNFT, address _operator, bool _approved) external; - - function getApproved(address indexed _oNFT, uint256 _tokenId) external view returns (address); - - function isApprovedForAll(address indexed _oNFT, address _owner, address _operator) external view returns (bool); - -} -``` - -## Rationale - -Licensing credentials from a dNFT's contract can be retrieved with `authorizedBy`, which specifies the details of a licensing agreement, which may include the oNFT. Those credentials may be verified with a `registry` service. - -Anyone can retrieve licensing royalty information with `licensingRoyalty` via the registry. While it is not possible to enforce the rules set out in this EIP on-chain, just like [EIP-2981](./eip-2981.md), we encourages NFT marketplaces to follow this EIP. - -### Two stages: Licensing and Discovery - -Taking the moment when the dNFT is minted as the cut-off point, the stage before is called the **Licensing** stage, and the subsequent stage is called the **Discovery** stage. The interface `IERC5635Licensing` is for the **Licensing** stage, and the interfaces `IERC5635DNFT` and `IERC5635Registry` are for the **Discovery** stage. - -### Design decision: beneficiary of licensing agreement - -As soon as someone sells their NFT, the full licensed rights are passed along to the new owner without any encumbrances, so that the beneficiary should be the new owner. - -### Difference between CantBeEvil Licenses and Licensing Agreements. - -CantBeEvil licenses are creator-holder licenses which indicate what rights the NFTs' holder are granted from the creator. Meanwhile, licensing agreements is a contract between a licensor and licensee. So, CantBeEvil licenses cannot be used as a licensing agreement. - -### Design decision: Relationship between different approval levels - -The approved address can `license()` the licensing agreement to **dNFT** on behalf of the holder of an **oNFT**. We define two levels of approval like that: - -1. `approve` will lead to approval for one NFT related to an id. -2. `setApprovalForAll` will lead to approval of all NFTs owned by `msg.sender`. - -## Backwards Compatibility - -This standard is compatible with [EIP-721](./eip-721.md), [EIP-1155](./eip-1155.md), and [EIP-2981](./eip-2981.md). - -## Reference Implementation - -### Examples - -#### Deploying an [EIP-721](./eip-721.md) NFT and signaling support for dNFT - -```solidity -constructor (string memory name, string memory symbol, string memory baseURI) { - _name = name; - _symbol = symbol; - _setBaseURI(baseURI); - // register the supported interfaces to conform to ERC721 via ERC165 - _registerInterface(_INTERFACE_ID_ERC721); - _registerInterface(_INTERFACE_ID_ERC721_METADATA); - _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE); - // dNFT interface - _registerInterface(_INTERFACE_ID_IERC5635DNFT); -} -``` - -#### Checking if the NFT being sold on your marketplace is a dNFT - -```solidity -bytes4 private constant _INTERFACE_ID_IERC5635DNFT = 0xd584841c; - -function checkDNFT(address _contract) internal returns (bool) { - (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_IERC5635DNFT); - return success; -} -``` - -#### Checking if an address is a Registry - -```solidity -bytes4 private constant _INTERFACE_ID_IERC5635Registry = 0xb5065e9f; - -function checkLARegistry(address _contract) internal returns (bool) { - (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_IERC5635Registry); - return success; -} -``` - -## Security Considerations - -Needs discussion. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5635.md diff --git a/EIPS/eip-5639.md b/EIPS/eip-5639.md index 71892d0cb29091..1ea59384983285 100644 --- a/EIPS/eip-5639.md +++ b/EIPS/eip-5639.md @@ -1,296 +1 @@ ---- -eip: 5639 -title: Delegation Registry -description: Delegation of permissions for safer and more convenient signing operations. -author: foobar (@0xfoobar), Wilkins Chung (@wwhchung) , ryley-o (@ryley-o), Jake Rockland (@jakerockland), andy8052 (@andy8052) -discussions-to: https://ethereum-magicians.org/t/eip-5639-delegation-registry/10949 -status: Review -type: Standards Track -category: ERC -created: 2022-09-09 ---- - -## Abstract - -This EIP describes the details of the Delegation Registry, a proposed protocol and ABI definition that provides the ability to link one or more delegate wallets to a vault wallet in a manner which allows the linked delegate wallets to prove control and asset ownership of the vault wallet. - -## Motivation - -Proving ownership of an asset to a third party application in the Ethereum ecosystem is common. Users frequently sign payloads of data to authenticate themselves before gaining access to perform some operation. However, this method--akin to giving the third party root access to one's main wallet--is both insecure and inconvenient. - -***Examples:*** - - 1. In order for you to edit your profile on OpenSea, you must sign a message with your wallet. - 2. In order to access NFT gated content, you must sign a message with the wallet containing the NFT in order to prove ownership. - 3. In order to gain access to an event, you must sign a message with the wallet containing a required NFT in order to prove ownership. - 4. In order to claim an airdrop, you must interact with the smart contract with the qualifying wallet. - 5. In order to prove ownership of an NFT, you must sign a payload with the wallet that owns that NFT. - -In all the above examples, one interacts with the dApp or smart contract using the wallet itself, which may be - - - inconvenient (if it is controlled via a hardware wallet or a multi-sig) - - insecure (since the above operations are read-only, but you are signing/interacting via a wallet that has write access) - -Instead, one should be able to approve multiple wallets to authenticate on behalf of a given wallet. - -### Problems with existing methods and solutions - -Unfortunately, we've seen many cases where users have accidentally signed a malicious payload. The result is almost always a significant loss of assets associated with the delegate address. - -In addition to this, many users keep significant portions of their assets in 'cold storage'. With the increased security from 'cold storage' solutions, we usually see decreased accessibility because users naturally increase the barriers required to access these wallets. - -### Proposal: Use of a Delegation Registry - -This proposal aims to provide a mechanism which allows a vault wallet to grant wallet, contract or token level permissions to a delegate wallet. This would achieve a safer and more convenient way to sign and authenticate, and provide 'read only' access to a vault wallet via one or more secondary wallets. - -From there, the benefits are twofold. This EIP gives users increased security via outsourcing potentially malicious signing operations to wallets that are more accessible (hot wallets), while being able to maintain the intended security assumptions of wallets that are not frequently used for signing operations. - -#### Improving dApp Interaction Security - -Many dApps requires one to prove control of a wallet to gain access. At the moment, this means that you must interact with the dApp using the wallet itself. This is a security issue, as malicious dApps or phishing sites can lead to the assets of the wallet being compromised by having them sign malicious payloads. - -However, this risk would be mitigated if one were to use a secondary wallet for these interactions. Malicious interactions would be isolated to the assets held in the secondary wallet, which can be set up to contain little to nothing of value. - -#### Improving Multiple Device Access Security - -In order for a non-hardware wallet to be used on multiple devices, you must import the seed phrase to each device. Each time a seed phrase is entered on a new device, the risk of the wallet being compromised increases as you are increasing the surface area of devices that have knowledge of the seed phrase. - -Instead, each device can have its own unique wallet that is an authorized secondary wallet of the main wallet. If a device specific wallet was ever compromised or lost, you could simply remove the authorization to authenticate. - -Further, wallet authentication can be chained so that a secondary wallet could itself authorize one or many tertiary wallets, which then have signing rights for both the secondary address as well as the root main address. This, can allow teams to each have their own signer while the main wallet can easily invalidate an entire tree, just by revoking rights from the root stem. - -#### Improving Convenience - -Many invididuals use hardware wallets for maximum security. However, this is often inconvenient, since many do not want to carry their hardware wallet with them at all times. - -Instead, if you approve a non-hardware wallet for authentication activities (such as a mobile device), you would be able to use most dApps without the need to have your hardware wallet on hand. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Let: - - - `vault` represent the vault address we are trying to authenticate or prove asset ownership for. - - `delegate` represent the address we want to use for signing in lieu of `vault`. - -**A Delegation Registry must implement IDelegationRegistry** - -```solidity -/** - * @title An immutable registry contract to be deployed as a standalone primitive - * @dev New project launches can read previous cold wallet -> hot wallet delegations - * from here and integrate those permissions into their flow - */ -interface IDelegationRegistry { - /// @notice Delegation type - enum DelegationType { - NONE, - ALL, - CONTRACT, - TOKEN - } - - /// @notice Info about a single delegation, used for onchain enumeration - struct DelegationInfo { - DelegationType type_; - address vault; - address delegate; - address contract_; - uint256 tokenId; - } - - /// @notice Info about a single contract-level delegation - struct ContractDelegation { - address contract_; - address delegate; - } - - /// @notice Info about a single token-level delegation - struct TokenDelegation { - address contract_; - uint256 tokenId; - address delegate; - } - - /// @notice Emitted when a user delegates their entire wallet - event DelegateForAll(address vault, address delegate, bool value); - - /// @notice Emitted when a user delegates a specific contract - event DelegateForContract(address vault, address delegate, address contract_, bool value); - - /// @notice Emitted when a user delegates a specific token - event DelegateForToken(address vault, address delegate, address contract_, uint256 tokenId, bool value); - - /// @notice Emitted when a user revokes all delegations - event RevokeAllDelegates(address vault); - - /// @notice Emitted when a user revoes all delegations for a given delegate - event RevokeDelegate(address vault, address delegate); - - /** - * ----------- WRITE ----------- - */ - - /** - * @notice Allow the delegate to act on your behalf for all contracts - * @param delegate The hotwallet to act on your behalf - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForAll(address delegate, bool value) external; - - /** - * @notice Allow the delegate to act on your behalf for a specific contract - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForContract(address delegate, address contract_, bool value) external; - - /** - * @notice Allow the delegate to act on your behalf for a specific token - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param tokenId The token id for the token you're delegating - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external; - - /** - * @notice Revoke all delegates - */ - function revokeAllDelegates() external; - - /** - * @notice Revoke a specific delegate for all their permissions - * @param delegate The hotwallet to revoke - */ - function revokeDelegate(address delegate) external; - - /** - * @notice Remove yourself as a delegate for a specific vault - * @param vault The vault which delegated to the msg.sender, and should be removed - */ - function revokeSelf(address vault) external; - - /** - * ----------- READ ----------- - */ - - /** - * @notice Returns all active delegations a given delegate is able to claim on behalf of - * @param delegate The delegate that you would like to retrieve delegations for - * @return info Array of DelegationInfo structs - */ - function getDelegationsByDelegate(address delegate) external view returns (DelegationInfo[] memory); - - /** - * @notice Returns an array of wallet-level delegates for a given vault - * @param vault The cold wallet who issued the delegation - * @return addresses Array of wallet-level delegates for a given vault - */ - function getDelegatesForAll(address vault) external view returns (address[] memory); - - /** - * @notice Returns an array of contract-level delegates for a given vault and contract - * @param vault The cold wallet who issued the delegation - * @param contract_ The address for the contract you're delegating - * @return addresses Array of contract-level delegates for a given vault and contract - */ - function getDelegatesForContract(address vault, address contract_) external view returns (address[] memory); - - /** - * @notice Returns an array of contract-level delegates for a given vault's token - * @param vault The cold wallet who issued the delegation - * @param contract_ The address for the contract holding the token - * @param tokenId The token id for the token you're delegating - * @return addresses Array of contract-level delegates for a given vault's token - */ - function getDelegatesForToken(address vault, address contract_, uint256 tokenId) - external - view - returns (address[] memory); - - /** - * @notice Returns all contract-level delegations for a given vault - * @param vault The cold wallet who issued the delegations - * @return delegations Array of ContractDelegation structs - */ - function getContractLevelDelegations(address vault) - external - view - returns (ContractDelegation[] memory delegations); - - /** - * @notice Returns all token-level delegations for a given vault - * @param vault The cold wallet who issued the delegations - * @return delegations Array of TokenDelegation structs - */ - function getTokenLevelDelegations(address vault) external view returns (TokenDelegation[] memory delegations); - - /** - * @notice Returns true if the address is delegated to act on the entire vault - * @param delegate The hotwallet to act on your behalf - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForAll(address delegate, address vault) external view returns (bool); - - /** - * @notice Returns true if the address is delegated to act on your behalf for a token contract or an entire vault - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForContract(address delegate, address vault, address contract_) - external - view - returns (bool); - - /** - * @notice Returns true if the address is delegated to act on your behalf for a specific token, the token's contract or an entire vault - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param tokenId The token id for the token you're delegating - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId) - external - view - returns (bool); -} -``` - -### Checking Delegation - -A dApp or smart contract would check whether or not a delegate is authenticated for a vault by checking the return value of checkDelegateForAll. - -A dApp or smart contract would check whether or not a delegate can authenticated for a contract associated with a by checking the return value of checkDelegateForContract. - -A dApp or smart contract would check whether or not a delegate can authenticated for a specific token owned by a vault by checking the return value of checkDelegateForToken. - -A delegate can act on a token if they have a token level delegation, contract level delegation (for that token's contract) or vault level delegation. - -A delegate can act on a contract if they have contract level delegation or vault level delegation. - -For the purposes of saving gas, it is expected if delegation checks are performed at a smart contract level, the dApp would provide a hint to the smart contract which level of delegation the delegate has so that the smart contract can verify with the Delegation Registry using the most gas efficient check method. - -## Rationale - -### Allowing for vault, contract or token level delegation - -In order to support a wide range of delegation use cases, the proposed specification allows a vault to delegate all assets it controls, assets of a specific contract, or a specific token. This ensures that a vault has fine grained control over the security of their assets, and allows for emergent behavior around granting third party wallets limited access only to assets relevant to them. - -### On-chain enumeration - -In order to support ease of integration and adoption, this specification has chosen to include on-chain enumeration of delegations and incur the additional gas cost associated with supporting enumeration. On-chain enumeration allows for dApp frontends to identify the delegations that any connected wallet has access to, and can provide UI selectors. - -Without on-chain enumeration, a dApp would require the user to manually input the vault, or would need a way to index all delegate events. - - -## Security Considerations - -The core purpose of this EIP is to enhance security and promote a safer way to authenticate wallet control and asset ownership when the main wallet is not needed and assets held by the main wallet do not need to be moved. Consider it a way to do 'read only' authentication. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5639.md diff --git a/EIPS/eip-5643.md b/EIPS/eip-5643.md index 8f83bdc40f5882..75d7aca255131d 100644 --- a/EIPS/eip-5643.md +++ b/EIPS/eip-5643.md @@ -1,225 +1 @@ ---- -eip: 5643 -title: Subscription NFTs -description: Add subscription-based functionality to EIP-721 tokens -author: cygaar (@cygaar) -discussions-to: https://ethereum-magicians.org/t/eip-5643-subscription-nfts/10802 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-10 -requires: 721 ---- - -## Abstract - -This standard is an extension of [EIP-721](./eip-721.md). It proposes an additional interface for NFTs to be used as recurring, expirable subscriptions. The interface includes functions to renew and cancel the subscription. - -## Motivation - -NFTs are commonly used as accounts on decentralized apps or membership passes to communities, events, and more. However, it is currently rare to see NFTs like these that have a finite expiration date. The "permanence" of the blockchain often leads to memberships that have no expiration dates and thus no required recurring payments. However, for many real-world applications, a paid subscription is needed to keep an account or membership valid. - -The most prevalent on-chain application that makes use of the renewable subscription model is the Ethereum Name Service (ENS), which utilizes a similar interface to the one proposed below. Each domain can be renewed for a certain period of time, and expires if payments are no longer made. A common interface will make it easier for future projects to develop subscription-based NFTs. In the current Web2 world, it's hard for a user to see or manage all of their subscriptions in one place. With a common standard for subscriptions, it will be easy for a single application to determine the number of subscriptions a user has, see when they expire, and renew/cancel them as requested. - -Additionally, as the prevalence of secondary royalties from NFT trading disappears, creators will need new models for generating recurring income. For NFTs that act as membership or access passes, pivoting to a subscription-based model is one way to provide income and also force issuers to keep providing value. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -interface IERC5643 { - /// @notice Emitted when a subscription expiration changes - /// @dev When a subscription is canceled, the expiration value should also be 0. - event SubscriptionUpdate(uint256 indexed tokenId, uint64 expiration); - - /// @notice Renews the subscription to an NFT - /// Throws if `tokenId` is not a valid NFT - /// @param tokenId The NFT to renew the subscription for - /// @param duration The number of seconds to extend a subscription for - function renewSubscription(uint256 tokenId, uint64 duration) external payable; - - /// @notice Cancels the subscription of an NFT - /// @dev Throws if `tokenId` is not a valid NFT - /// @param tokenId The NFT to cancel the subscription for - function cancelSubscription(uint256 tokenId) external payable; - - /// @notice Gets the expiration date of a subscription - /// @dev Throws if `tokenId` is not a valid NFT - /// @param tokenId The NFT to get the expiration date of - /// @return The expiration date of the subscription - function expiresAt(uint256 tokenId) external view returns(uint64); - - /// @notice Determines whether a subscription can be renewed - /// @dev Throws if `tokenId` is not a valid NFT - /// @param tokenId The NFT to get the expiration date of - /// @return The renewability of a the subscription - function isRenewable(uint256 tokenId) external view returns(bool); -} -``` - -The `expiresAt(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `isRenewable(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `renewSubscription(uint256 tokenId, uint64 duration)` function MAY be implemented as `external` or `public`. - -The `cancelSubscription(uint256 tokenId)` function MAY be implemented as `external` or `public`. - -The `SubscriptionUpdate` event MUST be emitted whenever the expiration date of a subscription is changed. - -The `supportsInterface` method MUST return `true` when called with `0x8c65f84d`. - -## Rationale - -This standard aims to make on-chain subscriptions as simple as possible by adding the minimal required functions and events for implementing on-chain subscriptions. It is important to note that in this interface, the NFT itself represents ownership of a subscription, there is no facilitation of any other fungible or non-fungible tokens. - -### Subscription Management - -Subscriptions represent agreements to make advanced payments in order to receive or participate in something. In order to facilitate these agreements, a user must be able to renew or cancel their subscriptions hence the `renewSubscription` and `cancelSubscription` functions. It also important to know when a subscription expires - users will need this information to know when to renew, and applications need this information to determine the validity of a subscription NFT. The `expiresAt` function provides this functionality. Finally, it is possible that a subscription may not be renewed once expired. The `isRenewable` function gives users and applications that information. - -### Easy Integration - -Because this standard is fully EIP-721 compliant, existing protocols will be able to facilitate the transfer of subscription NFTs out of the box. With only a few functions to add, protocols will be able to fully manage a subscription's expiration, determine whether a subscription is expired, and see whether it can be renewed. - -## Backwards Compatibility - -This standard can be fully EIP-721 compatible by adding an extension function set. - -The new functions introduced in this standard add minimal overhead to the existing EIP-721 interface, which should make adoption straightforward and quick for developers. - -## Test Cases - -The following tests require Foundry. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../src/ERC5643.sol"; - -contract ERC5643Mock is ERC5643 { - constructor(string memory name_, string memory symbol_) ERC5643(name_, symbol_) {} - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} - -contract ERC5643Test is Test { - event SubscriptionUpdate(uint256 indexed tokenId, uint64 expiration); - - address user1; - uint256 tokenId; - ERC5643Mock erc5643; - - function setUp() public { - tokenId = 1; - user1 = address(0x1); - - erc5643 = new ERC5643Mock("erc5369", "ERC5643"); - erc5643.mint(user1, tokenId); - } - - function testRenewalValid() public { - vm.warp(1000); - vm.prank(user1); - vm.expectEmit(true, true, false, true); - emit SubscriptionUpdate(tokenId, 3000); - erc5643.renewSubscription(tokenId, 2000); - } - - function testRenewalNotOwner() public { - vm.expectRevert("Caller is not owner nor approved"); - erc5643.renewSubscription(tokenId, 2000); - } - - function testCancelValid() public { - vm.prank(user1); - vm.expectEmit(true, true, false, true); - emit SubscriptionUpdate(tokenId, 0); - erc5643.cancelSubscription(tokenId); - } - - function testCancelNotOwner() public { - vm.expectRevert("Caller is not owner nor approved"); - erc5643.cancelSubscription(tokenId); - } - - function testExpiresAt() public { - vm.warp(1000); - - assertEq(erc5643.expiresAt(tokenId), 0); - vm.startPrank(user1); - erc5643.renewSubscription(tokenId, 2000); - assertEq(erc5643.expiresAt(tokenId), 3000); - - erc5643.cancelSubscription(tokenId); - assertEq(erc5643.expiresAt(tokenId), 0); - } -} -``` - -## Reference Implementation - -Implementation: `ERC5643.sol` - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.13; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5643.sol"; - -contract ERC5643 is ERC721, IERC5643 { - mapping(uint256 => uint64) private _expirations; - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - function renewSubscription(uint256 tokenId, uint64 duration) external payable { - require(_isApprovedOrOwner(msg.sender, tokenId), "Caller is not owner nor approved"); - - uint64 currentExpiration = _expirations[tokenId]; - uint64 newExpiration; - if (currentExpiration == 0) { - newExpiration = uint64(block.timestamp) + duration; - } else { - if (!_isRenewable(tokenId)) { - revert SubscriptionNotRenewable(); - } - newExpiration = currentExpiration + duration; - } - - _expirations[tokenId] = newExpiration; - - emit SubscriptionUpdate(tokenId, newExpiration); - } - - function cancelSubscription(uint256 tokenId) external payable { - require(_isApprovedOrOwner(msg.sender, tokenId), "Caller is not owner nor approved"); - delete _expirations[tokenId]; - emit SubscriptionUpdate(tokenId, 0); - } - - function expiresAt(uint256 tokenId) external view returns(uint64) { - return _expirations[tokenId]; - } - - function isRenewable(uint256 tokenId) external pure returns(bool) { - return true; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC5643).interfaceId || super.supportsInterface(interfaceId); - } -} -``` - -## Security Considerations - -This EIP standard does not affect ownership of an NFT and thus can be considered secure. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5643.md diff --git a/EIPS/eip-5646.md b/EIPS/eip-5646.md index db381c1f58fac1..9dd2acf7914541 100644 --- a/EIPS/eip-5646.md +++ b/EIPS/eip-5646.md @@ -1,119 +1 @@ ---- -eip: 5646 -title: Token State Fingerprint -description: Unambiguous token state identifier -author: Naim Ashhab (@ashhanai) -discussions-to: https://ethereum-magicians.org/t/eip-5646-discussion-token-state-fingerprint/10808 -status: Final -type: Standards Track -category: ERC -created: 2022-09-11 -requires: 165 ---- - -## Abstract - -This specification defines the minimum interface required to unambiguously identify the state of a mutable token without knowledge of implementation details. - -## Motivation - -Currently, protocols need to know about tokens' state properties to create the unambiguous identifier. Unfortunately, this leads to an obvious bottleneck in which protocols need to support every new token specifically. - -![](../assets/eip-5646/support-per-abi.png) - -## Specification - -The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in RFC 2119. - -```solidity -pragma solidity ^0.8.0; - -interface ERC5646 is ERC165 { - - /// @notice Function to return current token state fingerprint. - /// @param tokenId Id of a token state in question. - /// @return Current token state fingerprint. - function getStateFingerprint(uint256 tokenId) external view returns (bytes32); - -} -``` - -- `getStateFingerprint` MUST return a different value when the token state changes. -- `getStateFingerprint` MUST NOT return a different value when the token state remains the same. -- `getStateFingerprint` MUST include all state properties that might change during the token lifecycle (are not immutable). -- `getStateFingerprint` MAY include computed values, such as values based on a current timestamp (e.g., expiration, maturity). -- `getStateFingerprint` MAY include token metadata URI. -- `supportsInterface(0xf5112315)` MUST return `true`. - -## Rationale - -Protocols can use state fingerprints as a part of a token identifier and support mutable tokens without knowing any state implementation details. - -![](../assets/eip-5646/support-per-eip.png) - -State fingerprints don't have to factor in state properties that are immutable, because they can be safely identified by a token id. - -This standard is not for use cases where token state property knowledge is required, as these cases cannot escape the bottleneck problem described earlier. - -## Backwards Compatibility - -This EIP is not introducing any backward incompatibilities. - -## Reference Implementation - -```solidity -pragma solidity ^0.8.0; - -/// @title Example of a mutable token implementing state fingerprint. -contract LPToken is ERC721, ERC5646 { - - /// @dev Stored token states (token id => state). - mapping (uint256 => State) internal states; - - struct State { - address asset1; - address asset2; - uint256 amount1; - uint256 amount2; - uint256 fee; // Immutable - address operator; // Immutable - uint256 expiration; // Parameter dependent on a block.timestamp - } - - - /// @dev State fingerprint getter. - /// @param tokenId Id of a token state in question. - /// @return Current token state fingerprint. - function getStateFingerprint(uint256 tokenId) override public view returns (bytes32) { - State storage state = states[tokenId]; - - return keccak256( - abi.encode( - state.asset1, - state.asset2, - state.amount1, - state.amount2, - // state.fee don't need to be part of the fingerprint computation as it is immutable - // state.operator don't need to be part of the fingerprint computation as it is immutable - block.timestamp >= state.expiration - ) - ); - } - - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return super.supportsInterface(interfaceId) || - interfaceId == type(ERC5646).interfaceId; - } - -} -``` - -## Security Considerations - -Token state fingerprints from two different contracts may collide. Because of that, they should be compared only in the context of one token contract. - -If the `getStateFingerprint` implementation does not include all parameters that could change the token state, a token owner would be able to change the token state without changing the token fingerprint. It could break the trustless assumptions of several protocols, which create, e.g., buy offers for tokens. The token owner would be able to change the state of the token before accepting an offer. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5646.md diff --git a/EIPS/eip-5679.md b/EIPS/eip-5679.md index 988127a5bb524c..ebb3aca9adb263 100644 --- a/EIPS/eip-5679.md +++ b/EIPS/eip-5679.md @@ -1,124 +1 @@ ---- -eip: 5679 -title: Token Minting and Burning -description: An extension for minting and burning EIP-20, EIP-721, and EIP-1155 tokens -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-5679-mint-and-burn-tokens/10913 -status: Final -type: Standards Track -category: ERC -created: 2022-09-17 -requires: 20, 165, 721, 1155 ---- - -## Abstract - -This EIP introduces a consistent way to extend token standards for minting and burning. - -## Motivation - -Minting and Burning are typical actions for creating and destroying tokens. -By establishing a consistent way to mint and burn a token, we complete the basic lifecycle. - -Some implementations of [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md) -have been able to use `transfer` methods or the-like -to mint and burn tokens. However, minting and burning change token supply. The access controls -of minting and burning also usually follow different rules than transfer. -Therefore, creating separate methods for burning and minting simplifies implementations -and reduces security error. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -1. Any contract complying with [EIP-20](./eip-20.md) when extended with this EIP, -**MUST** implement the following interface: - -```solidity -// The EIP-165 identifier of this interface is 0xd0017968 -interface IERC5679Ext20 { - function mint(address _to, uint256 _amount, bytes calldata _data) external; - function burn(address _from, uint256 _amount, bytes calldata _data) external; -} -``` - -2. Any contract complying with [EIP-721](./eip-721.md) when extended with this EIP, -**MUST** implement the following interface: - -```solidity -// The EIP-165 identifier of this interface is 0xcce39764 -interface IERC5679Ext721 { - function safeMint(address _to, uint256 _id, bytes calldata _data) external; - function burn(address _from, uint256 _id, bytes calldata _data) external; -} -``` - -3. Any contract complying with [EIP-1155](./eip-1155.md) when extended with this EIP, -**MUST** implement the following interface: - -```solidity -// The EIP-165 identifier of this interface is 0xf4cedd5a -interface IERC5679Ext1155 { - function safeMint(address _to, uint256 _id, uint256 _amount, bytes calldata _data) external; - function safeMintBatch(address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data) external; - function burn(address _from, uint256 _id, uint256 _amount, bytes[] calldata _data) external; - function burnBatch(address _from, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata _data) external; -} -``` - -4. When the token is being minted, the transfer events **MUST** be emitted as if -the token in the `_amount` for EIP-20 and EIP-1155 and token id being `_id` for EIP-721 and EIP-1155 -were transferred from address `0x0` to the recipient address identified by `_to`. -The total supply **MUST** increase accordingly. - -5. When the token is being burned, the transfer events **MUST** be emitted as if -the token in the `_amount` for EIP-20 and EIP-1155 and token id being `_id` for EIP-721 and EIP-1155 -were transferred from the recipient address identified by `_to` to the address of `0x0`. -The total supply **MUST** decrease accordingly. - -6. `safeMint` MUST implement the same receiver restrictions as `safeTransferFrom` as defined in -[EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md). - -7. It's RECOMMENDED for the client to implement [EIP-165](./eip-165.md) identifiers as specified above. - -## Rationale - -1. It's possible that the interface be consolidated to the same as EIP-1155 which is always bearing `_amount` field, -regardless of whether it's a EIP-20, EIP-721 or EIP-1155. But we choose that each ERC token should have their own -standard way of representing the amount of token to follow the same way of `_id` and `_amount` in their original -token standard. - -2. We have chosen to identify the interface with [EIP-165](./eip-165.md) identifiers each individually, -instead of having a single identifier because the signatures of interface are different. - -3. We have chosen NOT to create new events but to require the usage of existing transfer event as required by EIP-20 -EIP-721 and EIP-1155 for maximum compatibility. - -4. We have chosen to add `safeMintBatch` and `burnBatch` methods for EIP-1155 but not for EIP-721 to follow the -convention of EIP-721 and EIP-1155 respectively. - -5. We have not add extension for [EIP-777](./eip-777.md) because it already handles Minting and Burning. - -## Backwards Compatibility - -This EIP is designed to be compatible for EIP-20, EIP-721 and EIP-1155 respectively. - -## Security Considerations - -This EIP depends on the security soundness of the underlying book keeping behavior of the token implementation. -In particular, a token contract should carefully design the access control for which role is granted permission -to mint a new token. Failing to safe guard such behavior can cause fraudulent issuance and an elevation of total supply. - -The burning should also carefully design the access control. Typically only the following two roles are entitled to burn a token: - -- Role 1. The current token holder -- Role 2. An role with special privilege. - -Either Role 1 OR Role 2 or a consensus between the two are entitled to conduct the burning action. -However as author of this EIP we do recognize there are potentially other use case where a third type of role shall be entitled -to burning. We keep this EIP less opinionated in such restriction but implementors should be cautious about designing -the restriction. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5679.md diff --git a/EIPS/eip-5700.md b/EIPS/eip-5700.md index 425c18a3bf8747..53eff466e283f4 100644 --- a/EIPS/eip-5700.md +++ b/EIPS/eip-5700.md @@ -1,355 +1 @@ ---- -eip: 5700 -title: Bindable Token Interface -description: Interface for binding fungible and non-fungible tokens to assets. -author: Leeren (@leeren) -discussions-to: https://ethereum-magicians.org/t/eip-5700-bindable-token-standard/11077 -status: Draft -type: Standards Track -category: ERC -created: 2022-09-22 -requires: 165, 721, 1155 ---- - -## Abstract - -This standard defines an interface for [ERC-721](./eip-721.md) or [ERC-1155](./eip-155.md) tokens, known as "bindables", to "bind" to [ERC-721](./eip-721.md) NFTs. - -When bindable tokens "bind" to an NFT, even though their ownership is transferred to the NFT, the NFT owner may "unbind" the tokens and claim their ownership. This enables bindable tokens to transfer with their bound NFTs without extra cost, offering a more effective way to create and transfer N:1 token-to-NFT bundles. Until an NFT owner decides to unbind them, bound tokens stay locked and resume their base token functionalities after unbinding. - -This standard supports various use-cases such as: - -- NFT-bundled physical assets like microchipped streetwear, digitized car collections, and digitally twinned real estate. -- NFT-bundled digital assets such as accessorizable virtual wardrobes, composable music tracks, and customizable metaverse land. - -## Motivation - -A standard interface for NFT binding offers a seamless and efficient way to bundle and transfer tokens with NFTs, ensuring compatibility with wallets, marketplaces, and other NFT applications. It eliminates the need for rigid, implementation-specific strategies for token ownership. - -In contrast with other standards that deal with token ownership at the account level, this standard aims to address token ownership at the NFT level. Its objective is to build a universal interface for token bundling, compatible with existing [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) standards. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### ERC-721 Bindable - -**Smart contracts implementing the ERC-721 bindable standard MUST implement the `IERC721Bindable` interface.** - -**Implementers of the `IER721Bindable` interface MUST return `true` if `0x82a34a7d` is passed as the identifier to the `supportsInterface` function.** - -```solidity -/// @title ERC-721 Bindable Token Standard -/// @dev See https://eips.ethereum.org/ERCS/eip-5700 -/// Note: the ERC-165 identifier for this interface is 0x82a34a7d. -interface IERC721Bindable /* is IERC721 */ { - - /// @notice This event emits when an unbound token is bound to an NFT. - /// @param operator The address approved to perform the binding. - /// @param from The address of the unbound token owner. - /// @param bindAddress The contract address of the NFT being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenId The identifier of binding token. - event Bind( - address indexed operator, - address indexed from, - address indexed bindAddress, - uint256 bindId, - uint256 tokenId - ); - - /// @notice This event emits when an NFT-bound token is unbound. - /// @param operator The address approved to perform the unbinding. - /// @param from The owner of the NFT the token is bound to. - /// @param to The address of the new unbound token owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token. - event Unbind( - address indexed operator, - address indexed from, - address to, - address indexed bindAddress, - uint256 bindId, - uint256 tokenId - ); - - /// @notice Binds token `tokenId` to NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is the current owner, - /// an authorized operator, or the approved address for the token. It also - /// MUST throw if the token is already bound or if `from` is not the token - /// owner. Finally, it MUST throw if the NFT contract does not support the - /// ERC-721 interface or if the NFT being bound to does not exist. Before - /// binding, token ownership MUST be transferred to the contract address of - /// the NFT. On bind completion, the function MUST emit `Transfer` & `Bind` - /// events to reflect the implicit token transfer and subsequent bind. - /// @param from The address of the unbound token owner. - /// @param bindAddress The contract address of the NFT being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenId The identifier of the binding token. - function bind( - address from, - address bindAddress, - uint256 bindId, - uint256 tokenId - ) external; - - /// @notice Unbinds token `tokenId` from NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is the current owner, - /// an authorized operator, or the approved address for the NFT the token - /// is bound to. It also MUST throw if the token is unbound, if `from` is - /// not the owner of the bound NFT, or if `to` is the zero address. After - /// unbinding, token ownership MUST be transferred to `to`, during which - /// the function MUST check if `to` is a valid contract (code size > 0), - /// and if so, call `onERC721Received`, throwing if the wrong identifier is - /// returned. On unbind completion, the function MUST emit `Unbind` & - /// `Transfer` events to reflect the unbind and subsequent transfer. - /// @param from The address of the owner of the NFT the token is bound to. - /// @param to The address of the unbound token new owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token. - function unbind( - address from, - address to, - address bindAddress, - uint256 bindId, - uint256 tokenId - ) external; - - /// @notice Gets the NFT address and identifier token `tokenId` is bound to. - /// @dev When the token is unbound, this function MUST return the zero - /// address for the address portion to indicate no binding exists. - /// @param tokenId The identifier of the token being queried. - /// @return The token-bound NFT contract address and numerical identifier. - function binderOf(uint256 tokenId) external view returns (address, uint256); - - /// @notice Gets total tokens bound to NFT `bindId` at address `bindAddress`. - /// @param bindAddress The contract address of the NFT being queried. - /// @param bindId The identifier of the NFT being queried. - /// @return The total number of tokens bound to the queried NFT. - function boundBalanceOf(address bindAddress, uint256 bindId) external view returns (uint256); - -``` - -### ERC-1155 Bindable - -**Smart contracts implementing the ERC-1155 Bindable standard MUST implement the `IERC1155Bindable` interface.** - -**Implementers of the `IER1155Bindable` interface MUST return `true` if `0xd0d55c6` is passed as the identifier to the `supportsInterface` function.** - -```solidity -/// @title ERC-1155 Bindable Token Standard -/// @dev See https://eips.ethereum.org/ERCS/eip-5700 -/// Note: the ERC-165 identifier for this interface is 0xd0d555c6. -interface IERC1155Bindable /* is IERC1155 */ { - - /// @notice This event emits when token(s) are bound to an NFT. - /// @param operator The address approved to perform the binding. - /// @param from The owner address of the unbound tokens. - /// @param bindAddress The contract address of the NFT being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenId The identifier of the binding token type. - /// @param amount The number of tokens binding to the NFT. - event Bind( - address indexed operator, - address indexed from, - address indexed bindAddress, - uint256 bindId, - uint256 tokenId, - uint256 amount - ); - - /// @notice This event emits when token(s) of different types are bound to an NFT. - /// @param operator The address approved to perform the batch binding. - /// @param from The owner address of the unbound tokens. - /// @param bindAddress The contract address of the NFTs being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenIds The identifiers of the binding token types. - /// @param amounts The number of tokens per type binding to the NFTs. - event BindBatch( - address indexed operator, - address indexed from, - address indexed bindAddress, - uint256 bindId, - uint256[] tokenIds, - uint256[] amounts - ); - - /// @notice This event emits when token(s) are unbound from an NFT. - /// @param operator The address approved to perform the unbinding. - /// @param from The owner address of the NFT the tokens are bound to. - /// @param to The address of the unbound tokens' new owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token type. - /// @param amount The number of tokens unbinding from the NFT. - event Unbind( - address indexed operator, - address indexed from, - address to, - address indexed bindAddress, - uint256 bindId, - uint256 tokenId, - uint256 amount - ); - - /// @notice This event emits when token(s) of different types are unbound from an NFT. - /// @param operator The address approved to perform the batch binding. - /// @param from The owner address of the unbound tokens. - /// @param to The address of the unbound tokens' new owner. - /// @param bindAddress The contract address of the NFTs being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenIds The identifiers of the unbinding token types. - /// @param amounts The number of tokens per type unbinding from the NFTs. - event UnbindBatch( - address indexed operator, - address indexed from, - address to, - address indexed bindAddress, - uint256 bindId, - uint256[] tokenIds, - uint256[] amounts - ); - - /// @notice Binds `amount` tokens of `tokenId` to NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. It also MUST throw if the `from` owns fewer than `amount` - /// tokens. Finally, it MUST throw if the NFT contract does not support the - /// ERC-721 interface or if the NFT being bound to does not exist. Before - /// binding, tokens MUST be transferred to the contract address of the NFT. - /// On bind completion, the function MUST emit `Transfer` & `Bind` events - /// to reflect the implicit token transfers and subsequent bind. - /// @param from The owner address of the unbound tokens. - /// @param bindAddress The contract address of the NFT being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenId The identifier of the binding token type. - /// @param amount The number of tokens binding to the NFT. - function bind( - address from, - address bindAddress, - uint256 bindId, - uint256 tokenId, - uint256 amount - ) external; - - /// @notice Binds `amounts` tokens of `tokenIds` to NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. It also MUST throw if the length of `amounts` is not the - /// same as `tokenIds`, or if any balances of `tokenIds` for `from` is less - /// than that of `amounts`. Finally, it MUST throw if the NFT contract does - /// not support the ERC-721 interface or if the bound NFT does not exist. - /// Before binding, tokens MUST be transferred to the contract address of - /// the NFT. On bind completion, the function MUST emit `TransferBatch` and - /// `BindBatch` events to reflect the batch token transfers and bind. - /// @param from The owner address of the unbound tokens. - /// @param bindAddress The contract address of the NFTs being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenIds The identifiers of the binding token types. - /// @param amounts The number of tokens per type binding to the NFTs. - function batchBind( - address from, - address bindAddress, - uint256 bindId, - uint256[] calldata tokenIds, - uint256[] calldata amounts - ) external; - - /// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. It also MUST throw if `from` is not the owner of the bound - /// NFT, if the NFT's token balance is fewer than `amount`, or if `to` is - /// the zero address. After unbinding, tokens MUST be transferred to `to`, - /// during which the function MUST check if `to` is a valid contract (code - /// size > 0), and if so, call `onERC1155Received`, throwing if the wrong \ - /// identifier is returned. On unbind completion, the function MUST emit - /// `Unbind` & `Transfer` events to reflect the unbind and transfers. - /// @param from The owner address of the NFT the tokens are bound to. - /// @param to The address of the unbound tokens' new owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token type. - /// @param amount The number of tokens unbinding from the NFT. - function unbind( - address from, - address to, - address bindAddress, - uint256 bindId, - uint256 tokenId, - uint256 amount - ) external; - - /// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. It also MUST throw if the length of `amounts` is not the - /// same as `tokenIds`, if any balances of `tokenIds` for the NFT is less - /// than that of `amounts`, or if `to` is the zero addresss. After - /// unbinding, tokens MUST be transferred to `to`, during which the - /// function MUST check if `to` is a valid contract (code size > 0), and if - /// so, call `onERC1155BatchReceived`, throwing if the wrong identifier is - /// returned. On unbind completion, the function MUST emit `UnbindBatch` & - /// `TransferBatch` events to reflect the batch unbind and transfers. - /// @param from The owner address of the unbound tokens. - /// @param to The address of the unbound tokens' new owner. - /// @param bindAddress The contract address of the NFTs being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenIds The identifiers of the unbinding token types. - /// @param amounts The number of tokens per type unbinding from the NFTs. - function batchUnbind( - address from, - address to, - address bindAddress, - uint256 bindId, - uint256[] calldata tokenIds, - uint256[] calldata amounts - ) external; - - /// @notice Gets the number of tokens of type `tokenId` bound to NFT `bindId` at address `bindAddress`. - /// @param bindAddress The contract address of the bound NFT. - /// @param bindId The identifier of the bound NFT. - /// @param tokenId The identifier of the token type bound to the NFT. - /// @return The number of tokens of type `tokenId` bound to the NFT. - function boundBalanceOf( - address bindAddress, - uint256 bindId, - uint256 tokenId - ) external view returns (uint256); - - /// @notice Gets the number of tokens of types `bindIds` bound to NFTs `bindIds` at address `bindAddress`. - /// @param bindAddress The contract address of the bound NFTs. - /// @param bindIds The identifiers of the bound NFTs. - /// @param tokenIds The identifiers of the token types bound to the NFTs. - /// @return balances The bound balances for each token type / NFT pair. - function boundBalanceOfBatch( - address bindAddress, - uint256[] calldata bindIds, - uint256[] calldata tokenIds - ) external view returns (uint256[] memory balances); - -} -``` - -## Rationale - -A standard for token binding unlocks a new layer of composability for allowing wallets, applications, and protocols to interact with, trade, and display bundled NFTs. One example use-case of this is at Dopamine, where streetwear garments may be bundled with digital assets such as music, avatars, or digital-twins of the garments, by representing these assets as bindable tokens and binding them to microchips represented as NFTs. - -### Binding Mechanism - -During binding, a bindable token's technical ownership is conferred to its bound NFT, while allowing the NFT owner to unbind at any time. A caveat of this lightweight design is that applications that have yet to adopt this standard will not show the bundled tokens as owned by the NFT owner. - -## Backwards Compatibility - -The bindable token interface is designed to be compatible with existing ERC-721 and ERC-1155 standards. - -## Reference Implementation - -- [ERC-721 Bindable](../assets/eip-5700/erc721/ERC721Bindable.sol). -- [ERC-1155 Bindable](../assets/eip-5700/erc1155/ERC1155Bindable.sol). - -## Security Considerations - -During binding, because ownership is conferred to the bound NFT contract, implementations should take caution in ensuring unbinding may only be performed by the designated NFT owner. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5700.md diff --git a/EIPS/eip-5719.md b/EIPS/eip-5719.md index 23ab6b79cb8e99..ae5564f7059518 100644 --- a/EIPS/eip-5719.md +++ b/EIPS/eip-5719.md @@ -1,94 +1 @@ ---- -eip: 5719 -title: Signature replacement interface -description: Non-interactive replacing of smart contract wallet signatures that became stale due to configuration changes. -author: Agustin Aguilar (@Agusx1211) -discussions-to: https://ethereum-magicians.org/t/erc-signature-replacing-for-smart-contract-wallets/11059 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-26 -requires: 1271 ---- - -## Abstract - -Smart contract wallet signed messages can become stale, meaning a signature that once was valid could become invalid at any point. - -Signatures MAY become stale for reasons like: - -* The internal set of signers changed -* The wallet makes signatures expirable -* The contract was updated to a new implementation - -The following standard allows smart contract wallets to expose a URI that clients can use to replace a stale signature with a valid one. - -## Motivation - -In contrast to EOA signatures, [EIP-1271](./eip-1271.md) signatures are not necessarily idempotent; they can become invalid at any point in time. This poses a challenge to protocols that rely on signatures remaining valid for extended periods of time. - -A signature MAY need to be mutated due to one of the following scenarios: - -1. The wallet removes a signer that contributed to signing the initial message. -2. The wallet uses a Merkle tree to store signers, adding a new signer. -3. The wallet uses a Merkle tree to store signatures, adding new signatures. -4. The wallet is updated to a new implementation, and the signature schema changes. - -Non-interactive signature replacement SHOULD be possible, since the wallet that originally signed the message MAY NOT be available when the signature needs to be validated. An example use-case is the settlement of a trade in an exchange that uses an off-chain order book. - -## Specification - -The wallet contract MUST implement the following function: - -```solidity -function getAlternativeSignature(bytes32 _digest) external view returns (string); -``` - -The returned string MUST be a URI pointing to a JSON object with the following schema: - -```json -{ - "title": "Signature alternative", - "type": "object", - "properties": { - "blockHash": { - "type": "string", - "description": "A block.hash on which the signature should be valid." - }, - "signature": { - "type": "string", - "description": "The alternative signature for the given digest." - } - } -} -``` - -### Client process for replacing a signature - -A client is an entity that holds a signature and intends to validate it, either for off-chain or on-chain use. To use the smart contract wallet signature, the client MUST perform the following actions: - -1) Try validating the signature using [EIP-1271](./eip-1271.md); if the signature is valid, then the signature can be used as-is. -2) If the signature is not valid, call `getAlternativeSignature(_digest)`, passing the `digest` corresponding to the old signature. -3) If the call fails, no URI is returned, or the content of the URI is not valid, then the signature MUST be considered invalid. -4) Try validating the new signature using [EIP-1271](./eip-1271.md); if the signature is valid, it can be used as a drop-in replacement of the original signature. -5) If the validation fails, repeat the process from step (2) (notice: if the URI returns the same signature, the signature MUST be considered invalid). - -Clients MUST implement a retry limit when fetching alternative signatures. This limit is up to the client to define. - -## Rationale - -A URI is chosen because it can accommodate centralized and decentralized solutions. For example, a server can implement live re-encoding for Merkle proofs, or an IPFS link could point to a directory with all the pre-computed signature mutations. - -The `getAlternativeSignature` method points to an off-chain source because it's expected that the smart contract wallet doesn't contain on-chain records for all signed digests, if that were the case then such contract wouldn't need to use this EIP since it could directly validate the `digest` on`isValidSignature` ignoring the stale signature. - -## Backwards Compatibility - -Existing wallets that do not implement the `getAlternativeSignature` method can still sign messages without any changes; if any signatures become invalidated, clients will drop them on step (3). - -## Security Considerations - -Some applications use signatures as secrets; these applications would risk leaking such secrets if the EIP exposes the signatures. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5719.md diff --git a/EIPS/eip-5725.md b/EIPS/eip-5725.md index 14f015bf636675..cc777ddf2eb107 100644 --- a/EIPS/eip-5725.md +++ b/EIPS/eip-5725.md @@ -1,281 +1 @@ ---- -eip: 5725 -title: Transferable Vesting NFT -description: An interface for transferable vesting NFTs which release underlying tokens over time. -author: Apeguru (@Apegurus), Marco De Vries , Mario , DeFiFoFum (@DeFiFoFum), Elliott Green (@elliott-green) -discussions-to: https://ethereum-magicians.org/t/eip-5725-transferable-vesting-nft/11099 -status: Final -type: Standards Track -category: ERC -created: 2022-09-08 -requires: 721 ---- - -## Abstract - -A **Non-Fungible Token** (NFT) standard used to vest tokens ([ERC-20](./eip-20.md) or otherwise) over a vesting release curve. - -The following standard allows for the implementation of a standard API for NFT based contracts that hold and represent the vested and locked properties of any underlying token ([ERC-20](./eip-20.md) or otherwise) that is emitted to the NFT holder. This standard is an extension of the [ERC-721](./eip-721.md) token that provides basic functionality for creating vesting NFTs, claiming the tokens and reading vesting curve properties. - -## Motivation - -Vesting contracts, including timelock contracts, lack a standard and unified interface, which results in diverse implementations of such contracts. Standardizing such contracts into a single interface would allow for the creation of an ecosystem of on- and off-chain tooling around these contracts. In addition, liquid vesting in the form of non-fungible assets can prove to be a huge improvement over traditional **Simple Agreement for Future Tokens** (SAFTs) or **Externally Owned Account** (EOA)-based vesting as it enables transferability and the ability to attach metadata similar to the existing functionality offered by with traditional NFTs. - -Such a standard will not only provide a much-needed [ERC-20](./eip-20.md) token lock standard, but will also enable the creation of secondary marketplaces tailored for semi-liquid SAFTs. - -This standard also allows for a variety of different vesting curves to be implement easily. - -These curves could represent: - -- linear vesting -- cliff vesting -- exponential vesting -- custom deterministic vesting - -### Use Cases - -1. A framework to release tokens over a set period of time that can be used to build many kinds of NFT financial products such as bonds, treasury bills, and many others. -2. Replicating SAFT contracts in a standardized form of semi-liquid vesting NFT assets. - - SAFTs are generally off-chain, while today's on-chain versions are mainly address-based, which makes distributing vesting shares to many representatives difficult. Standardization simplifies this convoluted process. -3. Providing a path for the standardization of vesting and token timelock contracts. - - There are many such contracts in the wild and most of them differ in both interface and implementation. -4. NFT marketplaces dedicated to vesting NFTs. - - Whole new sets of interfaces and analytics could be created from a common standard for token vesting NFTs. -5. Integrating vesting NFTs into services like Safe Wallet. - - A standard would mean services like Safe Wallet could more easily and uniformly support interactions with these types of contracts inside of a multisig contract. -6. Enable standardized fundraising implementations and general fundraising that sell vesting tokens (eg. SAFTs) in a more transparent manner. -7. Allows tools, front-end apps, aggregators, etc. to show a more holistic view of the vesting tokens and the properties available to users. - - Currently, every project needs to write their own visualization of the vesting schedule of their vesting assets. If this is standardized, third-party tools could be developed to aggregate all vesting NFTs from all projects for the user, display their schedules and allow the user to take aggregated vesting actions. - - Such tooling can easily discover compliance through the [ERC-165](./eip-165.md) `supportsInterface(InterfaceID)` check. -8. Makes it easier for a single wrapping implementation to be used across all vesting standards that defines multiple recipients, periodic renting of vesting tokens etc. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/** - * @title Non-Fungible Vesting Token Standard. - * @notice A non-fungible token standard used to vest ERC-20 tokens over a vesting release curve - * scheduled using timestamps. - * @dev Because this standard relies on timestamps for the vesting schedule, it's important to keep track of the - * tokens claimed per Vesting NFT so that a user cannot withdraw more tokens than allotted for a specific Vesting NFT. - * @custom:interface-id 0xbd3a202b - */ -interface IERC5725 is IERC721 { - /** - * This event is emitted when the payout is claimed through the claim function. - * @param tokenId the NFT tokenId of the assets being claimed. - * @param recipient The address which is receiving the payout. - * @param claimAmount The amount of tokens being claimed. - */ - event PayoutClaimed(uint256 indexed tokenId, address indexed recipient, uint256 claimAmount); - - /** - * This event is emitted when an `owner` sets an address to manage token claims for all tokens. - * @param owner The address setting a manager to manage all tokens. - * @param spender The address being permitted to manage all tokens. - * @param approved A boolean indicating whether the spender is approved to claim for all tokens. - */ - event ClaimApprovalForAll(address indexed owner, address indexed spender, bool approved); - - /** - * This event is emitted when an `owner` sets an address to manage token claims for a `tokenId`. - * @param owner The `owner` of `tokenId`. - * @param spender The address being permitted to manage a tokenId. - * @param tokenId The unique identifier of the token being managed. - * @param approved A boolean indicating whether the spender is approved to claim for `tokenId`. - */ - event ClaimApproval(address indexed owner, address indexed spender, uint256 indexed tokenId, bool approved); - - /** - * @notice Claim the pending payout for the NFT. - * @dev MUST grant the claimablePayout value at the time of claim being called to `msg.sender`. - * MUST revert if not called by the token owner or approved users. - * MUST emit PayoutClaimed. - * SHOULD revert if there is nothing to claim. - * @param tokenId The NFT token id. - */ - function claim(uint256 tokenId) external; - - /** - * @notice Number of tokens for the NFT which have been claimed at the current timestamp. - * @param tokenId The NFT token id. - * @return payout The total amount of payout tokens claimed for this NFT. - */ - function claimedPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Number of tokens for the NFT which can be claimed at the current timestamp. - * @dev It is RECOMMENDED that this is calculated as the `vestedPayout()` subtracted from `payoutClaimed()`. - * @param tokenId The NFT token id. - * @return payout The amount of unlocked payout tokens for the NFT which have not yet been claimed. - */ - function claimablePayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Total amount of tokens which have been vested at the current timestamp. - * This number also includes vested tokens which have been claimed. - * @dev It is RECOMMENDED that this function calls `vestedPayoutAtTime` - * with `block.timestamp` as the `timestamp` parameter. - * @param tokenId The NFT token id. - * @return payout Total amount of tokens which have been vested at the current timestamp. - */ - function vestedPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Total amount of vested tokens at the provided timestamp. - * This number also includes vested tokens which have been claimed. - * @dev `timestamp` MAY be both in the future and in the past. - * Zero MUST be returned if the timestamp is before the token was minted. - * @param tokenId The NFT token id. - * @param timestamp The timestamp to check on, can be both in the past and the future. - * @return payout Total amount of tokens which have been vested at the provided timestamp. - */ - function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) external view returns (uint256 payout); - - /** - * @notice Number of tokens for an NFT which are currently vesting. - * @dev The sum of vestedPayout and vestingPayout SHOULD always be the total payout. - * @param tokenId The NFT token id. - * @return payout The number of tokens for the NFT which are vesting until a future date. - */ - function vestingPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice The start and end timestamps for the vesting of the provided NFT. - * MUST return the timestamp where no further increase in vestedPayout occurs for `vestingEnd`. - * @param tokenId The NFT token id. - * @return vestingStart The beginning of the vesting as a unix timestamp. - * @return vestingEnd The ending of the vesting as a unix timestamp. - */ - function vestingPeriod(uint256 tokenId) external view returns (uint256 vestingStart, uint256 vestingEnd); - - /** - * @notice Token which is used to pay out the vesting claims. - * @param tokenId The NFT token id. - * @return token The token which is used to pay out the vesting claims. - */ - function payoutToken(uint256 tokenId) external view returns (address token); - - /** - * @notice Sets a global `operator` with permission to manage all tokens owned by the current `msg.sender`. - * @param operator The address to let manage all tokens. - * @param approved A boolean indicating whether the spender is approved to claim for all tokens. - */ - function setClaimApprovalForAll(address operator, bool approved) external; - - /** - * @notice Sets a tokenId `operator` with permission to manage a single `tokenId` owned by the `msg.sender`. - * @param operator The address to let manage a single `tokenId`. - * @param tokenId the `tokenId` to be managed. - * @param approved A boolean indicating whether the spender is approved to claim for all tokens. - */ - function setClaimApproval(address operator, bool approved, uint256 tokenId) external; - - /** - * @notice Returns true if `owner` has set `operator` to manage all `tokenId`s. - * @param owner The owner allowing `operator` to manage all `tokenId`s. - * @param operator The address who is given permission to spend tokens on behalf of the `owner`. - */ - function isClaimApprovedForAll(address owner, address operator) external view returns (bool isClaimApproved); - - /** - * @notice Returns the operating address for a `tokenId`. - * If `tokenId` is not managed, then returns the zero address. - * @param tokenId The NFT `tokenId` to query for a `tokenId` manager. - */ - function getClaimApproved(uint256 tokenId) external view returns (address operator); -} - -``` - -## Rationale - -### Terms - -These are base terms used around the specification which function names and definitions are based on. - -- _vesting_: Tokens which a vesting NFT is vesting until a future date. -- _vested_: Total amount of tokens a vesting NFT has vested. -- _claimable_: Amount of vested tokens which can be unlocked. -- _claimed_: Total amount of tokens unlocked from a vesting NFT. -- _timestamp_: The unix `timestamp` (seconds) representation of dates used for vesting. - -### Vesting Functions - -**`vestingPayout` + `vestedPayout`** - -`vestingPayout(uint256 tokenId)` and `vestedPayout(uint256 tokenId)` add up to the total number of tokens which can be claimed by the end of of the vesting schedule. This is also equal to `vestedPayoutAtTime(uint256 tokenId, uint256 timestamp)` with `type(uint256).max` as the `timestamp`. - -The rationale for this is to guarantee that the tokens `vested` and tokens `vesting` are always in sync. The intent is that the vesting curves created are deterministic across the `vestingPeriod`. This creates useful opportunities for integration with these NFTs. For example: A vesting schedule can be iterated through and a vesting curve could be visualized, either on-chain or off-chain. - -**`vestedPayout` vs `claimedPayout` & `claimablePayout`** - -```solidity -vestedPayout - claimedPayout - claimablePayout = lockedPayout -``` - -- `vestedPayout(uint256 tokenId)` provides the total amount of payout tokens which have **vested** _including `claimedPayout(uint256 tokenId)`_. -- `claimedPayout(uint256 tokenId)` provides the total amount of payout tokens which have been unlocked at the current `timestamp`. -- `claimablePayout(uint256 tokenId)` provides the amount of payout tokens which can be unlocked at the current `timestamp`. - -The rationale for providing three functions is to support a number of features: - -1. The return of `vestedPayout(uint256 tokenId)` will always match the return of `vestedPayoutAtTime(uint256 tokenId, uint256 timestamp)` with `block.timestamp` as the `timestamp`. -2. `claimablePayout(uint256 tokenId)` can be used to easily see the current payout unlock amount and allow for unlock cliffs by returning zero until a `timestamp` has been passed. -3. `claimedPayout(uint256 tokenId)` is helpful to see tokens unlocked from an NFT and it is also necessary for the calculation of vested-but-locked payout tokens: `vestedPayout - claimedPayout - claimablePayout = lockedPayout`. This would depend on how the vesting curves are configured by the an implementation of this standard. - -`vestedPayoutAtTime(uint256 tokenId, uint256 timestamp)` provides functionality to iterate through the `vestingPeriod(uint256 tokenId)` and provide a visual of the release curve. The intent is that release curves are created which makes `vestedPayoutAtTime(uint256 tokenId, uint256 timestamp)` deterministic. - -### Timestamps - -Generally in Solidity development it is advised against using `block.timestamp` as a state dependant variable as the timestamp of a block can be manipulated by a miner. The choice to use a `timestamp` over a `block` is to allow the interface to work across multiple **Ethereum Virtual Machine** (EVM) compatible networks which generally have different block times. Block proposal with a significantly fabricated timestamp will generally be dropped by all node implementations which makes the window for abuse negligible. - -The `timestamp` makes cross chain integration easy, but internally, the reference implementation keeps track of the token payout per Vesting NFT to ensure that excess tokens alloted by the vesting terms cannot be claimed. - -### Limitation of Scope - -- **Historical claims**: While historical vesting schedules can be determined on-chain with `vestedPayoutAtTime(uint256 tokenId, uint256 timestamp)`, historical claims would need to be calculated through historical transaction data. Most likely querying for `PayoutClaimed` events to build a historical graph. - -### Extension Possibilities - -These feature are not supported by the standard as is, but the standard could be extended to support these more advanced features. - -- **Custom Vesting Curves**: This standard intends on returning deterministic `vesting` values given NFT `tokenId` and a **timestamp** as inputs. This is intentional as it provides for flexibility in how the vesting curves work under the hood which doesn't constrain projects who intend on building a complex smart contract vesting architecture. -- **NFT Rentals**: Further complex DeFi tools can be created if vesting NFTs could be rented. - -This is done intentionally to keep the base standard simple. These features can and likely will be added through extensions of this standard. - -## Backwards Compatibility - -- The Vesting NFT standard is meant to be fully backwards compatible with any current [ERC-721](./eip-721.md) integrations and marketplaces. -- The Vesting NFT standard also supports [ERC-165](./eip-165.md) interface detection for detecting `EIP-721` compatibility, as well as Vesting NFT compatibility. - -## Test Cases - -The reference vesting NFT repository includes tests written in Hardhat. - -## Reference Implementation - -A reference implementation of this EIP can be found in [ERC-5725 assets](../assets/eip-5725/README.md). - -## Security Considerations - -**timestamps** - -- Vesting schedules are based on timestamps. As such, it's important to keep track of the number of tokens which have been claimed and to not give out more tokens than alloted for a specific Vesting NFT. - - `vestedPayoutAtTime(tokenId, type(uint256).max)`, for example, must return the total payout for a given `tokenId` - -**approvals** - -- When an [ERC-721](./eip-721.md) approval is made on a Vesting NFT, the operator would have the rights to transfer the Vesting NFT to themselves and then claim the vested tokens. -- When a ERC-5725 approval is made on a Vesting NFT, the operator would have the rights to claim the vested tokens, but not transfer the NFT away from the owner. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5725.md diff --git a/EIPS/eip-5727.md b/EIPS/eip-5727.md index 9b5575d8c7eccd..0a94cf1ae1c65b 100644 --- a/EIPS/eip-5727.md +++ b/EIPS/eip-5727.md @@ -1,461 +1 @@ ---- -eip: 5727 -title: Semi-Fungible Soulbound Token -description: An interface for soulbound tokens, also known as badges or account-bound tokens, that can be both fungible and non-fungible. -author: Austin Zhu (@AustinZhu), Terry Chen -discussions-to: https://ethereum-magicians.org/t/eip-5727-semi-fungible-soulbound-token/11086 -status: Draft -type: Standards Track -category: ERC -created: 2022-09-28 -requires: 165, 712, 721, 3525, 4906, 5192, 5484 ---- - -## Abstract - -An interface for soulbound tokens (SBT), which are non-transferable tokens representing a person's identity, credentials, affiliations, and reputation. - -Our interface can handle a combination of fungible and non-fungible tokens in an organized way. It provides a set of core methods that can be used to manage the lifecycle of soulbound tokens, as well as a rich set of extensions that enables DAO governance, delegation, token expiration, and account recovery. - -This interface aims to provide a flexible and extensible framework for the development of soulbound token systems. - -## Motivation - -The Web3 ecosystem nowadays is largely dominated by highly-financialized tokens, which are designed to be freely transferable and interchangeable. However, there are many use cases in our society that require non-transferablity. For example, a membership card guarantees one's proprietary rights in a community, and such rights should not be transferable to others. - -We have already seen many attempts to create such non-transferable tokens in the Ethereum community. However, they lack the flexibility to support both fungible and non-fungible tokens and do not provide extensible features for critical use cases. - -Our interface can be used to represent non-transferable ownerships, and provides features for common use cases including but not limited to: - -- granular lifecycle management of SBTs (e.g. minting, revocation, subscription and expiration) -- management of SBTs via community voting and delegation (e.g. DAO governance, operators) -- recovery of SBTs (e.g. account recovery and key rotation) -- fungible and non-fungible SBTs (e.g. membership cards and loyalty points) -- the grouping of SBTs using slots (e.g. complex reward schemes with a combination of vouchers, points, and badges) -- claimable SBTs (e.g. airdrops, giveaways, and referrals) - -A common interface for soulbound tokens will not only help enrich the Web3 ecosystem but also facilitates the growth of a decentralized society. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -A token is identified by its `tokenId`, which is a 256-bit unsigned integer. A token can also have a value denoting its denomination. - -A slot is identified by its `slotId`, which is a 256-bit unsigned integer. Slots are used to group fungible and non-fungible tokens together, thus make tokens semi-fungible. A token can only belong to one slot at a time. - -### Core - -The core methods are used to manage the lifecycle of SBTs. They MUST be supported by all semi-fungible SBT implementations. - -```solidity -/** - * @title ERC5727 Soulbound Token Interface - * @dev The core interface of the ERC5727 standard. - */ -interface IERC5727 is IERC3525, IERC5192, IERC5484, IERC4906 { - /** - * @dev MUST emit when a token is revoked. - * @param from The address of the owner - * @param tokenId The token id - */ - event Revoked(address indexed from, uint256 indexed tokenId); - - /** - * @dev MUST emit when a token is verified. - * @param by The address that initiated the verification - * @param tokenId The token id - * @param result The result of the verification - */ - event Verified(address indexed by, uint256 indexed tokenId, bool result); - - /** - * @notice Get the verifier of a token. - * @dev MUST revert if the `tokenId` does not exist - * @param tokenId the token for which to query the verifier - * @return The address of the verifier of `tokenId` - */ - function verifierOf(uint256 tokenId) external view returns (address); - - /** - * @notice Get the issuer of a token. - * @dev MUST revert if the `tokenId` does not exist - * @param tokenId the token for which to query the issuer - * @return The address of the issuer of `tokenId` - */ - function issuerOf(uint256 tokenId) external view returns (address); - - /** - * @notice Issue a token in a specified slot to an address. - * @dev MUST revert if the `to` address is the zero address. - * MUST revert if the `verifier` address is the zero address. - * @param to The address to issue the token to - * @param tokenId The token id - * @param slot The slot to issue the token in - * @param burnAuth The burn authorization of the token - * @param verifier The address of the verifier - * @param data Additional data used to issue the token - */ - function issue( - address to, - uint256 tokenId, - uint256 slot, - BurnAuth burnAuth, - address verifier, - bytes calldata data - ) external payable; - - /** - * @notice Issue credit to a token. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param amount The amount of the credit - * @param data The additional data used to issue the credit - */ - function issue( - uint256 tokenId, - uint256 amount, - bytes calldata data - ) external payable; - - /** - * @notice Revoke a token from an address. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param data The additional data used to revoke the token - */ - function revoke(uint256 tokenId, bytes calldata data) external payable; - - /** - * @notice Revoke credit from a token. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param amount The amount of the credit - * @param data The additional data used to revoke the credit - */ - function revoke( - uint256 tokenId, - uint256 amount, - bytes calldata data - ) external payable; - - /** - * @notice Verify if a token is valid. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param data The additional data used to verify the token - * @return A boolean indicating whether the token is successfully verified - */ - function verify( - uint256 tokenId, - bytes calldata data - ) external returns (bool); -} -``` - -### Extensions - -All extensions below are OPTIONAL for [ERC-5727](./eip-5727.md) implementations. An implementation MAY choose to implement some, none, or all of them. - -#### Enumerable - -This extension provides methods to enumerate the tokens of a owner. It is recommended to be implemented together with the core interface. - -```solidity -/** - * @title ERC5727 Soulbound Token Enumerable Interface - * @dev This extension allows querying the tokens of a owner. - */ -interface IERC5727Enumerable is IERC3525SlotEnumerable, IERC5727 { - /** - * @notice Get the number of slots of a owner. - * @param owner The owner whose number of slots is queried for - * @return The number of slots of the `owner` - */ - function slotCountOfOwner(address owner) external view returns (uint256); - - /** - * @notice Get the slot with `index` of the `owner`. - * @dev MUST revert if the `index` exceed the number of slots of the `owner`. - * @param owner The owner whose slot is queried for. - * @param index The index of the slot queried for - * @return The slot is queried for - */ - function slotOfOwnerByIndex( - address owner, - uint256 index - ) external view returns (uint256); - - /** - * @notice Get the balance of a owner in a slot. - * @dev MUST revert if the slot does not exist. - * @param owner The owner whose balance is queried for - * @param slot The slot whose balance is queried for - * @return The balance of the `owner` in the `slot` - */ - function ownerBalanceInSlot( - address owner, - uint256 slot - ) external view returns (uint256); -} -``` - -#### Metadata - -This extension provides methods to fetch the metadata of a token, a slot and the contract itself. It is recommended to be implemented if you need to specify the appearance and properties of tokens, slots and the contract (i.e. the SBT collection). - -```solidity -/** - * @title ERC5727 Soulbound Token Metadata Interface - * @dev This extension allows querying the metadata of soulbound tokens. - */ -interface IERC5727Metadata is IERC3525Metadata, IERC5727 { - -} -``` - -#### Governance - -This extension provides methods to manage the mint and revocation permissions through voting. It is useful if you want to rely on a group of voters to decide the issuance a particular SBT. - -```solidity -/** - * @title ERC5727 Soulbound Token Governance Interface - * @dev This extension allows issuing of tokens by community voting. - */ -interface IERC5727Governance is IERC5727 { - enum ApprovalStatus { - Pending, - Approved, - Rejected, - Removed - } - - /** - * @notice Emitted when a token issuance approval is changed. - * @param approvalId The id of the approval - * @param creator The creator of the approval, zero address if the approval is removed - * @param status The status of the approval - */ - event ApprovalUpdate( - uint256 indexed approvalId, - address indexed creator, - ApprovalStatus status - ); - - /** - * @notice Emitted when a voter approves an approval. - * @param voter The voter who approves the approval - * @param approvalId The id of the approval - */ - event Approve( - address indexed voter, - uint256 indexed approvalId, - bool approve - ); - - /** - * @notice Create an approval of issuing a token. - * @dev MUST revert if the caller is not a voter. - * MUST revert if the `to` address is the zero address. - * @param to The owner which the token to mint to - * @param tokenId The id of the token to mint - * @param amount The amount of the token to mint - * @param slot The slot of the token to mint - * @param burnAuth The burn authorization of the token to mint - * @param data The additional data used to mint the token - */ - function requestApproval( - address to, - uint256 tokenId, - uint256 amount, - uint256 slot, - BurnAuth burnAuth, - address verifier, - bytes calldata data - ) external; - - /** - * @notice Remove `approvalId` approval request. - * @dev MUST revert if the caller is not the creator of the approval request. - * MUST revert if the approval request is already approved or rejected or non-existent. - * @param approvalId The approval to remove - */ - function removeApprovalRequest(uint256 approvalId) external; - - /** - * @notice Approve `approvalId` approval request. - * @dev MUST revert if the caller is not a voter. - * MUST revert if the approval request is already approved or rejected or non-existent. - * @param approvalId The approval to approve - * @param approve True if the approval is approved, false if the approval is rejected - * @param data The additional data used to approve the approval (e.g. the signature, voting power) - */ - function voteApproval( - uint256 approvalId, - bool approve, - bytes calldata data - ) external; - - /** - * @notice Get the URI of the approval. - * @dev MUST revert if the `approvalId` does not exist. - * @param approvalId The approval whose URI is queried for - * @return The URI of the approval - */ - function approvalURI( - uint256 approvalId - ) external view returns (string memory); -} -``` - -#### Delegate - -This extension provides methods to delegate (undelegate) mint right in a slot to (from) an operator. It is useful if you want to allow an operator to mint tokens in a specific slot on your behalf. - -```solidity -/** - * @title ERC5727 Soulbound Token Delegate Interface - * @dev This extension allows delegation of issuing and revocation of tokens to an operator. - */ -interface IERC5727Delegate is IERC5727 { - /** - * @notice Emitted when a token issuance is delegated to an operator. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - event Delegate(address indexed operator, uint256 indexed slot); - - /** - * @notice Emitted when a token issuance is revoked from an operator. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - event UnDelegate(address indexed operator, uint256 indexed slot); - - /** - * @notice Delegate rights to `operator` for a slot. - * @dev MUST revert if the caller does not have the right to delegate. - * MUST revert if the `operator` address is the zero address. - * MUST revert if the `slot` is not a valid slot. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - function delegate(address operator, uint256 slot) external; - - /** - * @notice Revoke rights from `operator` for a slot. - * @dev MUST revert if the caller does not have the right to delegate. - * MUST revert if the `operator` address is the zero address. - * MUST revert if the `slot` is not a valid slot. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - - function undelegate(address operator, uint256 slot) external; - - /** - * @notice Check if an operator has the permission to issue or revoke tokens in a slot. - * @param operator The operator to check - * @param slot The slot to check - */ - function isOperatorFor( - address operator, - uint256 slot - ) external view returns (bool); -} - -``` - -#### Recovery - -This extension provides methods to recover tokens from a stale owner. It is recommended to use this extension so that users are able to retrieve their tokens from a compromised or old wallet in certain situations. The signing scheme SHALL be compatible with [EIP-712](./eip-712.md) for readability and usability. - -```solidity -/** - * @title ERC5727 Soulbound Token Recovery Interface - * @dev This extension allows recovering soulbound tokens from an address provided its signature. - */ -interface IERC5727Recovery is IERC5727 { - /** - * @notice Emitted when the tokens of `owner` are recovered. - * @param from The owner whose tokens are recovered - * @param to The new owner of the tokens - */ - event Recovered(address indexed from, address indexed to); - - /** - * @notice Recover the tokens of `owner` with `signature`. - * @dev MUST revert if the signature is invalid. - * @param owner The owner whose tokens are recovered - * @param signature The signature signed by the `owner` - */ - function recover(address owner, bytes memory signature) external; -} -``` - -#### Expirable - -This extension provides methods to manage the expiration of tokens. It is useful if you want to expire/invalidate tokens after a certain period of time. - -```solidity -/** - * @title ERC5727 Soulbound Token Expirable Interface - * @dev This extension allows soulbound tokens to be expirable and renewable. - */ -interface IERC5727Expirable is IERC5727, IERC5643 { - /** - * @notice Set the expiry date of a token. - * @dev MUST revert if the `tokenId` token does not exist. - * MUST revert if the `date` is in the past. - * @param tokenId The token whose expiry date is set - * @param expiration The expire date to set - * @param isRenewable Whether the token is renewable - */ - function setExpiration( - uint256 tokenId, - uint64 expiration, - bool isRenewable - ) external; -} -``` - -## Rationale - -### Token storage model - -We adopt semi-fungible token storage models designed to support both fungible and non-fungible tokens, inspired by the semi-fungible token standard. We found that such a model is better suited to the representation of SBT than the model used in [ERC-1155](./eip-1155.md). - -Firstly, each slot can be used to represent different categories of SBTs. For instance, a DAO can have membership SBTs, role badges, reputations, etc. in one SBT collection. - -Secondly, unlike [ERC-1155](./eip-1155.md), in which each unit of fungible tokens is exactly the same, our interface can help differentiate between similar tokens. This is justified by that credential scores obtained from different entities differ not only in value but also in their effects, validity periods, origins, etc. However, they still share the same slot as they all contribute to a person's credibility, membership, etc. - -### Recovery mechanism - -To prevent the loss of SBTs, we propose a recovery mechanism that allows users to recover their tokens by providing a signature signed by their owner address. This mechanism is inspired by [ERC-1271](./eip-1271.md). - -Since SBTs are bound to an address and are meant to represent the identity of the address, which cannot be split into fractions. Therefore, each recovery should be considered as a transfer of all the tokens of the owner. This is why we use the `recover` function instead of `transferFrom` or `safeTransferFrom`. - -## Backwards Compatibility - -This EIP proposes a new token interface which is compatible with [ERC-721](./eip-721.md), [ERC-3525](./eip-3525.md), [ERC-4906](./eip-4906.md), [ERC-5192](./eip-5192.md), [ERC-5484](./eip-5484.md). - -This EIP is also compatible with [ERC-165](./eip-165.md). - -## Test Cases - -Our sample implementation includes test cases written using Hardhat. - -## Reference Implementation - -You can find our reference implementation [here](../assets/eip-5727/ERC5727.sol). - -## Security Considerations - -This EIP does not involve the general transfer of tokens, and thus there will be no security issues related to token transfer generally. - -However, users should be aware of the security risks of using the recovery mechanism. If a user loses his/her private key, all his/her soulbound tokens will be exposed to potential theft. The attacker can create a signature and restore all SBTs of the victim. Therefore, users should always keep their private keys safe. We recommend developers implement a recovery mechanism that requires multiple signatures to restore SBTs. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5727.md diff --git a/EIPS/eip-5732.md b/EIPS/eip-5732.md index 0828f88c2a39e0..5c3ec66886a6cc 100644 --- a/EIPS/eip-5732.md +++ b/EIPS/eip-5732.md @@ -1,125 +1 @@ ---- -eip: 5732 -title: Commit Interface -description: A simple but general commit interface to support commit-reveal scheme. -author: Zainan Victor Zhou (@xinbenlv), Matt Stam (@mattstam) -discussions-to: https://ethereum-magicians.org/t/erc-5732-simple-commit-interface-to-support-commit-reveal-schemes/11115 -status: Final -type: Standards Track -category: ERC -created: 2022-09-29 -requires: 165, 1271 ---- - -## Abstract - -A simple commit interface to support commit-reveal scheme which provides **only** a commit -method but no reveal method, allowing implementations to integrate this interface -with arbitrary reveal methods such as `vote` or `transfer`. - -## Motivation - -1. support commit-reveal privacy for applications such as voting. -2. make it harder for attackers for front-running, back-running or sandwich attacks. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Interfaces referenced in this specification are as follows: - -```solidity -pragma solidity >=0.7.0 <0.9.0; - -// The EIP-165 identifier of this interface is 0xf14fcbc8 -interface IERC_COMMIT_CORE { - function commit(bytes32 _commitment) payable external; -} - -pragma solidity >=0.7.0 <0.9.0; - -// The EIP-165 identifier of this interface is 0x67b2ec2c -interface IERC_COMMIT_GENERAL { - event Commit( - uint256 indexed _timePoint, - address indexed _from, - bytes32 indexed _commitment, - bytes _extraData); - function commitFrom( - address _from, - bytes32 _commitment, - bytes calldata _extraData) - payable external returns(uint256 timePoint); -} -``` - -1. A compliant contract MUST implement the `IERC_COMMIT_CORE` interface. -2. A compliant contract SHOULD implement the `IERC_COMMIT_GENERAL` interface. -3. A compliant contract that implements the `IERC_COMMIT_GENERAL` interface MUST accept `commit(_commitment)` as equivalent to `commitFrom(msg.sender, _commitment, [/*empty array*/])`. -4. The `timePoint` return value of `commitFrom` is RECOMMENDED to use `block.timestamp` or `block.number` or a number that indicates the ordering of different commitments. When `commitFrom` is being called. -5. A compliant contract that implements `IERC_COMMIT_GENERAL` MUST emit event `Commit` when a commitment is accepted and recorded. In the parameter of both `Commit` and the `commitFrom` method, the `_timePoint` is a time-point-representing value that represents ordering of commitments in which a latter commitment will always have a _greater or equal value_ than a former commitment, such as `block.timestamp` or `block.number` or other time scale chosen by implementing contracts. - -6. The `extraData` is reserved for future behavior extension. If the `_from` is different from the TX signer, it is RECOMMENDED that compliant contract SHOULD validate signature for `_from`. For EOAs this will be validating its ECDSA signatures on chain. For smart contract accounts, it is RECOMMENDED to use [EIP-1271](./eip-1271.md) to validate the signatures. - -7. One or more methods of a compliant contract MAY be used for reveal. - -But there MUST be a way to supply an extra field of `secret_salt`, so that committer can later open the `secret_salt` in the reveal TX that exposes the `secret_salt`. The size and location of `secret_salt` is intentionally unspecified in this EIP to maximize flexibility for integration. - -8. It is RECOMMENDED for compliant contracts to implement [EIP-165](./eip-165.md). - -## Rationale - -1. One design options is that we can attach a Commit Interface to any individual ERCs such as voting standards or token standards. We choose to have a simple and generalize commit interface so all ERCs can be extended to support commit-reveal without changing their basic method signatures. - -2. The key derived design decision we made is we will have a standardized `commit` method without a standardized `reveal` method, making room for customized reveal method or using `commit` with existing standard. - -3. We chose to have a simple one parameter method of `commit` in our Core interface to make it fully backward compatible with a few prior-adoptions e.g. ENS - -4. We also add a `commitFrom` to easy commitment being generated off-chain and submitted by some account on behalf by another account. - -## Backwards Compatibility - -This EIP is backward compatible with all existing ERCs method signature that has extraData. New EIPs can be designed with an extra field of "salt" to make it easier to support this EIP, but not required. - -The `IERC_COMMIT_CORE` is backward compatible with ENS implementations and other existing prior-art. - -## Reference Implementation - -### Commit with ENS Register as Reveal - -In ENS registering process, currently inside of `ETHRegistrarController` contract a commit function is being used to allow registerer fairly register a desire domain to avoid being front-run. - -Here is how ENS uses commitment in its registration logic: - -```solidity -function commit(bytes32 commitment) public { - require(commitments[commitment] + maxCommitmentAge < now); - commitments[commitment] = now; -} -``` - -With this EIP it can be updated to - -```solidity -function commit(bytes32 commitment, bytes calldata data) public { - require(commitments[commitment] + maxCommitmentAge < now); - commitments[commitment] = now; - emit Commit(...); -} -``` - -## Security Considerations - -1. Do not use the reference implementation in production. It is just for demonstration purposes. -2. The reveal transactions and parameters, especially `secret_salt`, MUST be kept secret before they are revealed. -3. The length of `secret_salt` must be cryptographically long enough and the random values used to generate `secret_salt` must be cryptographically safe. -4. Users must NEVER reuse a used `secret_salt`. It's recommended for client applications to warn users who attempt to do so. -5. Contract implementations should consider deleting the commitment of a given sender immediately to reduce the chances of a replay attack or re-entry attack. -6. Contract implementations may consider including the ordering of commitment received to add restrictions on the order of reveal transactions. -7. There is potential for replay attacks across different chainIds or chains resulting from forks. In these cases, the chainId must be included in the generation of commitment. For applications with a higher risk of replay attacks, implementors should consider battle-tested and cryptographically-secure solutions such as [EIP-712](./eip-712.md) to compose commitments before creating their own new solution. -8. Proper time gaps are suggested if the purpose is to avoid frontrunning attacks. -9. For compliant contract that requires the `_timePoint` from the next transaction to be _strictly greater_ than that of any previous transaction, `block.timestamp` and `block.number` are not reliable as two transactions could co-exist in the same block resulting in the same `_timePoint` value. In such case, extra measures to enforce this strict monotonicity are required, such as the use of a separate sate variable in the contract to keep track of number of commits it receives, or to reject any second/other TX that shares the same `block.timestamp` or `block.number`. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5732.md diff --git a/EIPS/eip-5744.md b/EIPS/eip-5744.md index 1e78429af86f9d..d54add5df66457 100644 --- a/EIPS/eip-5744.md +++ b/EIPS/eip-5744.md @@ -1,93 +1 @@ ---- -eip: 5744 -title: Latent Fungible Token -description: An interface for tokens that become fungible after a period of time. -author: Cozy Finance (@cozyfinance), Tony Sheng (@tonysheng), Matt Solomon (@mds1), David Laprade (@davidlaprade), Payom Dousti (@payomdousti), Chad Fleming (@chad-js), Franz Chen (@Dendrimer) -discussions-to: https://ethereum-magicians.org/t/eip-5744-latent-fungible-token/11111 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-09-29 -requires: 20, 2612 ---- - -## Abstract - -The following standard is an extension of [EIP-20](./eip-20.md) that enables tokens to become fungible after some initial non-fungible period. -Once minted, tokens are non-fungible until they reach maturity. -At maturity, they become fungible and can be transferred, traded, and used in any way that a standard EIP-20 token can be used. - -## Motivation - -Example use cases include: - -- Receipt tokens that do not become active until a certain date or condition is met. For example, this can be used to enforce minimum deposit durations in lending protocols. -- Vesting tokens that cannot be transferred or used until the vesting period has elapsed. - -## Specification - -All latent fungible tokens MUST implement EIP-20 to represent the token. -The `balanceOf` and `totalSupply` return quantities for all tokens, not just the matured, fungible tokens. -A new method called `balanceOfMatured` MUST be added to the ABI. -This method returns the balance of matured tokens for a given address: - -```solidity -function balanceOfMatured(address user) external view returns (uint256); -``` - -An additional method called `getMints` MUST be added, which returns an array of all mint metadata for a given address: - -```solidity -struct MintMetadata { - // Amount of tokens minted. - uint256 amount; - // Timestamp of the mint, in seconds. - uint256 time; - // Delay in seconds until these tokens mature and become fungible. When the - // delay is not known (e.g. if it's dependent on other factors aside from - // simply elapsed time), this value must be `type(uint256).max`. - uint256 delay; -} - -function getMints(address user) external view returns (MintMetadata[] memory); -``` - -Note that the implementation does not require that each of the above metadata parameters are stored as a `uint256`, just that they are returned as `uint256`. - -An additional method called `mints` MAY be added. -This method returns the metadata for a mint based on its ID: - -```solidity -function mints(address user, uint256 id) external view returns (MintMetadata memory); -``` - -The ID is not prescriptive—it may be an index in an array, or may be generated by other means. - -The `transfer` and `transferFrom` methods MAY be modified to revert when transferring tokens that have not matured. -Similarly, any methods that burn tokens MAY be modified to revert when burning tokens that have not matured. - -All latent fungible tokens MUST implement EIP-20’s optional metadata extensions. -The `name` and `symbol` functions MUST reflect the underlying token’s `name` and `symbol` in some way. - -## Rationale - -The `mints` method is optional because the ID is optional. In some use cases such as vesting where a user may have a maximum of one mint, an ID is not required. - -Similarly, vesting use cases may want to enforce non-transferrable tokens until maturity, whereas lending receipt tokens with a minimum deposit duration may want to support transfers at all times. - -It is possible that the number of mints held by a user is so large that it is impractical to return all of them in a single `eth_call`. -This is unlikely so it was not included in the spec. -If this is likely for a given use case, the implementer may choose to implement an alternative method that returns a subset of the mints, such as `getMints(address user, uint256 startId, uint256 endId)`. -However, if IDs are not sequential, a different signature may be required, and therefore this was not included in the specification. - -## Backwards Compatibility - -This proposal is fully backward compatible with the EIP-20 standard and has no known compatibility issues with other standards. - -## Security Considerations - -Iterating over large arrays of mints is not recommended, as this is very expensive and may cause the protocol, or just a user's interactions with it, to be stuck if this exceeds the block gas limit and reverts. There are some ways to mitigate this, with specifics dependent on the implementation. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5744.md diff --git a/EIPS/eip-5750.md b/EIPS/eip-5750.md index c570673aac4347..da416f47b0dc3a 100644 --- a/EIPS/eip-5750.md +++ b/EIPS/eip-5750.md @@ -1,166 +1 @@ ---- -eip: 5750 -title: General Extensibility for Method Behaviors -description: Designating last param of dynamically sized bytes to be used for behavior extensions of methods. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-5750-method-with-extra-data/11176 -status: Final -type: Standards Track -category: ERC -created: 2022-10-04 -requires: 165 ---- - -## Abstract - -This EIP standardizes the passing of unstructured call data to functions to enable future extensibility. - -## Motivation - -The purpose of having extra data in a method is to allow further extensions to existing method interfaces. - -It is it useful to make methods extendable. Any methods complying with this EIP, such as overloaded `transfer` and `vote` could use string reasons as the extra data. Existing EIPs that have exported methods compliant with this EIP can be extended for behaviors such as using the extra data to prove endorsement, as a salt, as a nonce, or as a commitment for a reveal/commit scheme. Finally, data can be passed forward to callbacks. - -There are two ways to achieve extensibility for existing functions. Each comes with their set of challenges: - -1. Add a new method - - * What will the method name be? - * What will the parameters be? - * How many use-cases does a given method signature support? - * Does this support off-chain signatures? - -2. Use one or more existing parameters, or add one or more new ones - - * Should existing parameters be repurposed, or should more be added? - * How many parameters should be used? - * What are their sizes and types? - -Standardizing how methods can be extended helps to answer these questions. - -Finally, this EIP aims to achieve maximum backward and future compatibility. Many EIPs already partially support this EIP, such as [EIP-721](./eip-721.md) and [EIP-1155](./eip-1155.md). This EIP supports many use cases, from commit-reveal schemes ([EIP-5732](./eip-5732.md)), to adding digital signatures alongside with a method call. Other implementers and EIPs should be able to depend on the compatibility granted by this EIP so that all compliant method interfaces are eligible for future new behaviors. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -When used in this EIP, the term `bytes` MUST be interpreted as the dynamically-sized byte array in Solidity data types. - -1. Unlike many other ERCs which is compliant at the `contract` level, this ERC's specification specify compliance at `method` level. - -2. Any method with a bytes as this method's last parameter is an _eligible_ method. It looks like this `function methodName(type1 value1, type2 value2, ... bytes data)`. - -3. A _compliant_ method MUST be an _eligible_ method and MUST also designate that last `bytes` field in its method parameter for behaviors extensions. - -4. If an _eligible_ method has an overloaded sibling method that -has the exact same method name and exact same preceding parameters -except for not having the last `bytes` parameter, the behavior -of the compliant method MUST be identical to -its overloaded sibling method when last `bytes` is an empty array. - -### Examples of compliant and non-compliant methods - -1. Here is a compliant method `methodName1` in a `Foo` contract - -```solidity -contract Foo { - // @dev This method allows extension behavior via `_data` field; - function methodName1(uint256 _param1, address _param2, bytes calldata _data); - function firstNonRelatedMethod(uint256 someValue); - function secondNonRelatedMethod(uint256 someValue); -} -``` - -2. Here is a compliant method `methodName2` in a `Bar` contract which is an overloaded method for another `methodName2`. - - -```solidity -contract Foo { - // @dev This is a sibling method to `methodName2(uint256 _param1, address _param2, bytes calldata _data);` - function methodName2(uint256 _param1, address _param2); - - // @dev This method allows extension behavior via `_data` field; - // When passed in an empty array for `_data` field, this method - // MUST behave IDENTICAL to - // its overloaded sibling `methodName2(uint256 _param1, address _param2);` - function methodName2(uint256 _param1, address _param2, bytes calldata _data); - - function firstNonRelatedMethod(uint256 someValue); - function secondNonRelatedMethod(uint256 someValue); -} -``` - -3. Here is a non-compliant method `methodName1` because it do not allow extending behavior - -```solidity -contract Foo { - // @dev This method DO NOT allow extension behavior via `_data` field; - function methodName1(uint256 _param1, address _param2, bytes calldata _data); - function firstNonRelatedMethod(uint256 someValue); - function secondNonRelatedMethod(uint256 someValue); -} -``` - -4. Here is a non-compliant method -`methodName2(uint256 _param1, address _param2, bytes calldata _data);` -because it behaves differently -to its overloaded sibling method -`methodName2(uint256 _param1, address _param2);` when `_data` is empty array. - -```solidity -contract Foo { - // @dev This is a sibling method to `methodName2(uint256 _param1, address _param2, bytes calldata _data);` - function methodName2(uint256 _param1, address _param2); - - // @dev This method allows extension behavior via `_data` field; - // When passed in an empty array for `_data` field, this method - // behave DIFFERENTLY to - // its overloaded sibling `methodName2(uint256 _param1, address _param2);` - function methodName2(uint256 _param1, address _param2, bytes calldata _data); - - function firstNonRelatedMethod(uint256 someValue); - function secondNonRelatedMethod(uint256 someValue); -} -``` - -## Rationale - -1. Using the dynamically-sized `bytes` type allows for maximum flexibility by enabling payloads of arbitrary types. -2. Having the bytes specified as the last parameter makes this EIP compatible with the calldata layout of solidity. - -## Backwards Compatibility - -Many existing EIPs already have compliant methods as part of their specification. All contracts compliant with those EIPs are either fully or partially compliant with this EIP. - -Here is an incomplete list: - -* In [EIP-721](./eip-721.md), the following method is already compliant: - * `function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;` is already compliant -* In [EIP-1155](./eip-1155.md), the following methods are already compliant - * `function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;` - * `function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;` -* In [EIP-777](./eip-777.md), the following methods are already compliant - * `function burn(uint256 amount, bytes calldata data) external;` - * `function send(address to, uint256 amount, bytes calldata data) external;` - -However, not all functions that have a `bytes` as the last parameter are compliant. The following functions are not compliant without an overload since their last parameter is involved in functionality: - -* In [EIP-2535](./eip-2535.md), the following methods is not compliant: - * `function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external;` - * **Either** of the following can be done to create a compliance. - 1. An overload MUST be created: `function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata, bytes calldata _data) external;` which adds a new `_data` after all parameters of original method. - 2. The use of `bytes memory _calldata` MUST be relaxed to allow for extending behaviors. -* In [EIP-1271](./eip-1271.md), the following method is not compliant: - * `function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue);` - * **Either** of the following can be done to create a compliance: - 1. An new overload MUST be created: `function isValidSignature(bytes32 _hash, bytes memory _signature, bytes calldata _data) public view returns (bytes4 magicValue);` which adds a new `_data` after all parameters of original method. - 2. The use of `bytes memory _signature` MUST be relaxed to allow for extending behaviors. - -## Security Considerations - -1. If using the extra data for extended behavior, such as supplying signature for onchain verification, or supplying commitments in a commit-reveal scheme, best practices should be followed for those particular extended behaviors. -2. Compliant contracts must also take into consideration that the data parameter will be publicly revealed when submitted into the mempool or included in a block, so one must consider the risk of replay and transaction ordering attacks. **Unencrypted personally identifiable information must never be included in the data parameter.** - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5750.md diff --git a/EIPS/eip-5753.md b/EIPS/eip-5753.md index abc42c14a75833..28e07128920329 100644 --- a/EIPS/eip-5753.md +++ b/EIPS/eip-5753.md @@ -1,255 +1 @@ ---- -eip: 5753 -title: Lockable Extension for EIP-721 -description: Interface for disabling token transfers (locking) and re-enabling them (unlocking). -author: Filipp Makarov (@filmakarov) -discussions-to: https://ethereum-magicians.org/t/lockable-nfts-extension/8800 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-10-05 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [EIP-721](./eip-721.md). It introduces lockable NFTs. The locked asset can be used in any way except by selling and/or transferring it. The owner or operator can lock the token. When a token is locked, the unlocker address (an EOA or a contract) is set. Only the unlocker is able to `unlock` the token. - -## Motivation - -With NFTs, digital objects become digital goods, which are verifiably ownable, easily tradable, and immutably stored on the blockchain. That's why it's very important to continuously improve UX for non-fungible tokens, not just inherit it from one of the fungible tokens. - -In DeFi there is an UX pattern when you lock your tokens on a service smart contract. For example, if you want to borrow some $DAI, you have to provide some $ETH as collateral for a loan. During the loan period this $ETH is being locked into the lending service contract. Such a pattern works for $ETH and other fungible tokens. - -However, it should be different for NFTs because NFTs have plenty of use cases that require the NFT to stay in the holder's wallet even when it is used as collateral for a loan. You may want to keep using your NFT as a verified PFP on Twitter, or use it to authorize a Discord server through collab.land. You may want to use your NFT in a P2E game. And you should be able to do all of this even during the lending period, just like you are able to live in your house even if it is mortgaged. - -The following use cases are enabled for lockable NFTs: - -- **NFT-collateralised loans** Use your NFT as collateral for a loan without locking it on the lending protocol contract. Lock it on your wallet instead and continue enjoying all the utility of your NFT. -- **No collateral rentals of NFTs** Borrow NFT for a fee, without a need for huge collateral. You can use NFT, but not transfer it, so the lender is safe. The borrowing service contract automatically transfers NFT back to the lender as soon as the borrowing period expires. -- **Primary sales** Mint NFT for only the part of the price and pay the rest when you are satisfied with how the collection evolves. -- **Secondary sales** Buy and sell your NFT by installments. Buyer gets locked NFT and immediately starts using it. At the same time he/she is not able to sell the NFT until all the installments are paid. If full payment is not received, NFT goes back to the seller together with a fee. -- **S is for Safety** Use your exclusive blue chip NFTs safely and conveniently. The most convenient way to use NFT is together with MetaMask. However, MetaMask is vulnerable to various bugs and attacks. With `Lockable` extension you can lock your NFT and declare your safe cold wallet as an unlocker. Thus, you can still keep your NFT on MetaMask and use it conveniently. Even if a hacker gets access to your MetaMask, they won’t be able to transfer your NFT without access to the cold wallet. That’s what makes `Lockable` NFTs safe. -- **Metaverse ready** Locking NFT tickets can be useful during huge Metaverse events. That will prevent users, who already logged in with an NFT, from selling it or transferring it to another user. Thus we avoid double usage of one ticket. -- **Non-custodial staking** There are different approaches to non-custodial staking proposed by communities like CyberKongz, Moonbirds and other. Approach suggested in this impementation supposes that the token can only be staked in one place, not several palces at a time (it is like you can not deposit money in two bank accounts simultaneously). Also it doesn't require any additional code and is available with just locking feature. -Another approach to the same concept is using locking to provide proof of HODL. You can lock your NFTs from selling as a manifestation of loyalty to the community and start earning rewards for that. It is better version of the rewards mechanism, that was originally introduced by The Hashmasks and their $NCT token. -- **Safe and convenient co-ownership and co-usage** Extension of safe co-ownership and co-usage. For example, you want to purchase an expensive NFT asset together with friends, but it is not handy to use it with multisig, so you can safely rotate and use it between wallets. The NFT will be stored on one of the co-owners' wallet and he will be able to use it in any way (except transfers) without requiring multi-approval. Transfers will require multi-approval. - - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -EIP-721 compliant contracts MAY implement this EIP to provide standard methods of locking and unlocking the token at its current owner address. -If the token is locked, the `getLocked` function MUST return an address that is able to unlock the token. -For tokens that are not locked, the `getLocked` function MUST return `address(0)`. -The user MAY permanently lock the token by calling `lock(address(1), tokenId)`. - -When the token is locked, all the [EIP-721](./eip-721.md) transfer functions MUST revert, except if the transaction has been initiated by an unlocker. -When the token is locked, the [EIP-721](./eip-721.md) `approve` method MUST revert for this token. -When the token is locked, the [EIP-721](./eip-721.md) `getApproved` method SHOULD return `unlocker` address for this token so the unlocker is able to transfer this token. -When the token is locked, the `lock` method MUST revert for this token, even when it is called with the same `unlocker` as argument. -When the locked token is transferred by an unlocker, the token MUST be unlocked after the transfer. - -Marketplaces should call `getLocked` method of an EIP-721 Lockable token contract to learn whether a token with a specified tokenId is locked or not. Locked tokens SHOULD NOT be available for listings. Locked tokens can not be sold. Thus, marketplaces SHOULD hide the listing for the tokens that has been locked, because such orders can not be fulfilled. - -### Contract Interface - -```solidity -pragma solidity >=0.8.0; - -/// @dev Interface for the Lockable extension - -interface ILockable { - - /** - * @dev Emitted when `id` token is locked, and `unlocker` is stated as unlocking wallet. - */ - event Lock (address indexed unlocker, uint256 indexed id); - - /** - * @dev Emitted when `id` token is unlocked. - */ - event Unlock (uint256 indexed id); - - /** - * @dev Locks the `id` token and gives the `unlocker` address permission to unlock. - */ - function lock(address unlocker, uint256 id) external; - - /** - * @dev Unlocks the `id` token. - */ - function unlock(uint256 id) external; - - /** - * @dev Returns the wallet, that is stated as unlocking wallet for the `tokenId` token. - * If address(0) returned, that means token is not locked. Any other result means token is locked. - */ - function getLocked(uint256 tokenId) external view returns (address); - -} -``` - -The `supportsInterface` method MUST return `true` when called with `0x72b68110`. - -## Rationale - -This approach proposes a solution that is designed to be as minimal as possible. It only allows to lock the item (stating who will be able to unlock it) and unlock it when needed if a user has permission to do it. - -At the same time, it is a generalized implementation. It allows for a lot of extensibility and any of the potential use cases (or all of them), mentioned in the Motivation section. - -When there is a need to grant temporary and/or redeemable rights for the token (rentals, purchase with instalments) this EIP involves the real transfer of the token to the temporary user's wallet, not just assigning a role. -This choice was made to increase compatibility with all the existing NFT eco-system tools and dApps, such as Collab.land. Otherwise, it would require from all of such dApps implementing additional interfaces and logic. - -Naming and reference implementation for the functions and storage entities mimics that of Approval flow for [EIP-721] in order to be intuitive. - -## Backwards Compatibility - -This standard is compatible with current [EIP-721](./eip-721.md) standards. - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.8.0; - -import '../ILockable.sol'; -import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; - -/// @title Lockable Extension for ERC721 - -abstract contract ERC721Lockable is ERC721, ILockable { - - /*/////////////////////////////////////////////////////////////// - LOCKABLE EXTENSION STORAGE - //////////////////////////////////////////////////////////////*/ - - mapping(uint256 => address) internal unlockers; - - /*/////////////////////////////////////////////////////////////// - LOCKABLE LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Public function to lock the token. Verifies if the msg.sender is the owner - * or approved party. - */ - - function lock(address unlocker, uint256 id) public virtual { - address tokenOwner = ownerOf(id); - require(msg.sender == tokenOwner || isApprovedForAll(tokenOwner, msg.sender) - , "NOT_AUTHORIZED"); - require(unlockers[id] == address(0), "ALREADY_LOCKED"); - unlockers[id] = unlocker; - _approve(unlocker, id); - } - - /** - * @dev Public function to unlock the token. Only the unlocker (stated at the time of locking) can unlock - */ - function unlock(uint256 id) public virtual { - require(msg.sender == unlockers[id], "NOT_UNLOCKER"); - unlockers[id] = address(0); - } - - /** - * @dev Returns the unlocker for the tokenId - * address(0) means token is not locked - * reverts if token does not exist - */ - function getLocked(uint256 tokenId) public virtual view returns (address) { - require(_exists(tokenId), "Lockable: locking query for nonexistent token"); - return unlockers[tokenId]; - } - - /** - * @dev Locks the token - */ - function _lock(address unlocker, uint256 id) internal virtual { - unlockers[id] = unlocker; - } - - /** - * @dev Unlocks the token - */ - function _unlock(uint256 id) internal virtual { - unlockers[id] = address(0); - } - - /*/////////////////////////////////////////////////////////////// - OVERRIDES - //////////////////////////////////////////////////////////////*/ - - function approve(address to, uint256 tokenId) public virtual override { - require (getLocked(tokenId) == address(0), "Can not approve locked token"); - super.approve(to, tokenId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - // if it is a Transfer or Burn - if (from != address(0)) { - // token should not be locked or msg.sender should be unlocker to do that - require(getLocked(tokenId) == address(0) || msg.sender == getLocked(tokenId), "LOCKED"); - } - } - - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - // if it is a Transfer or Burn, we always deal with one token, that is startTokenId - if (from != address(0)) { - // clear locks - delete unlockers[tokenId]; - } - } - - /** - * @dev Optional override, if to clear approvals while the tken is locked - */ - function getApproved(uint256 tokenId) public view virtual override returns (address) { - if (getLocked(tokenId) != address(0)) { - return address(0); - } - return super.getApproved(tokenId); - } - - /*/////////////////////////////////////////////////////////////// - ERC165 LOGIC - //////////////////////////////////////////////////////////////*/ - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC721Lockable).interfaceId || - super.supportsInterface(interfaceId); - } - -} -``` - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard for the contract that manages [EIP-721](./eip-721.md) tokens. - -### Considerations for the contracts that work with lockable tokens - -- Make sure that every contract that is stated as `unlocker` can actually unlock the token in all cases. -- There are use cases, that involve transferring the token to a temporary owner and then lock it. For example, NFT rentals. Smart contracts that manage such services should always use `transferFrom` instead of `safeTransferFrom` to avoid re-entrancies. -- There are no MEV considerations regarding lockable tokens as only authorized parties are allowed to lock and unlock. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md) +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5753.md diff --git a/EIPS/eip-5773.md b/EIPS/eip-5773.md index ce5417220b8a0d..38bbf248970e0c 100644 --- a/EIPS/eip-5773.md +++ b/EIPS/eip-5773.md @@ -1,461 +1 @@ ---- -eip: 5773 -title: Context-Dependent Multi-Asset Tokens -description: An interface for Multi-Asset tokens with context dependent asset type output controlled by owner's preference. -author: Bruno Škvorc (@Swader), Cicada (@CicadaNCR), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/multiresource-tokens/11326 -status: Final -type: Standards Track -category: ERC -created: 2022-10-10 -requires: 165, 721 ---- - -## Abstract - -The Multi-Asset NFT standard allows for the construction of a new primitive: context-dependent output of information per single NFT. - -The context-dependent output of information means that the asset in an appropriate format is displayed based on how the token is being accessed. I.e. if the token is being opened in an e-book reader, the PDF asset is displayed, if the token is opened in the marketplace, the PNG or the SVG asset is displayed, if the token is accessed from within a game, the 3D model asset is accessed and if the token is accessed by the (Internet of Things) IoT hub, the asset providing the necessary addressing and specification information is accessed. - -An NFT can have multiple assets (outputs), which can be any kind of file to be served to the consumer, and orders them by priority. They do not have to match in mimetype or tokenURI, nor do they depend on one another. Assets are not standalone entities, but should be thought of as “namespaced tokenURIs” that can be ordered at will by the NFT owner, but only modified, updated, added, or removed if agreed on by both the owner of the token and the issuer of the token. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having multiple assets associated with a single NFT allows for greater utility, usability and forward compatibility. - -In the four years since [ERC-721](./eip-721.md) was published, the need for additional functionality has resulted in countless extensions. This EIP improves upon ERC-721 in the following areas: - -- [Cross-metaverse compatibility](#cross-metaverse-compatibility) -- [Multi-media output](#multi-media-output) -- [Media redundancy](#media-redundancy) -- [NFT evolution](#nft-evolution) - -### Cross-metaverse compatibility - -At the time of writing this proposal, the metaverse is still a fledgling, not full defined, term. No matter how the definition of metaverse evolves, the proposal can support any number of different implementations. - -Cross-metaverse compatibility could also be referred to as cross-engine compatibility. An example of this is where a cosmetic item for game A is not available in game B because the frameworks are incompatible. - -Such NFT can be given further utility by means of new additional assets: more games, more cosmetic items, appended to the same NFT. Thus, a game cosmetic item as an NFT becomes an ever-evolving NFT of infinite utility. - -The following is a more concrete example. One asset is a cosmetic item for game A, a file containing the cosmetic assets. Another is a cosmetic asset file for game B. A third is a generic asset intended to be shown in catalogs, marketplaces, portfolio trackers, or other generalized NFT viewers, containing a representation, stylized thumbnail, and animated demo/trailer of the cosmetic item. - -This EIP adds a layer of abstraction, allowing game developers to directly pull asset data from a user's NFTs instead of hard-coding it. - -### Multi-media output - -An NFT of an eBook can be represented as a PDF, MP3, or some other format, depending on what software loads it. If loaded into an eBook reader, a PDF should be displayed, and if loaded into an audiobook application, the MP3 representation should be used. Other metadata could be present in the NFT (perhaps the book's cover image) for identification on various marketplaces, Search Engine Result Pages (SERPs), or portfolio trackers. - -### Media redundancy - -Many NFTs are minted hastily without best practices in mind - specifically, many NFTs are minted with metadata centralized on a server somewhere or, in some cases, a hardcoded IPFS gateway which can also go down, instead of just an IPFS hash. - -By adding the same metadata file as different assets, e.g., one asset of a metadata and its linked image on Arweave, one asset of this same combination on Sia, another of the same combination on IPFS, etc., the resilience of the metadata and its referenced information increases exponentially as the chances of all the protocols going down at once become less likely. - -### NFT evolution - -Many NFTs, particularly game related ones, require evolution. This is especially the case in modern metaverses where no metaverse is actually a metaverse - it is just a multiplayer game hosted on someone's server which replaces username/password logins with reading an account's NFT balance. - -When the server goes down or the game shuts down, the player ends up with nothing (loss of experience) or something unrelated (assets or accessories unrelated to the game experience, spamming the wallet, incompatible with other “verses” - see [cross-metaverse](#cross-metaverse-compatibility) compatibility above). - -With Multi-Asset NFTs, a minter or another pre-approved entity is allowed to suggest a new asset to the NFT owner who can then accept it or reject it. The asset can even target an existing asset which is to be replaced. - -Replacing an asset could, to some extent, be similar to replacing an ERC-721 token's URI. When an asset is replaced a clear line of traceability remains; the old asset is still reachable and verifiable. Replacing an asset's metadata URI obscures this lineage. It also gives more trust to the token owner if the issuer cannot replace the asset of the NFT at will. The propose-accept asset replacement mechanic of this proposal provides this assurance. - -This allows level-up mechanics where, once enough experience has been collected, a user can accept the level-up. The level-up consists of a new asset being added to the NFT, and once accepted, this new asset replaces the old one. - -As a concrete example, think of Pokemon™️ evolving - once enough experience has been attained, a trainer can choose to evolve their monster. With Multi-Asset NFTs, it is not necessary to have centralized control over metadata to replace it, nor is it necessary to airdrop another NFT into the user's wallet - instead, a new Raichu asset is minted onto Pikachu, and if accepted, the Pikachu asset is gone, replaced by Raichu, which now has its own attributes, values, etc. - -Alternative example of this, could be version control of an IoT device's firmware. An asset could represent its current firmware and once an update becomes available, the current asset could be replaced with the one containing the updated firmware. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title ERC-5773 Context-Dependent Multi-Asset Tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-5773 -/// @dev Note: the ERC-165 identifier for this interface is 0x06b4329a. - -pragma solidity ^0.8.16; - -interface IERC5773 /* is ERC165 */ { - /** - * @notice Used to notify listeners that an asset object is initialised at `assetId`. - * @param assetId ID of the asset that was initialised - */ - event AssetSet(uint64 assetId); - - /** - * @notice Used to notify listeners that an asset object at `assetId` is added to token's pending asset - * array. - * @param tokenIds An array of IDs of the tokens that received a new pending asset - * @param assetId ID of the asset that has been added to the token's pending assets array - * @param replacesId ID of the asset that would be replaced - */ - event AssetAddedToTokens( - uint256[] tokenIds, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - /** - * @notice Used to notify listeners that an asset object at `assetId` is accepted by the token and migrated - * from token's pending assets array to active assets array of the token. - * @param tokenId ID of the token that had a new asset accepted - * @param assetId ID of the asset that was accepted - * @param replacesId ID of the asset that was replaced - */ - event AssetAccepted( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - /** - * @notice Used to notify listeners that an asset object at `assetId` is rejected from token and is dropped - * from the pending assets array of the token. - * @param tokenId ID of the token that had an asset rejected - * @param assetId ID of the asset that was rejected - */ - event AssetRejected(uint256 indexed tokenId, uint64 indexed assetId); - - /** - * @notice Used to notify listeners that token's priority array is reordered. - * @param tokenId ID of the token that had the asset priority array updated - */ - event AssetPrioritySet(uint256 indexed tokenId); - - /** - * @notice Used to notify listeners that owner has granted an approval to the user to manage the assets of a - * given token. - * @dev Approvals must be cleared on transfer - * @param owner Address of the account that has granted the approval for all token's assets - * @param approved Address of the account that has been granted approval to manage the token's assets - * @param tokenId ID of the token on which the approval was granted - */ - event ApprovalForAssets( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - /** - * @notice Used to notify listeners that owner has granted approval to the user to manage assets of all of their - * tokens. - * @param owner Address of the account that has granted the approval for all assets on all of their tokens - * @param operator Address of the account that has been granted the approval to manage the token's assets on all of the - * tokens - * @param approved Boolean value signifying whether the permission has been granted (`true`) or revoked (`false`) - */ - event ApprovalForAllForAssets( - address indexed owner, - address indexed operator, - bool approved - ); - - /** - * @notice Accepts an asset at from the pending array of given token. - * @dev Migrates the asset from the token's pending asset array to the token's active asset array. - * @dev Active assets cannot be removed by anyone, but can be replaced by a new asset. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - `index` must be in range of the length of the pending asset array. - * @dev Emits an {AssetAccepted} event. - * @param tokenId ID of the token for which to accept the pending asset - * @param index Index of the asset in the pending array to accept - * @param assetId Id of the asset expected to be in the index - */ - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - /** - * @notice Rejects an asset from the pending array of given token. - * @dev Removes the asset from the token's pending asset array. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - `index` must be in range of the length of the pending asset array. - * @dev Emits a {AssetRejected} event. - * @param tokenId ID of the token that the asset is being rejected from - * @param index Index of the asset in the pending array to be rejected - * @param assetId Id of the asset expected to be in the index - */ - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - /** - * @notice Rejects all assets from the pending array of a given token. - * @dev Effectively deletes the pending array. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * @dev Emits a {AssetRejected} event with assetId = 0. - * @param tokenId ID of the token of which to clear the pending array - * @param maxRejections to prevent from rejecting assets which arrive just before this operation. - */ - function rejectAllAssets(uint256 tokenId, uint256 maxRejections) external; - - /** - * @notice Sets a new priority array for a given token. - * @dev The priority array is a non-sequential list of `uint16`s, where the lowest value is considered highest - * priority. - * @dev Value `0` of a priority is a special case equivalent to uninitialised. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - The length of `priorities` must be equal the length of the active assets array. - * @dev Emits a {AssetPrioritySet} event. - * @param tokenId ID of the token to set the priorities for - * @param priorities An array of priorities of active assets. The succession of items in the priorities array - * matches that of the succession of items in the active array - */ - function setPriority(uint256 tokenId, uint64[] calldata priorities) - external; - - /** - * @notice Used to retrieve IDs of the active assets of given token. - * @dev Asset data is stored by reference, in order to access the data corresponding to the ID, call - * `getAssetMetadata(tokenId, assetId)`. - * @dev You can safely get 10k - * @param tokenId ID of the token to retrieve the IDs of the active assets - * @return uint64[] An array of active asset IDs of the given token - */ - function getActiveAssets(uint256 tokenId) - external - view - returns (uint64[] memory); - - /** - * @notice Used to retrieve IDs of the pending assets of given token. - * @dev Asset data is stored by reference, in order to access the data corresponding to the ID, call - * `getAssetMetadata(tokenId, assetId)`. - * @param tokenId ID of the token to retrieve the IDs of the pending assets - * @return uint64[] An array of pending asset IDs of the given token - */ - function getPendingAssets(uint256 tokenId) - external - view - returns (uint64[] memory); - - /** - * @notice Used to retrieve the priorities of the active assets of a given token. - * @dev Asset priorities are a non-sequential array of uint16 values with an array size equal to active asset - * priorites. - * @param tokenId ID of the token for which to retrieve the priorities of the active assets - * @return uint16[] An array of priorities of the active assets of the given token - */ - function getActiveAssetPriorities(uint256 tokenId) - external - view - returns (uint64[] memory); - - /** - * @notice Used to retrieve the asset that will be replaced if a given asset from the token's pending array - * is accepted. - * @dev Asset data is stored by reference, in order to access the data corresponding to the ID, call - * `getAssetMetadata(tokenId, assetId)`. - * @param tokenId ID of the token to check - * @param newAssetId ID of the pending asset which will be accepted - * @return uint64 ID of the asset which will be replaced - */ - function getAssetReplacements(uint256 tokenId, uint64 newAssetId) - external - view - returns (uint64); - - /** - * @notice Used to fetch the asset metadata of the specified token's active asset with the given index. - * @dev Can be overridden to implement enumerate, fallback or other custom logic. - * @param tokenId ID of the token from which to retrieve the asset metadata - * @param assetId Asset Id, must be in the active assets array - * @return string The metadata of the asset belonging to the specified index in the token's active assets - * array - */ - function getAssetMetadata(uint256 tokenId, uint64 assetId) - external - view - returns (string memory); - - /** - * @notice Used to grant permission to the user to manage token's assets. - * @dev This differs from transfer approvals, as approvals are not cleared when the approved party accepts or - * rejects an asset, or sets asset priorities. This approval is cleared on token transfer. - * @dev Only a single account can be approved at a time, so approving the `0x0` address clears previous approvals. - * @dev Requirements: - * - * - The caller must own the token or be an approved operator. - * - `tokenId` must exist. - * @dev Emits an {ApprovalForAssets} event. - * @param to Address of the account to grant the approval to - * @param tokenId ID of the token for which the approval to manage the assets is granted - */ - function approveForAssets(address to, uint256 tokenId) external; - - /** - * @notice Used to retrieve the address of the account approved to manage assets of a given token. - * @dev Requirements: - * - * - `tokenId` must exist. - * @param tokenId ID of the token for which to retrieve the approved address - * @return address Address of the account that is approved to manage the specified token's assets - */ - function getApprovedForAssets(uint256 tokenId) - external - view - returns (address); - - /** - * @notice Used to add or remove an operator of assets for the caller. - * @dev Operators can call {acceptAsset}, {rejectAsset}, {rejectAllAssets} or {setPriority} for any token - * owned by the caller. - * @dev Requirements: - * - * - The `operator` cannot be the caller. - * @dev Emits an {ApprovalForAllForAssets} event. - * @param operator Address of the account to which the operator role is granted or revoked from - * @param approved The boolean value indicating whether the operator role is being granted (`true`) or revoked - * (`false`) - */ - function setApprovalForAllForAssets(address operator, bool approved) - external; - - /** - * @notice Used to check whether the address has been granted the operator role by a given address or not. - * @dev See {setApprovalForAllForAssets}. - * @param owner Address of the account that we are checking for whether it has granted the operator role - * @param operator Address of the account that we are checking whether it has the operator role or not - * @return bool The boolean value indicating whether the account we are checking has been granted the operator role - */ - function isApprovedForAllForAssets(address owner, address operator) - external - view - returns (bool); -} -``` - -The `getAssetMetadata` function returns the asset's metadata URI. The metadata, to which the metadata URI of the asset points, MAY contain a JSON response with the following fields: - -```json -{ - "name": "Asset Name", - "description": "The description of the token or asset", - "mediaUri": "ipfs://mediaOfTheAssetOrToken", - "thumbnailUri": "ipfs://thumbnailOfTheAssetOrToken", - "externalUri": "https://uriToTheProjectWebsite", - "license": "License name", - "licenseUri": "https://uriToTheLicense", - "tags": ["tags", "used", "to", "help", "marketplaces", "categorize", "the", "asset", "or", "token"], - "preferThumb": false, // A boolean flag indicating to UIs to prefer thumbnailUri instead of mediaUri wherever applicable - "attributes": [ - { - "label": "rarity", - "type": "string", - "value": "epic", - // For backward compatibility - "trait_type": "rarity" - }, - { - "label": "color", - "type": "string", - "value": "red", - // For backward compatibility - "trait_type": "color" - }, - { - "label": "height", - "type": "float", - "value": 192.4, - // For backward compatibility - "trait_type": "height", - "display_type": "number" - } - ] -} -``` - -While this is the suggested JSON schema for the asset metadata, it is not enforced and MAY be structured completely differently based on implementer's preference. - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **Should we use Asset or Resource when referring to the structure that comprises the token?**\ -The original idea was to call the proposal Multi-Resource, but while this denoted the broadness of the structures that could be held by a single token, the term *asset* represents it better.\ -An asset is defined as something that is owned by a person, company, or organization, such as money, property, or land. This is the best representation of what an asset of this proposal can be. An asset in this proposal can be a multimedia file, technical information, a land deed, or anything that the implementer has decided to be an asset of the token they are implementing. -2. **Why are [EIP-712](./eip-712.md) permit-style signatures to manage approvals not used?**\ -For consistency. This proposal extends ERC-721 which already uses 1 transaction for approving operations with tokens. It would be inconsistent to have this and also support signing messages for operations with assets. -3. **Why use indexes?**\ -To reduce the gas consumption. If the asset ID was used to find which asset to accept or reject, iteration over arrays would be required and the cost of the operation would depend on the size of the active or pending assets arrays. With the index, the cost is fixed. A list of active and pending assets arrays per token need to be maintained, since methods to get them are part of the proposed interface.\ -To avoid race conditions in which the index of an asset changes, the expected asset ID is included in operations requiring asset index, to verify that the asset being accessed using the index is the expected asset.\ -Implementation that would internally keep track of indices using mapping was attempted. The average cost of adding an asset to a token increased by over 25%, costs of accepting and rejecting assets also increased 4.6% and 7.1% respectively. We concluded that it is not necessary for this proposal and can be implemented as an extension for use cases willing to accept this cost. In the sample implementation provided, there are several hooks which make this possible. -4. **Why is a method to get all the assets not included?**\ -Getting all assets might not be an operation necessary for all implementers. Additionally, it can be added either as an extension, doable with hooks, or can be emulated using an indexer. -5. **Why is pagination not included?**\ -Asset IDs use `uint64`, testing has confirmed that the limit of IDs you can read before reaching the gas limit is around 30.000. This is not expected to be a common use case so it is not a part of the interface. However, an implementer can create an extension for this use case if needed. -6. **How does this proposal differ from the other proposals trying to address a similar problem?**\ -After reviewing them, we concluded that each contains at least one of these limitations: - - Using a single URI which is replaced as new assets are needed, this introduces a trust issue for the token owner. - - Focusing only on a type of asset, while this proposal is asset type agnostic. - - Having a different token for each new use case, this means that the token is not forward-compatible. - -### Multi-Asset Storage Schema - -Assets are stored within a token as an array of `uint64` identifiers. - -In order to reduce redundant on-chain string storage, multi asset tokens store assets by reference via inner storage. An asset entry on the storage is stored via a `uint64` mapping to asset data. - -An asset array is an array of these `uint64` asset ID references. - -Such a structure allows that, a generic asset can be added to the storage one time, and a reference to it can be added to the token contract as many times as we desire. Implementers can then use string concatenation to procedurally generate a link to a content-addressed archive based on the base *SRC* in the asset and the *token ID*. Storing the asset in a new token will only take 16 bytes of storage in the asset array per token for recurrent as well as `tokenId` dependent assets. - -Structuring token's assets in such a way allows for URIs to be derived programmatically through concatenation, especially when they differ only by `tokenId`. - -### Propose-Commit pattern for asset addition - -Adding assets to an existing token MUST be done in the form of a propose-commit pattern to allow for limited mutability by a 3rd party. When adding an asset to a token, it is first placed in the *"Pending"* array, and MUST be migrated to the *"Active"* array by the token's owner. The *"Pending"* assets array SHOULD be limited to 128 slots to prevent spam and griefing. - -### Asset management - -Several functions for asset management are included. In addition to permissioned migration from "Pending" to "Active", the owner of a token MAY also drop assets from both the active and the pending array -- an emergency function to clear all entries from the pending array MUST also be included. - -## Backwards Compatibility - -The MultiAsset token standard has been made compatible with [ERC-721](./eip-721.md) in order to take advantage of the robust tooling available for implementations of ERC-721 and to ensure compatibility with existing ERC-721 infrastructure. - -## Test Cases - -Tests are included in [`multiasset.ts`](../assets/eip-5773/test/multiasset.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-5773 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`MultiAssetToken.sol`](../assets/eip-5773/contracts/MultiAssetToken.sol). - -## Security Considerations - -The same security considerations as with [ERC-721](./eip-721.md) apply: hidden logic may be present in any of the functions, including burn, add asset, accept asset, and more. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5773.md diff --git a/EIPS/eip-5791.md b/EIPS/eip-5791.md index 34e06f58fb5fde..fe7a7ec69c1a1b 100644 --- a/EIPS/eip-5791.md +++ b/EIPS/eip-5791.md @@ -1,165 +1 @@ ---- -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) -discussions-to: https://ethereum-magicians.org/t/physical-backed-tokens/11350 -status: Draft -type: Standards Track -category: ERC -created: 2022-10-17 -requires: 191, 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It proposes a minimal interface for a [ERC-721](./eip-721.md) NFT to be "physically backed" and owned by whoever owns the NFT's physical counterpart. - -## Motivation - -NFT collectors enjoy collecting digital assets and sharing them with others online. However, there is currently no such standard for showcasing physical assets as NFTs with verified authenticity and ownership. Existing solutions are fragmented and tend to be susceptible to at least one of the following: - -- The ownership of the physical item and the ownership of the NFT are decoupled. - -- Verifying the authenticity of the physical item requires action from a trusted 3rd party (e.g. StockX). - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Requirements - -This approach requires that the physical item must have a chip attached to it that fulfills the following requirements: - -- The chip can securely generate and store an ECDSA secp256k1 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 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. - -### Approach - -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. - -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 also includes other functions that let anyone validate whether the chip in the physical item is backing an existing NFT in the collection. - -### Interface - -```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) - 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); -} - -``` - -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`). - -## 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. - -### 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 -- 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. - -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. - -## Backwards Compatibility - -This proposal is backward compatible with [ERC-721](./eip-721.md) on an API level. As mentioned above, for the token to be physical-backed, the contract must use a account-bound implementation of [ERC-721](./eip-721.md) (all [ERC-721](./eip-721.md) functions that transfer must throw) so that transfers go through the new function introduced here, which requires a chip signature. - -## Reference Implementation - -The following is a snippet on how to recover a chip address from a signature. - -```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(); - } - } - bytes32 blockHash = blockhash(blockNumberUsedInSig); - bytes32 signedHash = keccak256(abi.encodePacked(_msgSender(), blockHash)) - .toEthSignedMessageHash(); - address chipAddr = signedHash.recover(signatureFromChip); -} - -``` - -## 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). - -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. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5791.md diff --git a/EIPS/eip-5805.md b/EIPS/eip-5805.md index eb3eac56a75aac..40f7360db01726 100644 --- a/EIPS/eip-5805.md +++ b/EIPS/eip-5805.md @@ -1,402 +1 @@ ---- -eip: 5805 -title: Voting with delegation -description: An interface for voting weight tracking, with delegation support -author: Hadrien Croubois (@Amxx), Francisco Giordano (@frangio) -discussions-to: https://ethereum-magicians.org/t/eip-5805-voting-with-delegation/11407 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-07-04 -requires: 712, 6372 ---- - -## Abstract - -Many DAOs (decentralized autonomous organizations) rely on tokens to represent one's voting power. In order to perform this task effectively, the token contracts need to include specific mechanisms such as checkpoints and delegation. The existing implementations are not standardized. This ERC proposes to standardize the way votes are delegated from one account to another, and the way current and past votes are tracked and queried. The corresponding behavior is compatible with many token types, including but not limited to [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md). This ERC also considers the diversity of time tracking functions, allowing the voting tokens (and any contract associated with it) to track the votes based on `block.number`, `block.timestamp`, or any other non-decreasing function. - -## Motivation - -Beyond simple monetary transactions, decentralized autonomous organizations are arguably one of the most important use cases of blockchain and smart contract technologies. Today, many communities are organized around a governance contract that allows users to vote. Among these communities, some represent voting power using transferable tokens ([ERC-20](./eip-20.md), [ERC-721](./eip-721.md), other). In this context, the more tokens one owns, the more voting power one has. Governor contracts, such as Compound's `GovernorBravo`, read from these "voting token" contracts to get the voting power of the users. - -Unfortunately, simply using the `balanceOf(address)` function present in most token standards is not good enough: - -- The values are not checkpointed, so a user can vote, transfer its tokens to a new account, and vote again with the same tokens. -- A user cannot delegate their voting power to someone else without transferring full ownership of the tokens. - -These constraints have led to the emergence of voting tokens with delegation that contain the following logic: - -- Users can delegate the voting power of their tokens to themselves or a third party. This creates a distinction between balance and voting weight. -- The voting weights of accounts are checkpointed, allowing lookups for past values at different points in time. -- The balances are not checkpointed. - -This ERC is proposing to standardize the interface and behavior of these voting tokens. - -Additionally, the existing (non-standardized) implementations are limited to `block.number` based checkpoints. This choice causes many issues in a multichain environment, where some chains (particularly L2s) have an inconsistent or unpredictable time between blocks. This ERC also addresses this issue by allowing the voting token to use any time tracking function it wants, and exposing it so that other contracts (such as a Governor) can stay consistent with the token checkpoints. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Following pre-existing (but not-standardized) implementation, the EIP proposes the following mechanism. - -Each user account (address) can delegate to an account of its choice. This can be itself, someone else, or no one (represented by `address(0)`). Assets held by the user cannot express their voting power unless they are delegated. - -When a "delegator" delegates its tokens voting power to a "delegatee", its balance is added to the voting power of the delegatee. If the delegator changes its delegation, the voting power is subtracted from the old delegatee's voting power and added to the new delegate's voting power. The voting power of each account is tracked through time so that it is possible to query its value in the past. With tokens being delegated to at most one delegate at a given point in time, double voting is prevented. - -Whenever tokens are transferred from one account to another, the associated voting power should be deducted from the sender's delegate and added to the receiver's delegate. - -Tokens that are delegated to `address(0)` should not be tracked. This allows users to optimize the gas cost of their token transfers by skipping the checkpoint update for their delegate. - -To accommodate different types of chains, we want the voting checkpoint system to support different forms of time tracking. On the Ethereum mainnet, using block numbers provides backward compatibility with applications that historically use it. On the other hand, using timestamps provides better semantics for end users, and accommodates use cases where the duration is expressed in seconds. Other monotonic functions could also be deemed relevant by developers based on the characteristics of future applications and blockchains. - -Both timestamps, block numbers, and other possible modes use the same external interfaces. This allows transparent binding of third-party contracts, such as governor systems, to the vote tracking built into the voting contracts. For this to be effective, the voting contracts must, in addition to all the vote-tracking functions, expose the current value used for time-tracking. - -### Methods - -#### [ERC-6372](./eip-6372.md): clock and CLOCK_MODE - -Compliant contracts SHOULD implement ERC-6372 (Contract clock) to announce the clock that is used for vote tracking. - -If the contract does not implement ERC-6372, it MUST operate according to a block number clock, exactly as if ERC-6372's `CLOCK_MODE` returned `mode=blocknumber&from=default`. - -In the following specification, "the current clock" refers to either the result of ERC-6372's `clock()`, or the default of `block.number` in its absence. - -#### getVotes - -This function returns the current voting weight of an account. This corresponds to all the voting power delegated to it at the moment this function is called. - -As tokens delegated to `address(0)` should not be counted/snapshotted, `getVotes(0)` SHOULD always return `0`. - -This function MUST be implemented - -```yaml -- name: getVotes - type: function - stateMutability: view - inputs: - - name: account - type: address - outputs: - - name: votingWeight - type: uint256 -``` - -#### getPastVotes - -This function returns the historical voting weight of an account. This corresponds to all the voting power delegated to it at a specific timepoint. The timepoint parameter MUST match the operating mode of the contract. This function SHOULD only serve past checkpoints, which SHOULD be immutable. - -- Calling this function with a timepoint that is greater or equal to the current clock SHOULD revert. -- Calling this function with a timepoint strictly smaller than the current clock SHOULD NOT revert. -- For any integer that is strictly smaller than the current clock, the value returned by `getPastVotes` SHOULD be constant. This means that for any call to this function that returns a value, re-executing the same call (at any time in the future) SHOULD return the same value. - -As tokens delegated to `address(0)` should not be counted/snapshotted, `getPastVotes(0,x)` SHOULD always return `0` (for all values of `x`). - -This function MUST be implemented - -```yaml -- name: getPastVotes - type: function - stateMutability: view - inputs: - - name: account - type: address - - name: timepoint - type: uint256 - outputs: - - name: votingWeight - type: uint256 -``` - -#### delegates - -This function returns the address to which the voting power of an account is currently delegated. - -Note that if the delegate is `address(0)` then the voting power SHOULD NOT be checkpointed, and it should not be possible to vote with it. - -This function MUST be implemented - -```yaml -- name: delegates - type: function - stateMutability: view - inputs: - - name: account - type: address - outputs: - - name: delegatee - type: address -``` - -#### delegate - -This function changes the caller's delegate, updating the vote delegation in the meantime. - -This function MUST be implemented - -```yaml -- name: delegate - type: function - stateMutability: nonpayable - inputs: - - name: delegatee - type: address - outputs: [] -``` - -#### delegateBySig - -This function changes an account's delegate using a signature, updating the vote delegation in the meantime. - -This function MUST be implemented - -```yaml -- name: delegateBySig - type: function - stateMutability: nonpayable - inputs: - - name: delegatee - type: address - - name: nonce - type: uint256 - - name: expiry - type: uint256 - - name: v - type: uint8 - - name: r - type: bytes32 - - name: s - type: bytes32 - outputs: [] -``` - -This signature should follow the [EIP-712](./eip-712.md) format: - -A call to `delegateBySig(delegatee, nonce, expiry, v, r, s)` changes the signer's delegate to `delegatee`, increment the signer's nonce by 1, and emits a corresponding `DelegateChanged` event, and possibly `DelegateVotesChanged` events for the old and the new delegate accounts, if and only if the following conditions are met: - - -- The current timestamp is less than or equal to `expiry`. -- `nonces(signer)` (before the state update) is equal to `nonce`. - -If any of these conditions are not met, the `delegateBySig` call must revert. This translates to the following solidity code: - -```sol -require(expiry <= block.timestamp) -bytes signer = ecrecover( - keccak256(abi.encodePacked( - hex"1901", - DOMAIN_SEPARATOR, - keccak256(abi.encode( - keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)"), - delegatee, - nonce, - expiry)), - v, r, s) -require(signer != address(0)); -require(nounces[signer] == nonce); -// increment nonce -// set delegation of `signer` to `delegatee` -``` - -where `DOMAIN_SEPARATOR` is defined according to [EIP-712](./eip-712.md). The `DOMAIN_SEPARATOR` should be unique to the contract and chain to prevent replay attacks from other domains, -and satisfy the requirements of EIP-712, but is otherwise unconstrained. - -A common choice for `DOMAIN_SEPARATOR` is: - -```solidity -DOMAIN_SEPARATOR = keccak256( - abi.encode( - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), - keccak256(bytes(name)), - keccak256(bytes(version)), - chainid, - address(this) -)); -``` - -In other words, the message is the EIP-712 typed structure: - -```js -{ - "types": { - "EIP712Domain": [ - { - "name": "name", - "type": "string" - }, - { - "name": "version", - "type": "string" - }, - { - "name": "chainId", - "type": "uint256" - }, - { - "name": "verifyingContract", - "type": "address" - } - ], - "Delegation": [{ - "name": "delegatee", - "type": "address" - }, - { - "name": "nonce", - "type": "uint256" - }, - { - "name": "expiry", - "type": "uint256" - } - ], - "primaryType": "Permit", - "domain": { - "name": contractName, - "version": version, - "chainId": chainid, - "verifyingContract": contractAddress - }, - "message": { - "delegatee": delegatee, - "nonce": nonce, - "expiry": expiry - } -}} -``` - -Note that nowhere in this definition do we refer to `msg.sender`. The caller of the `delegateBySig` function can be any address. - -When this function is successfully executed, the delegator's nonce MUST be incremented to prevent replay attacks. - -#### nonces - -This function returns the current nonce for a given account. - -Signed delegations (see `delegateBySig`) are only accepted if the nonce used in the EIP-712 signature matches the return of this function. This value of `nonce(delegator)` should be incremented whenever a call to `delegateBySig` is performed on behalf of `delegator`. - -This function MUST be implemented - -```yaml -- name: nonces - type: function - stateMutability: view - inputs: - - name: account - type: delegator - outputs: - - name: nonce - type: uint256 -``` - -### Events - -#### DelegateChanged - -`delegator` changes the delegation of its assets from `fromDelegate` to `toDelegate`. - -MUST be emitted when the delegate for an account is modified by `delegate(address)` or `delegateBySig(address,uint256,uint256,uint8,bytes32,bytes32)`. - -```yaml -- name: DelegateChanged - type: event - inputs: - - name: delegator - indexed: true - type: address - - name: fromDelegate - indexed: true - type: address - - name: toDelegate - indexed: true - type: address -``` - -#### DelegateVotesChanged - -`delegate` available voting power changes from `previousBalance` to `newBalance`. - -This MUST be emitted when: - -- an account (that holds more than 0 assets) updates its delegation from or to `delegate`, -- an asset transfer from or to an account that is delegated to `delegate`. - -```yaml -- name: DelegateVotesChanged - type: event - inputs: - - name: delegate - indexed: true - type: address - - name: previousBalance - indexed: false - type: uint256 - - name: newBalance - indexed: false - type: uint256 -``` - -### Solidity interface - -```sol -interface IERC5805 is IERC6372 /* (optional) */ { - event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); - event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance); - - function getVotes(address account) external view returns (uint256); - function getPastVotes(address account, uint256 timepoint) external view returns (uint256); - function delegates(address account) external view returns (address); - function nonces(address owner) public view virtual returns (uint256) - - function delegate(address delegatee) external; - function delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) external; -} -``` - -### Expected properties - -Let `clock` be the current clock. - -- For all timepoints `t < clock`, `getVotes(address(0))` and `getPastVotes(address(0), t)` SHOULD return 0. -- For all accounts `a != 0`, `getVotes(a)` SHOULD be the sum of the "balances" of all the accounts that delegate to `a`. -- For all accounts `a != 0` and all timestamp `t < clock`, `getPastVotes(a, t)` SHOULD be the sum of the "balances" of all the accounts that delegated to `a` when `clock` overtook `t`. -- For all accounts `a`, `getPastVotes(a, t)` MUST be constant after `t < clock` is reached. -- For all accounts `a`, the action of changing the delegate from `b` to `c` MUST not increase the current voting power of `b` (`getVotes(b)`) and MUST not decrease the current voting power of `c` (`getVotes(c)`). - -## Rationale - -Delegation allows token holders to trust a delegate with their vote while keeping full custody of their token. This means that only a small-ish number of delegates need to pay gas for voting. This leads to better representation of small token holders by allowing their votes to be cast without requiring them to pay expensive gas fees. Users can take over their voting power at any point, and delegate it to someone else, or to themselves. - -The use of checkpoints prevents double voting. Votes, for example in the context of a governance proposal, should rely on a snapshot defined by a timepoint. Only tokens delegated at that timepoint can be used for voting. This means any token transfer performed after the snapshot will not affect the voting power of the sender/receiver's delegate. This also means that in order to vote, someone must acquire tokens and delegate them before the snapshot is taken. Governors can, and do, include a delay between the proposal is submitted and the snapshot is taken so that users can take the necessary actions (change their delegation, buy more tokens, ...). - -While timestamps produced by ERC-6372's `clock` are represented as `uint48`, `getPastVotes`'s timepoint argument is `uint256` for backward compatibility. Any timepoint `>=2**48` passed to `getPastVotes` SHOULD cause the function to revert, as it would be a lookup in the future. - -`delegateBySig` is necessary to offer a gasless workflow to token holders that do not want to pay gas for voting. - -The `nonces` mapping is given for replay protection. - -EIP-712 typed messages are included because of their widespread adoption in many wallet providers. - -## Backwards Compatibility - -Compound and OpenZeppelin already provide implementations of voting tokens. The delegation-related methods are shared between the two implementations and this ERC. For the vote lookup, this ERC uses OpenZeppelin's implementation (with return type uint256) as Compound's implementation causes significant restrictions of the acceptable values (return type is uint96). - -Both implementations use `block.number` for their checkpoints and do not implement ERC-6372, which is compatible with this ERC. - -Existing governors, that are currently compatible with OpenZeppelin's implementation will be compatible with the "block number mode" of this ERC. - -## Security Considerations - -Before doing a lookup, one should check the return value of `clock()` and make sure that the parameters of the lookup are consistent. Performing a lookup using a timestamp argument on a contract that uses block numbers will very likely cause a revert. On the other end, performing a lookup using a block number argument on a contract that uses timestamps will likely return 0. - -Though the signer of a `Delegation` may have a certain party in mind to submit their transaction, another party can always front-run this transaction and call `delegateBySig` before the intended party. The result is the same for the `Delegation` signer, however. - -Since the ecrecover precompile fails silently and just returns the zero address as `signer` when given malformed messages, it is important to ensure `signer != address(0)` to avoid `delegateBySig` from delegating "zombie funds" belonging to the zero address. - -Signed `Delegation` messages are censorable. The relaying party can always choose to not submit the `Delegation` after having received it, withholding the option to submit it. The `expiry` parameter is one mitigation to this. If the signing party holds ETH they can also just submit the `Delegation` themselves, which can render previously signed `Delegation`s invalid. - -If the `DOMAIN_SEPARATOR` contains the `chainId` and is defined at contract deployment instead of reconstructed for every signature, there is a risk of possible replay attacks between chains in the event of a future chain split. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5805.md diff --git a/EIPS/eip-5827.md b/EIPS/eip-5827.md index 0467afefeeebff..5a3f79e444a702 100644 --- a/EIPS/eip-5827.md +++ b/EIPS/eip-5827.md @@ -1,242 +1 @@ ---- -eip: 5827 -title: Auto-renewable allowance extension -description: Extension to enable automatic renewals on allowance approvals -author: zlace (@zlace0x), zhongfu (@zhongfu), edison0xyz (@edison0xyz) -discussions-to: https://ethereum-magicians.org/t/eip-5827-auto-renewable-allowance-extension/10392 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-10-22 -requires: 20, 165 ---- - -## Abstract - -This extension adds a renewable allowance mechanism to [ERC-20](./eip-20.md) allowances, in which a `recoveryRate` defines the amount of token per second that the allowance regains towards the initial maximum approval `amount`. - -## Motivation - -Currently, ERC-20 tokens support allowances, with which token owners can allow a spender to spend a certain amount of tokens on their behalf. However, this is not ideal in circumstances involving recurring payments (e.g. subscriptions, salaries, recurring direct-cost-averaging purchases). - -Many existing DApps circumvent this limitation by requesting that users grant a large or unlimited allowance. This presents a security risk as malicious DApps can drain users' accounts up to the allowance granted, and users may not be aware of the implications of granting allowances. - -An auto-renewable allowance enables many traditional financial concepts like credit and debit limits. An account owner can specify a spending limit, and limit the amount charged to the account based on an allowance that recovers over time. - - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -```solidity -pragma solidity ^0.8.0; - -interface IERC5827 /* is ERC20, ERC165 */ { - /* - * Note: the ERC-165 identifier for this interface is 0x93cd7af6. - * 0x93cd7af6 === - * bytes4(keccak256('approveRenewable(address,uint256,uint256)')) ^ - * bytes4(keccak256('renewableAllowance(address,address)')) ^ - * bytes4(keccak256('approve(address,uint256)') ^ - * bytes4(keccak256('transferFrom(address,address,uint256)') ^ - * bytes4(keccak256('allowance(address,address)') ^ - */ - - /** - * @notice Thrown when the available allowance is less than the transfer amount. - * @param available allowance available; 0 if unset - */ - error InsufficientRenewableAllowance(uint256 available); - - /** - * @notice Emitted when any allowance is set. - * @dev MUST be emitted even if a non-renewable allowance is set; if so, the - * @dev `_recoveryRate` MUST be 0. - * @param _owner owner of token - * @param _spender allowed spender of token - * @param _value initial and maximum allowance granted to spender - * @param _recoveryRate recovery amount per second - */ - event RenewableApproval( - address indexed _owner, - address indexed _spender, - uint256 _value, - uint256 _recoveryRate - ); - - /** - * @notice Grants an allowance of `_value` to `_spender` initially, which recovers over time - * @notice at a rate of `_recoveryRate` up to a limit of `_value`. - * @dev SHOULD cause `allowance(address _owner, address _spender)` to return `_value`, - * @dev SHOULD throw when `_recoveryRate` is larger than `_value`, and MUST emit a - * @dev `RenewableApproval` event. - * @param _spender allowed spender of token - * @param _value initial and maximum allowance granted to spender - * @param _recoveryRate recovery amount per second - */ - function approveRenewable( - address _spender, - uint256 _value, - uint256 _recoveryRate - ) external returns (bool success); - - /** - * @notice Returns approved max amount and recovery rate of allowance granted to `_spender` - * @notice by `_owner`. - * @dev `amount` MUST also be the initial approval amount when a non-renewable allowance - * @dev has been granted, e.g. with `approve(address _spender, uint256 _value)`. - * @param _owner owner of token - * @param _spender allowed spender of token - * @return amount initial and maximum allowance granted to spender - * @return recoveryRate recovery amount per second - */ - function renewableAllowance(address _owner, address _spender) - external - view - returns (uint256 amount, uint256 recoveryRate); - - /// Overridden ERC-20 functions - - /** - * @notice Grants a (non-increasing) allowance of _value to _spender and clears any existing - * @notice renewable allowance. - * @dev MUST clear set `_recoveryRate` to 0 on the corresponding renewable allowance, if - * @dev any. - * @param _spender allowed spender of token - * @param _value allowance granted to spender - */ - function approve(address _spender, uint256 _value) - external - returns (bool success); - - /** - * @notice Moves `amount` tokens from `from` to `to` using the caller's allowance. - * @dev When deducting `amount` from the caller's allowance, the allowance amount used - * @dev SHOULD include the amount recovered since the last transfer, but MUST NOT exceed - * @dev the maximum allowed amount returned by `renewableAllowance(address _owner, address - * @dev _spender)`. - * @dev SHOULD also throw `InsufficientRenewableAllowance` when the allowance is - * @dev insufficient. - * @param from token owner address - * @param to token recipient - * @param amount amount of token to transfer - */ - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool); - - /** - * @notice Returns amount currently spendable by `_spender`. - * @dev The amount returned MUST be as of `block.timestamp`, if a renewable allowance - * @dev for the `_owner` and `_spender` is present. - * @param _owner owner of token - * @param _spender allowed spender of token - * @return remaining allowance at the current point in time - */ - function allowance(address _owner, address _spender) - external - view - returns (uint256 remaining); -} -``` - -Base method `approve(address _spender, uint256 _value)` MUST set `recoveryRate` to 0. - -Both `allowance()` and `transferFrom()` MUST be updated to include allowance recovery logic. - -`approveRenewable(address _spender, uint256 _value, uint256 _recoveryRate)` MUST set both the initial allowance amount and the maximum allowance limit (to which the allowance can recover) to `_value`. - -`supportsInterface(0x93cd7af6)` MUST return `true`. - -### Additional interfaces - -**Token Proxy** - -Existing ERC-20 tokens can delegate allowance enforcement to a proxy contract that implements this specification. An additional query function exists to get the underlying ERC-20 token. - -```solidity -interface IERC5827Proxy /* is IERC5827 */ { - - /* - * Note: the ERC-165 identifier for this interface is 0xc55dae63. - * 0xc55dae63 === - * bytes4(keccak256('baseToken()') - */ - - /** - * @notice Get the underlying base token being proxied. - * @return baseToken address of the base token - */ - function baseToken() external view returns (address); -} -``` - -The `transfer()` function on the proxy MUST NOT emit the `Transfer` event (as the underlying token already does so). - -**Automatic Expiration** - -```solidity -interface IERC5827Expirable /* is IERC5827 */ { - /* - * Note: the ERC-165 identifier for this interface is 0x46c5b619. - * 0x46c5b619 === - * bytes4(keccak256('approveRenewable(address,uint256,uint256,uint64)')) ^ - * bytes4(keccak256('renewableAllowance(address,address)')) ^ - */ - - /** - * @notice Grants an allowance of `_value` to `_spender` initially, which recovers over time - * @notice at a rate of `_recoveryRate` up to a limit of `_value` and expires at - * @notice `_expiration`. - * @dev SHOULD throw when `_recoveryRate` is larger than `_value`, and MUST emit - * @dev `RenewableApproval` event. - * @param _spender allowed spender of token - * @param _value initial allowance granted to spender - * @param _recoveryRate recovery amount per second - * @param _expiration Unix time (in seconds) at which the allowance expires - */ - function approveRenewable( - address _spender, - uint256 _value, - uint256 _recoveryRate, - uint64 _expiration - ) external returns (bool success); - - /** - * @notice Returns approved max amount, recovery rate, and expiration timestamp. - * @return amount initial and maximum allowance granted to spender - * @return recoveryRate recovery amount per second - * @return expiration Unix time (in seconds) at which the allowance expires - */ - function renewableAllowance(address _owner, address _spender) - external - view - returns (uint256 amount, uint256 recoveryRate, uint64 expiration); -} -``` - -## Rationale - -Renewable allowances can be implemented with discrete resets per time cycle. However, a continuous `recoveryRate` allows for more flexible use cases not bound by reset cycles and can be implemented with simpler logic. - -## Backwards Compatibility - -Existing ERC-20 token contracts can delegate allowance enforcement to a proxy contract that implements this specification. - -## Reference Implementation - -An minimal implementation is included [here](../assets/eip-5827/ERC5827.sol) - -An audited, open source implemention of this standard as a `IERC5827Proxy` can be found at `https://github.com/suberra/funnel-contracts` - -## Security Considerations - -This EIP introduces a stricter set of constraints compared to ERC-20 with unlimited allowances. However, when `_recoveryRate` is set to a large value, large amounts can still be transferred over multiple transactions. - -Applications that are not [ERC-5827](./eip-5827.md)-aware may erroneously infer that the value returned by `allowance(address _owner, address _spender)` or included in `Approval` events is the maximum amount of tokens that `_spender` can spend from `_owner`. This may not be the case, such as when a renewable allowance is granted to `_spender` by `_owner`. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5827.md diff --git a/EIPS/eip-5850.md b/EIPS/eip-5850.md index 3c2ba83b0a83f0..6b5894060ade44 100644 --- a/EIPS/eip-5850.md +++ b/EIPS/eip-5850.md @@ -1,77 +1 @@ ---- -eip: 5850 -title: Complex Numbers stored in `bytes32` types -description: Store real and imaginary parts of complex numbers in the least significant and most significant 16 bytes respectively of a `bytes32` type. -author: Paul Edge (@genkifs) -discussions-to: https://ethereum-magicians.org/t/eip-5850-store-real-and-imaginary-parts-of-complex-numbers-in-the-least-significant-and-most-significant-16-bytes-respectively-of-a-bytes32-type/11532 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-10-29 ---- - -## Abstract - -This EIP proposes a natural way for complex numbers to be stored in and retrieved from the `bytes32` data-type. It splits the storage space exactly in half and, most importantly, assigns the real number part to the least significant 16 bytes and the imaginary number part to the most significant 16 bytes. - -## Motivation - -Complex numbers are an essential tool for many mathematical and scientific calculations. For example, Fourier Transforms, Characteristic functions, AC Circuits and Navier-Stokes equations all require the concept. - -Complex numbers can be represented in many different forms (polynomial, cartesian, polar, exponential). The EIP creates a standard that can accomodate cartesian, polar and exponential formats with example code given for the Cartesian representation, where a complex number is just the pair of real numbers which gives the real and imaginary co-ordinates of the complex number. Equal storage capacity is assigned to both components and the order they appear is explicitly defined. - -Packing complex numbers into a single `bytes32` data object halves storage costs and creates a more natural code object that can be passed around the solidity ecosystem. Existing code may not need to be rewritten for complex numbers. For example, mappings by `bytes32` are common and indexing in the 2D complex plane may improve code legibility. - -Decimal numbers, either fix or floating, are not yet fully supported by Solidity so enforcing similar standards for complex versions is premature. It can be suggested that fixed point methods such as prb-math be used with 18 decimal places, or floating point methods like abdk. However, it should be noted that this EIP supports any decimal number representation so long as it fits inside the 16 bytes space. - -## Specification - -A complex number would be defined as `bytes32` and a cartesian representation would be initalized with the `cnNew` function and converted back with `RealIm`, both given below. - -To create the complex number one would use - -```solidity -function cnNew(int128 _Real, int128 _Imag) public pure returns (bytes32){ - bytes32 Imag32 = bytes16(uint128(_Imag)); - bytes32 Real32 = bytes16(uint128(_Real)); - return (Real32>> 128) | Imag32; -} -``` - -and to convert back - -```solidity -function RealIm(bytes32 _cn) public pure returns (int128 Real, int128 Imag){ - bytes16[2] memory tmp = [bytes16(0), 0]; - assembly { - mstore(tmp, _cn) - mstore(add(tmp, 16), _cn) - } - Imag=int128(uint128(tmp[0])); - Real=int128(uint128(tmp[1])); -} -``` - -## Rationale - -An EIP is required as this proposal defines a complex numbers storage/type standard for multiple apps to use. - -This EIP proposes to package both the real and imaginary within one existing data type, `bytes32`. This allows compact storage without the need for structures and facilitates easy library implementations. The `bytes32` would remain available for existing, non-complex number uses. -Only the split and position of the real & imaginary parts is defined in this EIP. Manipulation of complex numbers (addition, multiplication etc.), number of decimal places and other such topics are left for other EIP discussions. This keeps this EIP more focused and therfore more likely to succeed. - -Defining real numbers in the 16 least-significant bytes allows direct conversion from `uint128` to `bytes32` for positive integers less than 2**127. -Direct conversion back from `bytes32` -> `uint` -> `int` are not recommended as the complex number may contain imaginary parts and/or the real part may be negative. It is better to always use `RealIm` for separating the complex part. - -Libraries for complex number manipulation can be implemented with the `Using Complex for bytes32` syntax where `Complex` would be the name of the library. - -## Backwards Compatibility - -There is no impact on other uses of the `bytes32` datatype. - -## Security Considerations - -If complex numbers are manipulated in `bytes32` form then overflow checks must be performed manually during the manipulation. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5850.md diff --git a/EIPS/eip-5851.md b/EIPS/eip-5851.md index a76020c925255d..635318a257457c 100644 --- a/EIPS/eip-5851.md +++ b/EIPS/eip-5851.md @@ -1,265 +1 @@ ---- -eip: 5851 -title: On-Chain Verifiable Credentials -description: Interface for contracts that manage verifiable claims and identifiers as Soulbound tokens. -author: Yu Liu (@yuliu-debond), Junyi Zhong (@Jooeys) -discussions-to: https://ethereum-magicians.org/t/eip-5815-kyc-certification-issuer-and-verifier-standard/11513 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-10-18 -requires: 721, 1155, 1167, 1967, 3475 ---- -## Abstract - -This proposal introduces a method of certifying that a particular address meets a claim, and a method of verifying those certifications using on-chain metadata. Claims are assertions or statements made about a subject having certain properties that may be met conditions (for example: `age >= 18`), and are certified by issuers using a Soundbound Token (SBT). - -## Motivation - -On-chain issuance of verifiable attestations are essential for use-case like: - -- Avoiding Sybil attacks with one person one vote -- Participation in certain events with credentials -- Compliance to government financial regulations etc. - -We are proposing a standard claims structure for Decentralized Identity (DID) issuers and verifier entities to create smart contracts in order to provide on-chain commitment of the off-chain verification process, and once the given address is associated with the given attestation of the identity verification off-chain, the issuers can then onboard other verifiers (i.e. governance, financial institution, non-profit organization, web3 related cooperation) to define the condition of the ownership of the user in order to reduce the technical barriers and overhead of current implementations. - -The motivation behind this proposal is to create a standard for verifier and issuer smart contracts to communicate with each other in a more efficient way. This will reduce the cost of KYC processes, and provide the possibility for on-chain KYC checks. By creating a standard for communication between verifiers and issuers, it will create an ecosystem in which users can be sure their data is secure and private. This will ultimately lead to more efficient KYC processes and help create a more trustful environment for users. It will also help to ensure that all verifier and issuer smart contracts are up-to-date with the most recent KYC regulations. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Definitions - -- Zero-Knowledge Proof (ZKP): a cryptographic device that can convince a verifier that an assertion is correct without revealing all of the inputs to the assertion. - -- Soulbound Token (SBT): A non-fungible and non-transferrable token that is used for defining the identity of the users. - -- SBT Certificate: An SBT that represents the ownership of ID signatures corresponding to the claims defined in `function standardClaim()`. - -- Verifiable Credential (VC): A collection of claims made by an issuer. These are temper evident credentials that allow the holders to prove that they posses certain characteristics (for example, passport verification, constraints like value of tokens in your wallet, etc) as demanded by the verifier entity. - -- Claim: An assertion that the DID Holder must fulfill to be verified. - -- Holder: The entity that stores the claim, such as a digital identity provider or a DID registry. The holder is responsible for validating the claim and providing verifiable evidence of the claim. - -- Claimer: The party making a claim, such as in an identity verification process. - -- Issuer: The entity that creates a verifiable credential from claims about one or more subjects to a holder. Example issuers include governments, corporations, non-profit organizations, trade associations, and individuals. - -- Verifier: An entity that validates data provided by an issuer of verifiable credentials, determining its accuracy, origin, currency and trustworthiness. - - - -### Metadata Standard - -Claims MUST be exposed in the following structures: - -#### 1. Metadata information - -Each claim requirement MUST be exposed using the following structure: - -```solidity - /** Metadata - * - * @param title defines the name of the claim field - * @param _type is the type of the data (bool,string,address,bytes,..) - * @param description additional information about claim details. - */ - struct Metadata { - string title; - string _type; - string description; - } -``` - -#### 2. Values Information - -This following structure will be used to define the actual claim information, based on the description of the `Metadata` structure, the structure is the same as `Values` structure of [EIP-3475](./eip-3475.md). - -```solidity - struct Values{ - string stringValue; - uint uintValue; - address addressValue; - bool boolValue; - } -``` - -#### 3. Claim structure - -Claims (eg. `age >= 18`, jurisdiction in allowlist, etc.) are represented by one or many instances of the `Claim` structure below: - -```solidity - /** Claims - * - * Claims structure consist of the conditions and value that holder claims to associate and verifier has to validate them. - * @notice the below given parameters are for reference purposes only, developers can optimize the fields that are needed to be represented on-chain by using schemes like TLV, encoding into base64 etc. - * @dev structure that defines the parameters for specific claims of the SBT certificate - * @notice this structure is used for the verification process, it contains the metadata, logic and expectation - * @notice logic can represent either the enum format for defining the different operations, or they can be logic operators (stored in form of ASCII figure based on unicode standard). like e.g: -("⊄" = U+2284, "⊂" = U+2282, "<" = U+003C , "<=" = U + 2265,"==" = U + 003D, "!="U + 2260, ">=" = U + 2265,">" = U + 2262). - */ - struct Claim { - Metadata metadata; - string logic; - Values expectation; - - } -``` - -description of some logic functions that can be used are as follows: - -| Symbol | Description | -|--------|--------------| -| ⊄ | does not belong to the set of values (or range) defined by the corresponding `Values` | -| ⊂ | condition that the parameter belongs to one of values defined by the `Values` | -| < | condition that the parameter is greater than value defined by the `Values` | -| == | condition that the parameter is strictly equal to the value defined by the `Values` structure | - -#### Claim Example - -```json -{ - "title":"age", - "type":"unit", - "description":"age of the person based on the birth date on the legal document", - "logic":">=", - "value":"18" -} -``` - -Defines the condition encoded for the index 1 (i.e the holder must be equal or more than 18 years old). - -### Interface specification - -#### Verifier - -```solidity - - /// @notice getter function to validate if the address `claimer` is the holder of the claim defined by the tokenId `SBTID` - /// @dev it MUST be defining the conditional operator (logic explained below) to allow the application to convert it into code logic - /// @dev logic given here MUST be the conditiaonl operator, MUST be one of ("⊄", "⊂", "<", "<=", "==", "!=", ">=", ">") - /// @param claimer is the EOA address that wants to validate the SBT issued to it by the issuer. - /// @param SBTID is the Id of the SBT that user is the claimer. - /// @return true if the assertion is valid, else false - /** - example ifVerified(0xfoo, 1) => true will mean that 0xfoo is the holder of the SBT identity token defined by tokenId of the given collection. - */ - function ifVerified(address claimer, uint256 SBTID) external view returns (bool); -``` - -#### Issuer - -```solidity - - /// @notice getter function to fetch the on-chain identification logic for the given identity holder. - /// @dev it MUST not be defined for address(0). - /// @param SBTID is the Id of the SBT that the user is the claimer. - /// @return the struct array of all the descriptions of condition metadata that is defined by the administrator for the given KYC provider. - /** - ex: standardClaim(1) --> { - { "title":"age", - "type": "uint", - "description": "age of the person based on the birth date on the legal document", - }, - "logic": ">=", - "value":"18" - } - Defines the condition encoded for the identity index 1, defining the identity condition that holder must be equal or more than 18 years old. - **/ - - function standardClaim(uint256 SBTID) external view returns (Claim[] memory); - - /// @notice function for setting the claim requirement logic (defined by Claims metadata) details for the given identity token defined by SBTID. - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims. - /// @param `claims` is the struct array of all the descriptions of condition metadata that is defined by the administrator. check metadata section for more information. - /** - example: changeStandardClaim(1, { "title":"age", - "type": "uint", - "description": "age of the person based on the birth date on the legal document", - }, - "logic": ">=", - "value":"18" - }); - will correspond to the functionality that admin needs to adjust the standard claim for the identification SBT with tokenId = 1, based on the conditions described in the Claims array struct details. - **/ - - function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) external returns (bool); - - /// @notice function which uses the ZKProof protocol to validate the identity based on the given - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which admin wants to define the Claims. - /// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID. - /** - example: certify(0xA....., 10) means that admin assigns the DID badge with id 10 to the address defined by the `0xA....` wallet. - */ - function certify(address claimer, uint256 SBTID) external returns (bool); - - /// @notice function which uses the ZKProof protocol to validate the identity based on the given - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims. - /// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID. - /* eg: revoke(0xfoo,1): means that KYC admin revokes the SBT certificate number 1 for the address '0xfoo'. */ - function revoke(address certifying, uint256 SBTID) external returns (bool); - -``` - -#### Events - -```solidity - /** - * standardChanged - * @notice standardChanged MUST be triggered when claims are changed by the admin. - * @dev standardChanged MUST also be triggered for the creation of a new SBTID. - e.g : emit StandardChanged(1, Claims(Metadata('age', 'uint', 'age of the person based on the birth date on the legal document' ), ">=", "18"); - is emitted when the Claim condition is changed which allows the certificate holder to call the functions with the modifier, claims that the holder must be equal or more than 18 years old. - */ - event StandardChanged(uint256 SBTID, Claim[] _claims); - - /** - * certified - * @notice certified MUST be triggered when the SBT certificate is given to the certifying address. - * eg: Certified(0xfoo,2); means that wallet holder address `0xfoo` is certified to hold a certificate issued with id 2, and thus can satisfy all the conditions defined by the required interface. - */ - event Certified(address claimer, uint256 SBTID); - - /** - * revoked - * @notice revoked MUST be triggered when the SBT certificate is revoked. - * eg: Revoked( 0xfoo,1); means that entity user 0xfoo has been revoked to all the function access defined by the SBT ID 1. - */ - event Revoked(address claimer, uint256 SBTID); -} -``` - -## Rationale - -TBD - -## Backwards Compatibility - -- This EIP is backward compliant for the contracts that keep intact the metadata structure of previous issued SBT's with their ID and claim requirement details. - - For e.g if the DeFI provider (using the modifiers to validate the ownership of required SBT by owner) wants the admin to change the logic of verification or remove certain claim structure, the previous holders of the certificates will be affected by these changes. - -## Test Cases - -Test cases for the minimal reference implementation can be found [here](../assets/eip-5851/contracts/test.sol) for using transaction verification regarding whether the users hold the tokens or not. Use Remix IDE to compile and test the contracts. - -## Reference Implementation - -The [interface](../assets/eip-5851/contracts/interfaces/IERC5851.sol) is divided into two separate implementations: - -- [EIP-5851 Verifier](../assets/eip-5851/contracts/ERC5851Verifier.sol) is a simple modifier that needs to be imported by functions that are to be only called by holders of the SBT certificates. Then the modifier will call the issuer contract to verifiy if the claimer has the SBT certifcate in question. - -- [EIP-5851 Issuer](../assets/eip-5851/contracts/ERC5851Issuer.sol) is an example of an identity certificate that can be assigned by a KYC controller contract. This is a full implementation of the standard interface. - -## Security Considerations - -1. Implementation of functional interfaces for creating KYC on SBT (i.e `changeStandardClaim()`, `certify()` and `revoke()`) are dependent on the admin role. Thus the developer must insure security of admin role and rotation of this role to the entity entrusted by the KYC attestation service provider and DeFI protocols that are using this attestation service. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5851.md diff --git a/EIPS/eip-5883.md b/EIPS/eip-5883.md index 5cfe3225bb677e..4e9fcc4d584bc2 100644 --- a/EIPS/eip-5883.md +++ b/EIPS/eip-5883.md @@ -1,93 +1 @@ ---- -eip: 5883 -title: Token Transfer by Social Recovery -description: On-Chain Social Recovery taking users' reputation into account & using a nearest-neighbour approach. -author: Erhard Dinhobl (@mrqc), Kevin Riedl (@wsdt) -discussions-to: https://ethereum-magicians.org/t/eip-5806-delegate-transaction/11409 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-07-19 ---- - -## Abstract - -This EIP standardizes a mechanism of a social recovery where a token may be transferred from an inaccessible account to a new account, given enough approvals from other identities. This approval is not purely technical, but rather needs human intervention. These humans are - based on the Soul Bound Token proposal - called Souls. When enough Souls give their approval (which is a Yes/No decision) and a threshold is reached, a token is transferred from an old to a new identity. - -## Motivation - -It is a known problem that the private key of an account can be lost. If that key is lost it's not possible to recover the tokens owned by that account. The holder loses those tokens forever. In addition to directly harming the token holder, the entire ecosystem of the token itself is affected: the more tokens that are lost the less tokens are available for the natural growth and planned evolution of that ecosystem. - - -## Specification - -```solidity - -pragma solidity ^0.8.7; - -interface ISocialRecovery { - /// @dev Related but independent identity approves the transfer - function approveTransfer(address from_, address to_) external; - - /// @dev User wants to move their onchain identity to another wallet which needs to be approved by n-nearest neighbour identities - function requestTransfer(address from_, address to_) external payable; - - function addNeighbour(address neighbour_) external; - - function removeNeighbour(address neighbour_) external; -} -``` - -**The math behind it**: - -A compliant contract SHOULD calculate the score of a node n with the following formula: - -$$ score(n) = tanh({ { {\displaystyle\sum_{i = 1}^{|N|} } {log{(n_i^{r} {1 \over t - n_i^{t} + 1})}} \over{|N| + 1}} + n^{r}}) $$ - -where: - -$t$ is the current time (can be any time-identifying value such as `block.timestamp`, `block.number`, etc.) - -$n^{r}$ is the reward count of the node n - -$N$ is the list of neighbours of n - -$n_i^{r}$ is the reward count of neighbour node i from n - -$n_i^{t}$ is the last timestamp (where a reward was booked on that account) of neighbour node i from n - - -**Flows**: - -```mermaid -%% Approval of asset movement - sequenceDiagram - AnyWallet->SmartContract: Requests transfer - SmartContract->All neighbours: Centralized notification via Websocket, EPNS, etc. - Neighbour->SmartContract: Approve Transfer - alt Threshold amount of approvers reached - alt Cumulative Score of approvers above threshold - SmartContract->NewAssetOwner: Transfer asset (e.g. identity token) - end - end - SmartContract->Neighbour: Add Reward to approver -``` - - -## Rationale - -The formula proposed was deemed very resilient and provides a coherent incentivation structure to actually see value in the on-chain score. The formula adds weights based on scores based on time which further contributes to the fairness of the metric. - - -## Security Considerations - - -1) We currently do not see any mechanism of preventing a user of getting a lot of rewards. Sure, a high reward is bound to a lot of investment but the person who wants to get that reward amount and has a enough money will reach it. The only thing which could be improved is that we somehow find a mechanism really identify users bound to an address. We thought about having a kind of a hashing mechanism which hashes a real world object which could be fuzzy (for sure!) and generates a hash out of it which is the same based on the fuzzy set. - -2) We implemented a threshold which must be reached to make a social token transfer possible. Currently there is no experience which defines a "good" or "bad" threshold hence we tried to find a first value. This can or must be adjusted based on future experience. - -3) Another problem we see is that the network of the neighbours is not active anymore to reach the necessary minimum threshold. Which means that due to not being able to reach the minimum amount of approvals a user gets stuck with the e.g. social token transfer he/she wants to perform. Hence the contract lives from its usage and if it tends to be not used anymore it will get useless. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5883.md diff --git a/EIPS/eip-5902.md b/EIPS/eip-5902.md index d2133f381bd703..68480ec9ee5ac0 100644 --- a/EIPS/eip-5902.md +++ b/EIPS/eip-5902.md @@ -1,421 +1 @@ ---- -eip: 5902 -title: Smart Contract Event Hooks -description: Format that allows contracts to semi-autonoumously respond to events emitted by other contracts -author: Simon Brown (@orbmis) -discussions-to: https://ethereum-magicians.org/t/idea-smart-contract-event-hooks-standard/11503 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-11-09 -requires: 712 ---- - -## Abstract - -This EIP proposes a standard for creating "hooks" that allow a smart contract function to be called automatically in response to a trigger fired by another contract, by using a public relayer network as a messaging bus. - -While there are many similar solutions in existence already, this proposal describes a simple yet powerful primitive that can be employed by many applications in an open, permissionless and decentralized manner. - -It relies on two interfaces, one for a publisher contract and one for a subscriber contract. The publisher contract emits events that are picked up by "relayers", who are independent entities that subscribe to "hook" events on publisher contracts, and call a function on the respective subscriber contracts, whenever a hook event is fired by the publisher contracts. Whenever a relayer calls the respective subscriber's contract with the details of the hook event emitted by the publisher contract, they are paid a fee by the subscriber. Both the publisher and subscriber contracts are registered in a central registry smart contract that relayers can use to discover hooks. - -## Motivation - -There exists a number of use cases that require some off-chain party to monitor the chain and respond to on-chain events by broadcasting a transaction. Such cases usually require some off-chain process to run alongside an Ethereum node in order to subscribe to events emitted by smart contract, and then execute some logic in response and subsequently broadcast a transaction to the network. This requires an Ethereum node and an open websocket connection to some long-running process that may only be used infrequently, resulting in a sub-optimal use of resources. - -This proposal would allow for a smart contract to contain the logic it needs to respond to events without having to store that logic in some off-chain process. The smart contract can subscribe to events fired by other smart contracts and would only execute the required logic when it is needed. This method would suit any contract logic that does not require off-chain computation, but usually requires an off-chain process to monitor the chain state. With this approach, subscribers do not need their own dedicated off-chain processes for monitoring and responding to contract events. Instead, a single incentivized relayer can subscribe to many different events on behalf of multiple different subscriber contracts. - -Examples of use cases that would benefit from this scheme include: - -### Collateralised Lending Protocols - -Collateralised lending protocols or stablecoins can emit events whenever they receive price oracle updates, which would allow borrowers to automatically "top-up" their open positions to avoid liquidation. - -For example, Maker uses the "medianizer" smart contract which maintains a whitelist of price feed contracts which are allowed to post price updates. Every time a new price update is received, the median of all feed prices is re-computed and the medianized value is updated. In this case, the medianizer smart contract could fire a hook event that would allow subscriber contracts to decide to re-collateralize their CDPs. - -### Automated Market Makers - -AMM liquidity pools could fire a hook event whenever liquidity is added or removed. This could allow a subscriber smart contracts to add or remove liquidity once the total pool liquidity reaches a certain point. - -AMMs can fire a hook whenever there is a trade within a trading pair, emitting the time-weighted-price-oracle update via an hook event. Subscribers can use this to create an automated Limit-Order-Book type contract to buy/sell tokens once an asset's spot price breaches a pre-specified threshold. - -### DAO Voting - -Hook events can be emitted by a DAO governance contract to signal that a proposal has been published, voted on, carried or vetoed, and would allow any subscriber contract to automatically respond accordingly. For example, to execute some smart contract function whenever a specific proposal has passed, such as an approval for payment of funds. - -### Scheduled Function Calls - -A scheduler service can be created whereby a subscriber can register for a scheduled funtion call, this could be done using unix cron format and the service can fire events from a smart contract on separate threads. Subscriber contracts can subscriber to the respective threads in order to subscribe to certain schedules (e.g. daily, weekly, hourly etc.), and could even register customer cron schedules. - -### Recurring Payments - -A service provider can fire Hook events that will allow subscriber contracts to automatically pay their service fees on a regular schedule. Once the subscriber contracts receive a hook event, they can call a function on the service provider's contract to transfer funds due. - -### Coordination via Delegation - -Hook event payloads can contain any arbitrary data, this means you can use things like the Delegatable framework to sign off-chain delegations which can faciliate a chain of authorized entities to publish valid Hook events. You can also use things like BLS threshold signatures, to facilitate multiple off-chain publishers to authorize the firing of a Hook. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### Registering a Publisher - -Both the publisher and subscriber contracts **MUST** register in a specific register contract, similarly to how smart contracts register an interface in the [ERC-1820](./eip-1820.md) contract. The registry contract **MUST** must use a deterministic deployment mechanism, i.e. using a factory contract and a specific salt. - -To register a publisher contract's hook, the `registerHook` function **MUST** be called on the registry contract. The parameters that need to be supplied are: - - - (address) The publisher contract address - - (uint256) The thread id that the hooks events will reference (a single contract can fire hook events with any number of threads, subscribers can choose which threads to subscribe to) - - (bytes) The public key associated with the hook events (optional) - -When the `registerHook` function is called on the registry contract, the registry contract **MUST** make a downstream call to the publisher contract address, by calling the publisher contract's `verifyEventHookRegistration` function, with the same arguments as passed to the `registerHook` function on the registry contract. The `verifyEventHookRegistration` function in the publisher contract **MUST** return true in order to indicate that the contract will allow itself to be added to the registry as a publisher. The registry contract **MUST** emit a `HookRegistered` event to indicate that a new publisher contract has been added. - -### Updating a Hook - -Publishers may want to update the details associated with a Hook event, or indeed remove support for a Hook event completely. The registry contract **MUST** implement the `updatePublisher` function to allow for an existing publisher contract to be updated in the registry. The registry contract **MUST** emit a `PublisherUpdated` event to indicate that the publisher contract was updated. - -### Removing a Hook - -To remove a previously registered Hook, the function `removeHook` function must be called on the Registry contract, with the same parameters as the `updateHook` function. The registry contract **MUST** emit a `HookRemoved` event with the same parameters as passed to the 'removeHook' function and the `msg.sender` value. - -### Registering a Subscriber - -To register a subscriber to a hook, the `registerSubscriber` function **MUST** be called on the registry contract with the following parameters: - - - (address) The publisher contract address - - (bytes32) The subscriber contract address - - (uint256) The thread id to subscribe to - - (uint256) The fee that the subscriber is willing to pay to get updates - - (uint256) The maximum gas that the subscriber will allow for updates, to prevent griefing attacks, or 0 to indicate no maximum - - (uint256) The maximum gas price that the subscriber is willing to repay the relayer on top of the fee, or 0 to indicate no rebates - - (uint256) The chain id that the subscriber wants updates from - - (address) The address of the token that the fee will be paid in or 0x0 for the chain's native asset (e.g. ETH, MATIC etc.) - -The subscriber contract **MAY** implement gas refunds on top of the fixed fee per update. Where a subscriber chooses to do this, then they **SHOULD** specify the `maximum gas` and `maximum gas price` parameters in order to protect themselves from griefing attacks. This is so that a malicious or careless relay doesn't set an exorbitantly high gas price and ends up draining the subscriber contracts. Subscriber contracts can otherwise choose to set a fee that is estimated to be sufficiently high to cover gas fees. - -Note that while the chain id and the token address were not included in the original version of the spec, the simple addition of these two parameters allows for leveraging the relayers for cross chain messages, should the subscriber wish to do this, and also allows for paying relayer fees in various tokens. - -### Updating a Subscription - -To update a subscription, the `updateSubscriber` function **MUST** be called with the same set of parameters as the `registerSubscriber` function. This might be done in order to cancel a subscription, or to change the subscription fee. Note that the `updateSubscriber` function **MUST** maintain the same `msg.sender` that the `registerSubscriber` function was called with. - -### Removing a Subscription - -To remove a previously registered subscription, the function `removeSubscriber` **MUST** be called on the Registry contract, with the same parameters as the `updateSubscriber` function, but without the `fee` parameter (i.e. publisher and subscriber contract addresses and thread id). The fee will be subsequently set to 0 to indicate that the subscriber no longer wants updates for this subscription. The registry contract **MUST** emit a `SubscriptionRemoved` event with publisher contract address, subscriber contract address and the thread id as topics. - -### Publishing an Event - -A publisher contract **SHOULD** emit a hook event from at least one function. The emitted event **MUST** be called `Hook` and **MUST** contain the following parameters: - - - uint256 (indexed) - threadId - - uint256 (indexed) - nonce - - bytes32 digest - - bytes payload - - bytes32 checksum - -The `nonce` value **MUST** be incremented every time a Hook event is fired by a publisher contract. Every Hook event **MUST** have a unique `nonce` value. The `nonce` property is initiated to 1, but the first Hook event ever fired **MUST** be set to 2. This is to prevent ambiguity between an uninitiated nonce variable and a nonce variable that is explicitly initiated to zero. - -The `digest` parameter of the event **MUST** be the keccak256 hash of the payload, and the `checksum` **MUST** be the keccak256 hash of the concatenation of the digest with the current blockheight, e.g.: - -`bytes32 checksum = keccak256(abi.encodePacked(digest, block.number));` - -The `Hook` event can be triggered by a function call from any EOA or external contract. This allows the payload to be created dynamically within the publisher contract. The subscriber contract **SHOULD** call the `verifyEventHook` function on the publisher contract to verify that the received Hook payload is valid. - -The payload **MAY** be passed to the function firing the Hook event instead of being generated within the publisher contract itself, but if a signature is provided it **MUST** sign a hash of the payload, and it is strongly recommended to use the [EIP-712](./eip-712.md) standard, and to follow the data structure outlined at the end of this proposal. This signature **SHOULD** be verified by the subscribers to ensure they are getting authentic events. The signature **MUST** correspond to the public key that was registered with the event. With this approach, the signature **SHOULD** be placed at the start of the payload (e.g. bytes 0 to 65 for an ECDSA signature with r, s, v properties). This method of verification can be used for cross-chain Hook events, where subscribers will not be able to call the `verifyHookEvent` of the publisher contract on another chain. - -The payload **MUST** be passed to subscribers as a byte array in calldata. The subscriber smart contract **SHOULD** convert the byte array into the required data type. For example, if the payload is a snark proof, the publisher would need to serialize the variables into a byte array, and the subscriber smart contract would need to deserialize it on the other end, e.g.: - -``` -struct SnarkProof { - uint256[2] a; - uint256[2][2] b; - uint256[2] c; - uint256[1] input; -} - -SnarkProof memory zkproof = abi.decode(payload, SnarkProof); -``` - -### Relayers - -Relayers are independent parties that listen to `Hook` events on publisher smart contracts. Relayers retrieve a list of subscribers for different hooks from the registry, and listen for hook events being fired on the publisher contracts. Once a hook event has been fired by a publisher smart contract, relayers can decide to relay the hook event's payload to the subscriber contracts by broadcasting a transaction that executes the subscriber contract's `verifyHook` function. Relayers are incentivised to do this because it is expected that the subscriber contract will remunerate them with ETH, or some other asset. - -Relayers **SHOULD** simulate the transaction locally before broadcasting it to make sure that the subscriber contract has sufficient balance for payment of the fee. This requires subscriber contracts to maintain a balance of ETH (or some asset) in order to provision payment of relayer fees. A subscriber contract **MAY** decide to revert a transaction based on some logic, which subsequently allows the subscriber contract to conditionally respond to events, depending on the data in the payload. In this case the relayer will simulate the transaction locally and determine not to relay the Hook event to the subscriber contract. - -### Verifying a Hook Event - -The `verifyHook` function of the subscriber contracts **SHOULD** include logic to ensure that they are retrieving authentic events. In the case where the Hook event contains a signature, then subscriber contracts **SHOULD** create a hash of the required parameters, and **SHOULD** verify that the signature in the hook event is valid against the derived hash and the publisher's public key (see the reference implemenetation for an example). The hook function **SHOULD** also verify the nonce of the hook event and record it internally, in order to prevent replay attacks. - -For Hook events without signatures, the subscriber contract **SHOULD** call the `verifyHookEvent` on the publisher contract in order to verify that the hook event is valid. The publisher smart contract **MUST** implement the `verifyHookEvent`, which accepts the hash of the payload, the thread id, the nonce, and the block height associated with the Hook event, and returns a boolean value to indicate the Hook event's authenticity. - -### Interfaces - -IRegistry.sol - -```js -/// @title IRegistry -/// @dev Implements the registry contract -interface IRegistry { - /// @dev Registers a new hook event by a publisher - /// @param publisherContract The address of the publisher contract - /// @param threadId The id of the thread these hook events will be fired on - /// @param signingKey The public key that corresponds to the signature of externally generated payloads (optional) - /// @return Returns true if the hook is successfully registered - function registerHook( - address publisherContract, - uint256 threadId, - bytes calldata signingKey - ) external returns (bool); - - /// @dev Verifies a hook with the publisher smart contract before adding it to the registry - /// @param publisherAddress The address of the publisher contract - /// @param threadId The id of the thread these hook events will be fired on - /// @param signingKey The public key used to verify the hook signatures - /// @return Returns true if the hook is successfully verified - function verifyHook( - address publisherAddress, - uint256 threadId, - bytes calldata signingKey - ) external returns (bool); - - /// @dev Update a previously registered hook event - /// @dev Can be used to transfer hook authorization to a new address - /// @dev To remove a hook, transfer it to the burn address - /// @param publisherContract The address of the publisher contract - /// @param threadId The id of the thread these hook events will be fired on - /// @param signingKey The public key used to verify the hook signatures - /// @return Returns true if the hook is successfully updated - function updateHook( - address publisherContract, - uint256 threadId, - bytes calldata signingKey - ) external returns (bool); - - /// @dev Remove a previously registered hook event - /// @param publisherContract The address of the publisher contract - /// @param threadId The id of the thread these hook events will be fired on - /// @param signingKey The public key used to verify the hook signatures - /// @return Returns true if the hook is successfully updated - function removeHook( - address publisherContract, - uint256 threadId, - bytes calldata signingKey - ) external returns (bool); - - /// @dev Registers a subscriber to a hook event - /// @param publisherContract The address of the publisher contract - /// @param subscriberContract The address of the contract subscribing to the event hooks - /// @param threadId The id of the thread these hook events will be fired on - /// @param fee The fee that the subscriber contract will pay the relayer - /// @param maxGas The maximum gas that the subscriber allow to spend, to prevent griefing attacks - /// @param maxGasPrice The maximum gas price that the subscriber is willing to rebate - /// @param chainId The chain id that the subscriber wants updates on - /// @param feeToken The address of the token that the fee will be paid in or 0x0 for the chain's native asset (e.g. ETH) - /// @return Returns true if the subscriber is successfully registered - function registerSubscriber( - address publisherContract, - address subscriberContract, - uint256 threadId, - uint256 fee, - uint256 maxGas, - uint256 maxGasPrice, - uint256 chainId, - address feeToken - ) external returns (bool); - - /// @dev Registers a subscriber to a hook event - /// @param publisherContract The address of the publisher contract - /// @param subscriberContract The address of the contract subscribing to the event hooks - /// @param threadId The id of the thread these hook events will be fired on - /// @param fee The fee that the subscriber contract will pay the relayer - /// @return Returns true if the subscriber is successfully updated - function updateSubscriber( - address publisherContract, - address subscriberContract, - uint256 threadId, - uint256 fee - ) external returns (bool); - - /// @dev Removes a subscription to a hook event - /// @param publisherContract The address of the publisher contract - /// @param subscriberContract The address of the contract subscribing to the event hooks - /// @param threadId The id of the thread these hook events will be fired on - /// @return Returns true if the subscriber is subscription removed - function removeSubscription( - address publisherContract, - address subscriberContract, - uint256 threadId - ) external returns (bool); -} -``` - -IPublisher.sol - -```js -/// @title IPublisher -/// @dev Implements a publisher contract -interface IPublisher { - /// @dev Example of a function that fires a hook event when it is called - /// @param payload The actual payload of the hook event - /// @param digest Hash of the hook event payload that was signed - /// @param threadId The thread number to fire the hook event on - function fireHook( - bytes calldata payload, - bytes32 digest, - uint256 threadId - ) external; - - /// @dev Adds / updates a new hook event internally - /// @param threadId The thread id of the hook - /// @param signingKey The public key associated with the private key that signs the hook events - function addHook(uint256 threadId, bytes calldata signingKey) external; - - /// @dev Called by the registry contract when registering a hook, used to verify the hook is valid before adding - /// @param threadId The thread id of the hook - /// @param signingKey The public key associated with the private key that signs the hook events - /// @return Returns true if the hook is valid and is ok to add to the registry - function verifyEventHookRegistration( - uint256 threadId, - bytes calldata signingKey - ) external view returns (bool); - - /// @dev Returns true if the specified hook is valid - /// @param payloadhash The hash of the hook's data payload - /// @param threadId The thread id of the hook - /// @param nonce The nonce of the current thread - /// @param blockheight The blockheight that the hook was fired at - /// @return Returns true if the specified hook is valid - function verifyEventHook( - bytes32 payloadhash, - uint256 threadId, - uint256 nonce, - uint256 blockheight - ) external view returns (bool); -} -``` - -ISubscriber.sol - -```js -/// @title ISubscriber -/// @dev Implements a subscriber contract -interface ISubscriber { - /// @dev Example of a function that is called when a hook is fired by a publisher - /// @param publisher The address of the publisher contract in order to verify hook event with - /// @param payload Hash of the hook event payload that was signed - /// @param threadId The id of the thread this hook was fired on - /// @param nonce Unique nonce of this hook - /// @param blockheight The block height at which the hook event was fired - function verifyHook( - address publisher, - bytes calldata payload, - uint256 threadId, - uint256 nonce, - uint256 blockheight - ) external; -} - -``` - -## Rationale - -The rationale for this design is that it allows smart contract developers to write contract logic that listens and responds to events fired in other smart contracts, without requiring them to run some dedicated off-chain process to achieve this. This best suits any simple smart contract logic that runs relatively infrequently in response to events in other contracts. - -This improves on the existing solutions to achieve a pub/sub design pattern. To elaborate: a number of service providers currently offer "webhooks" as a way to subscribe to events emitted by smart contracts, by having some API endpoint called when the events are emitted, or alternatively offer some serverless feature that can be triggered by some smart contract event. This approach works very well, but it does require that some API endpoint or serverless function be always available, which may require some dedicated server / process, which in turn will need to have some private key, and some amount of ETH in order to re-broadcast transactions, no to mention the requirement to maintain an account with some third party provider. - -This approach offers a more suitable alternative for when an "always-on" server instance is not desirable, e.g. in the case that it will be called infrequently. - -This proposal incorporates a decentralized market-driven relay network, and this decision is based on the fact that this is a highly scalable approach. Conversely, it is possible to implement this functionality without resorting to a market-driven approach, by simply defining a standard for contracts to allow other contracts to subscribe directly. That approach is conceptually simpler, but has its drawbacks, in so far as it requires a publisher contract to record subscribers in its own state, creating an overhead for data management, upgradeability etc. That approach would also require the publisher to call the `verifyHook` function on each subscriber contract, which will incur potentially significant gas costs for the publisher contract. - -## Security Considerations - -### Griefing Attacks - -It is imperative that subscriber contracts trust the publisher contracts not to fire events that hold no intrinsic interest or value for them, as it is possible that malicious publisher contracts can publish a large number of events that will in turn drain the ETH from the subscriber contracts. - -### Front-running Attacks - -It is advised not to rely on signatures alone to validate Hook events. It is important for publishers and subscribers of hooks to be aware that it is possible for a relayer to relay hook events before they are published, by examining the publisher's transaction in the mempool before it actually executes in the publisher's smart contract. The normal flow is for a "trigger" transaction to call a function in the publisher smart contract, which in turn fires an event which is then picked up by relayers. Competitive relayers will observe that it is possible to pluck the signature and payload from the trigger transaction in the public mempool and simply relay it to subscriber contracts before the trigger transaction has been actually included in a block. In fact, it is possible that the subscriber contracts process the event before the trigger transaction is processed, based purely on gas fee dynamics. This can mitigated against by subscriber contracts calling the `verifyEventHook` function on the publisher contract when they receive a Hook event. - -Another risk from front-running affects relayers, whereby the relayer's transactions to the subscriber contracts can be front-run by generalized MEV searchers in the mempool. It is likely that this sort of MEV capture will occur in the public mempool, and therefore it is advised that relayers use private channels to block builders to mitigate against this issue. - -### Relayer Competition - -By broadcasting transactions to a segregated mempool, relayers protect themselves from front-running by generalized MEV bots, but their transactions can still fail due to competition from other relayers. If two or more relayers decide to start relaying hook events from the same publisher to the same subscribers, then the relay transactions with the highest gas price will be executed before the others. This will result in the other relayer's transactions potentially failing on-chain, by being included later in the same block. For now, there are certain transaction optimization services that will prevent transactions from failing on-chain, which will offer a solution to this problem, though this is out-of-scope for this document. - -### Optimal Fees - -The fees that are paid to relayers are at the discretion of the subscribers, but it can be non-trivial to set fees to their optimal level, especially when considering volatile gas fees and competition between relayers. This will result in subscribers setting fees to a perceived "safe" level, which they are confident will incentivize relayers to relay Hook events. This will inevitably lead to poor price discovery and subscribers over-paying for updates. - -The best way to solve this problem is through an auction mechanism that would allow relayers to bid against each other for the right to relay a transaction, which would guarantee that subscribers are paying the optimal price for their updates. Describing an auction mechanism that would satisfy this requirements is out of scope for this proposal, but there exists proposals for general purpose auction mechanisms that can faciliate this without introducing undue latency. One exampe of such as proposal is SUAVE from Flashbots, and there will likely be several others in time. - -### Without an Auction - -In order to cultivate and maintain a reliable relayer market without the use of an auction mechanism, subscriber contracts would need to implement logic to either rebate any gas fees up to a specified limit, (while still allowing for execution of hook updates under normal conditions). - -Another approach would be to implement a logical condition that checks the gas price of the transaction that is calling the `verifyHook` function, to ensure that the gas price does not effectively reduce the fee to zero. This would require that the subscriber smart contract has some knowledge of the approximate gas used by it's `verifyHook` function, and to check that the condition `minFee >= fee - (gasPrice * gasUsed)` is true. This will mitigate against competitive bidding that would drive the _effective_ relayer fee to zero, by ensuring that there is some minimum fee below which the effective fee is not allowed to drop. This would mean that the highest gas price that can be paid before the transaction reverts is `fee - minFee + ε` where `ε ~= 1 gwei`. This will require careful estimation of the gas cost of the `verifyHook` function and an awareness that the gas used may change over time as the contract's state changes. The key insight with this approach is that competition between relayers will result in the fee that the subscribers pay always being the maximum, which is why the use of an auction mechanism is preferable. - -### Relayer Transaction Batching - -Another important consideration is with batching of Hook events. Relayers are logically incentivized to batch Hook updates to save on gas, seeing as gas savings amount to 21,000 * n where n is the number of hooks being processed in a block by a single relayer. If a relayer decides to batch multiple Hook event updates to various subscriber contracts into a single transaction, via a multi-call proxy contract, then they increase the risk of the entire batch failing on-chain if even one of the transactions in the batch fails on-chain. For example, if relayer A batches x number of Hook updates, and relayer B batches y number of Hook updates, it is possible that relayer A's batch is included in the same block in front of relayer B's batch, and if both batches contain at least one duplicate, (i.e. the same Hook event to the same subscriber), then this will cause relayer B's batch transaction to revert on-chain. This is an important consideration for relayers, and suggests that relayers should have access to some sort of bundle simulation service to identify conflicting transactions before they occur. - -### Replay Attacks - -When using signature verification, it is advised to use the [EIP-712](./eip-712.md) standard in order to prevent cross network replay attacks, where the same contract deployed on more than one network can have its hook events pushed to subscribers on other networks, e.g. a publisher contract on Polygon can fire a hook event that could be relayed to a subscriber contract on Gnosis Chain. Whereas the keys used to sign the hook events should ideally be unique, in reality this may not always be the case. - -For this reason, it is recommended to use [ERC-721](./eip-712.md) Typed Data Signatures. In this case the process that initiates the hook should create the signature according to the following data structure: - -```js -const domain = [ - { name: "name", type: "string" }, - { name: "version", type: "string" }, - { name: "chainId", type: "uint256" }, - { name: "verifyingContract", type: "address" }, - { name: "salt", type: "bytes32" } -] - -const hook = [ - { name: "payload", type: "string" }, - { type: "uint256", name: "nonce" }, - { type: "uint256", name: "blockheight" }, - { type: "uint256", name: "threadId" }, -] - -const domainData = { - name: "Name of Publisher Dapp", - version: "1", - chainId: parseInt(web3.version.network, 10), - verifyingContract: "0x123456789abcedf....publisher contract address", - salt: "0x123456789abcedf....random hash unique to publisher contract" -} - -const message = { - payload: "bytes array serialized payload" - nonce: 1, - blockheight: 999999, - threadId: 1, -} - -const eip712TypedData = { - types: { - EIP712Domain: domain, - Hook: hook - }, - domain: domainData, - primaryType: "Hook", - message: message -} -``` - -Note: please refer to the unit tests in the reference implmenetation for an example of how a hook event should be constructed properly by the publisher. - -Replay attacks can also occur on the same network that the event hook was fired, by simply re-broadcasting an event hook that was already broadcast previously. For this reason, subscriber contracts should check that a nonce is included in the event hook being received, and record the nonce in the contract's state. If the hook nonce is not valid, or has already been recorded, the transaction should revert. - -### Cross-chain Messaging - -There is also the possibility to leverage the `chainId` for more than preventing replay attacks, but also for accepting messages from other chains. In this use-case the subscriber contracts should register on the same chain that the subscriber contract is deployed on, and should set the `chainId` to the chain it wants to receive hook events from. - -## Copyright - -Copyright and related rights waived via CC0. +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5902.md diff --git a/EIPS/eip-5982.md b/EIPS/eip-5982.md index 6418ff2a7c59c7..855d6669e66b13 100644 --- a/EIPS/eip-5982.md +++ b/EIPS/eip-5982.md @@ -1,92 +1 @@ ---- -eip: 5982 -title: Role-based Access Control -description: An interface for role-based access control for smart contracts. -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/eip-5982-role-based-access-control/11759 -status: Review -type: Standards Track -category: ERC -created: 2022-11-15 -requires: 165, 5750 ---- - -## Abstract - -This EIP defines an interface for role-based access control for smart contracts. Roles are defined as `byte32`. The interface specifies how to read, grant, create and destroy roles. It specifies the sense of role power in the format of its ability to call a given method -identified by `bytes4` method selector. It also specifies how metadata of roles are represented. - -## Motivation - -There are many ways to establish access control for privileged actions. One common pattern is "role-based" access control, where one or more users are assigned to one or more "roles," which grant access to privileged actions. This pattern is more secure and flexible than ownership-based access control since it allows for many people to be granted permissions according to the principle of least privilege. - -## 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. - -Interfaces of reference is described as followed: - -```solidity -interface IERC_ACL_CORE { - function hasRole(bytes32 role, address account) external view returns (bool); - function grantRole(bytes32 role, address account) external; - function revokeRole(bytes32 role, address account) external; -} -``` - -```solidity -interface IERC_ACL_GENERAL { - event RoleGranted(address indexed grantor, bytes32 indexed role, address indexed grantee, bytes _data); - event RoleRevoked(address indexed revoker, bytes32 indexed role, address indexed revokee, bytes _data); - - event RoleCreated(address indexed roleGrantor, bytes32 role, bytes32 adminOfRole, string name, string desc, string uri, bytes32 calldata _data); - event RoleDestroyed(address indexed roleDestroyer, bytes32 role, bytes32 calldata _data); - event RolePowerSet(address indexed rolePowerSetter, bytes32 role, bytes4 methods, bytes calldata _data); - - function grantRole(bytes32 role, address account, bytes calldata _data) external; - function revokeRole(bytes32 role, address account, bytes calldata _data) external; - - function createRole(bytes32 role, bytes32 adminOfRole, string name, string desc, string uri, bytes32 calldata _data) external; - function destroyRole(bytes32 role, bytes32 calldata _data) external; - function setRolePower(bytes32 role, bytes4 methods, bytes calldata _data) view external returns(bool); - - function hasRole(bytes32 role, address account, bytes calldata _data) external view returns (bool); - function canGrantRole(bytes32 grantor, bytes32 grantee, bytes calldata _data) view external returns(bool); - function canRevokeRole(bytes32 revoker, bytes32 revokee, address account, bytes calldata _data) view external returns(bool); - function canExecute(bytes32 executor, bytes4 methods, bytes32 calldata payload, bytes calldata _data) view external returns(bool); -} -``` - -```solidity -interface IERC_ACL_METADATA { - function roleName(bytes32) external view returns(string); - function roleDescription(bytes32) external view returns(string); - function roleURI(bytes32) external view returns(string); -} -``` - -1. Compliant contracts MUST implement `IERC_ACL_CORE` -2. It is RECOMMENDED for compliant contracts to implement the optional extension `IERC_ACL_GENERAL`. -3. Compliant contracts MAY implement the optional extension `IERC_ACL_METADATA`. -4. A role in a compliant smart contract is represented in the format of `bytes32`. It's RECOMMENDED the value of such role is computed as a -`keccak256` hash of a string of the role name, in this format: `bytes32 role = keccak256("")`. such as `bytes32 role = keccak256("MINTER")`. -5. Compliant contracts SHOULD implement [ERC-165](./eip-165.md) identifier. - -## Rationale - -1. The names and parameters of methods in `IERC_ACL_CORE` are chosen to allow backward compatibility with OpenZeppelin's implementation. -2. The methods in `IERC_ACL_GENERAL` conform to [ERC-5750](./eip-5750.md) to allow extension. -3. The method of `renounceRole` was not adopted, consolidating with `revokeRole` to simplify interface. - - -## Backwards Compatibility - -Needs discussion. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-5982.md diff --git a/EIPS/eip-600.md b/EIPS/eip-600.md index 5165fa1fda244c..6b4ec5496ce87c 100644 --- a/EIPS/eip-600.md +++ b/EIPS/eip-600.md @@ -1,65 +1 @@ ---- -eip: 600 -title: Ethereum purpose allocation for Deterministic Wallets -author: Nick Johnson (@arachnid), Micah Zoltu (@micahzoltu) -type: Standards Track -category: ERC -status: Final -discussions-to: https://ethereum-magicians.org/t/eip-erc-app-keys-application-specific-wallet-accounts/2742 -created: 2017-04-13 ---- - -## Abstract -This EIP defines a logical hierarchy for deterministic wallets based on [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), the purpose scheme defined in [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki) and [this proposed change to BIP43](https://github.com/bitcoin/bips/pull/523). - -This EIP is a particular application of BIP43. - -## Motivation -Because Ethereum is based on account balances rather than UTXO, the hierarchy defined by BIP44 is poorly suited. As a result, several competing derivation path strategies have sprung up for deterministic wallets, resulting in inter-client incompatibility. This BIP seeks to provide a path to standardise this in a fashion better suited to Ethereum's unique requirements. - -## Specification -We define the following 2 levels in BIP32 path: - -
-m / purpose' / subpurpose' / EIP'
-
- -Apostrophe in the path indicates that BIP32 hardened derivation is used. - -Each level has a special meaning, described in the chapters below. - -### Purpose - -Purpose is set to 43, as documented in [this proposed change to BIP43](https://github.com/bitcoin/bips/pull/523). - -The purpose field indicates that this path is for a non-bitcoin cryptocurrency. - -Hardened derivation is used at this level. - -### Subpurpose -Subpurpose is set to 60, the SLIP-44 code for Ethereum. - -Hardened derivation is used at this level. - -### EIP -EIP is set to the EIP number specifying the remainder of the BIP32 derivation path. This permits new Ethereum-focused applications of deterministic wallets without needing to interface with the BIP process. - -Hardened derivation is used at this level. - -## Rationale -The existing convention is to use the 'Ethereum' coin type, leading to paths starting with `m/44'/60'/*`. Because this still assumes a UTXO-based coin, we contend that this is a poor fit, resulting in standardisation, usability, and security compromises. As a result, we are making the above proposal to define an entirely new hierarchy for Ethereum-based chains. - -## Backwards Compatibility -The introduction of another derivation path requires existing software to add support for this scheme in addition to any existing schemes. Given the already confused nature of wallet derivation paths in Ethereum, we anticipate this will cause relatively little additional disruption, and has the potential to improve matters significantly in the long run. - -## Test Cases -TBD - -## Implementation -None yet. - -## References -[This discussion on derivation paths](https://github.com/ethereum/EIPs/issues/84) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-600.md diff --git a/EIPS/eip-601.md b/EIPS/eip-601.md index 50504640ce3fe2..a43bbf49510d73 100644 --- a/EIPS/eip-601.md +++ b/EIPS/eip-601.md @@ -1,80 +1 @@ ---- -eip: 601 -title: Ethereum hierarchy for deterministic wallets -author: Nick Johnson (@arachnid), Micah Zoltu (@micahzoltu) -type: Standards Track -category: ERC -status: Final -discussions-to: https://ethereum-magicians.org/t/eip-erc-app-keys-application-specific-wallet-accounts/2742 -created: 2017-04-13 ---- - -## Abstract -This EIP defines a logical hierarchy for deterministic wallets based on [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki), the purpose scheme defined in [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki) and eip-draft-ethereum-purpose. - -This EIP is a particular application of eip-draft-ethereum-purpose. - -## Motivation -At present, different Ethereum clients and wallets use different derivation paths; a summary of them can be found [here](https://github.com/ethereum/EIPs/issues/84#issuecomment-292324521). Some of these paths violate BIP44, the standard defining derivation paths starting with `m/44'/`. This creates confusion and incompatibility between wallet implementations, in some cases making funds from one wallet inaccessible on another, and in others requiring prompting users manually for a derivation path, which hinders usability. - -Further, BIP44 was designed with UTXO-based blockchains in mind, and is a poor fit for Ethereum, which uses an accounts abstraction instead. - -As an alternative, we propose a deterministic wallet hierarchy better tailored to Ethereum's unique requiremnts. - -## Specification -We define the following 4 levels in BIP32 path: - -
-m / purpose' / subpurpose' / EIP' / wallet'
-
- -Apostrophe in the path indicates that BIP32 hardened derivation is used. - -Each level has a special meaning, described in the chapters below. - -### Purpose - -Purpose is a constant set to 43, indicating the key derivation is for a non-bitcoin cryptocurrency. - -Hardened derivation is used at this level. - -### Subpurpose -Subpurpose is set to 60, the SLIP-44 code for Ethereum. - -Hardened derivation is used at this level. - -### EIP -EIP is set to the EIP number specifying the remainder of the BIP32 derivation path. For paths following this EIP specification, the number assigned to this EIP is used. - -Hardened derivation is used at this level. - -### Wallet -This component of the path splits the wallet into different user identities, allowing a single wallet to have multiple public identities. - -Accounts are numbered from index 0 in sequentially increasing manner. This number is used as child index in BIP32 derivation. - -Hardened derivation is used at this level. - -Software should prevent a creation of an account if a previous account does not have a transaction history (meaning its address has not been used before). - -Software needs to discover all used accounts after importing the seed from an external source. - -## Rationale -The existing convention is to use the 'Ethereum' coin type, leading to paths starting with `m/44'/60'/*`. Because this still assumes a UTXO-based coin, we contend that this is a poor fit, resulting in standardisation, usability, and security compromises. As a result, we are making the above proposal to define an entirely new hierarchy for Ethereum-based chains. - -## Backwards Compatibility -The introduction of another derivation path requires existing software to add support for this scheme in addition to any existing schemes. Given the already confused nature of wallet derivation paths in Ethereum, we anticipate this will cause relatively little additional disruption, and has the potential to improve matters significantly in the long run. - -For applications that utilise mnemonics, the authors expect to submit another EIP draft that describes a method for avoiding backwards compatibility concerns when transitioning to this new derivation path. - -## Test Cases -TBD - -## Implementation -None yet. - -## References -[This discussion on derivation paths](https://github.com/ethereum/EIPs/issues/84) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-601.md diff --git a/EIPS/eip-6047.md b/EIPS/eip-6047.md index 9c8694e3fc5e23..918163040aac24 100644 --- a/EIPS/eip-6047.md +++ b/EIPS/eip-6047.md @@ -1,47 +1 @@ ---- -eip: 6047 -title: ERC-721 Balance indexing via Transfer event -description: Mandates emitting the Transfer event for ERC-721 NFTs during contract creation -author: Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/eip-xxx-require-erc721-to-always-emit-transfer/11894 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-11-26 -requires: 721 ---- - -## Abstract - -This EIP extends [ERC-721](./eip-721.md) to allow the tracking and indexing of NFTs by mandating that a pre-existing event be emitted during contract creation. - -ERC-721 requires a `Transfer` event to be emitted whenever a transfer or mint (i.e. transfer from `0x0`) or burn (i.g. transfer to `0x0`) occurs, **except during contract creation**. This EIP mandates that compliant contracts emit a `Transfer` event **regardless of whether it occurs during or after contract creation.** - -## Motivation - -[ERC-721](./eip-721.md) requires a `Transfer` event to be emitted whenever a transfer or mint (i.e. transfer from `0x0`) or burn (i.e. transfer to `0x0`) occurs, EXCEPT for during contract creation. Due to this exception, contracts can mint NFTs during contract creation without the event being emitted. Unlike ERC-721, the [ERC-1155](./eip-1155.md) standard mandates events to be emitted regardless of whether such minting occurs during or outside of contract creation. This allows an indexing service or any off-chain service to reliably capture and account for token creation. - -This EIP removes this exception granted by ERC-721 and mandates emitting the `Transfer` for ERC-721 during contract creation. In this manner, indexers and off-chain applications can track token minting, burning, and transferring while relying only on ERC-721's `Transfer` event log. - -## 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. Compliant contracts MUST implement [ERC-721](./eip-721.md) -2. Compliant contracts MUST emit a `Transfer` event whenever a token is transferred, minted (i.e. transferred from `0x0`), or burned (i.g. transferred to `0x0`), **including during contract creation.** - -## Rationale - -Using the existing `Transfer` event instead of creating a new event (e.g. `Creation`) allows this EIP to be backward compatible with existing indexers.E - -## Backwards Compatibility - -All contracts compliant with this EIP are compliant with ERC-721. However, not all contracts compliant with ERC-721 are compliant with this EIP. - -## Security Considerations - -No new security concerns. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6047.md diff --git a/EIPS/eip-6059.md b/EIPS/eip-6059.md index a07c6c3cd15b89..f7cae1e333f21c 100644 --- a/EIPS/eip-6059.md +++ b/EIPS/eip-6059.md @@ -1,480 +1 @@ ---- -eip: 6059 -title: Parent-Governed Nestable Non-Fungible Tokens -description: An interface for Nestable Non-Fungible Tokens with emphasis on parent token's control over the relationship. -author: Bruno Škvorc (@Swader), Cicada (@CicadaNCR), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/eip-6059-parent-governed-nestable-non-fungible-tokens/11914 -status: Final -type: Standards Track -category: ERC -created: 2022-11-15 -requires: 165, 721 ---- - -## Abstract - -The Parent-Governed Nestable NFT standard extends [ERC-721](./eip-721.md) by allowing for a new inter-NFT relationship and interaction. - -At its core, the idea behind the proposal is simple: the owner of an NFT does not have to be an Externally Owned Account (EOA) or a smart contract, it can also be an NFT. - -The process of nesting an NFT into another is functionally identical to sending it to another user. The process of sending a token out of another one involves issuing a transaction from the account owning the parent token. - -An NFT can be owned by a single other NFT, but can in turn have a number of NFTs that it owns. This proposal establishes the framework for the parent-child relationships of NFTs. A parent token is the one that owns another token. A child token is a token that is owned by another token. A token can be both a parent and child at the same time. Child tokens of a given token can be fully managed by the parent token's owner, but can be proposed by anyone. - -![Nestable tokens](../assets/eip-6059/img/eip-6059-nestable-tokens.png) - -The graph illustrates how a child token can also be a parent token, but both are still administered by the root parent token's owner. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability for tokens to own other tokens allows for greater utility, usability and forward compatibility. - -In the four years since [ERC-721](./eip-721.md) was published, the need for additional functionality has resulted in countless extensions. This ERC improves upon ERC-721 in the following areas: - -- [Bundling](#bundling) -- [Collecting](#collecting) -- [Membership](#membership) -- [Delegation](#delegation) - -### Bundling - -One of the most frequent uses of [ERC-721](./eip-721.md) is to disseminate the multimedia content that is tied to the tokens. In the event that someone wants to offer a bundle of NFTs from various collections, there is currently no easy way of bundling all of these together and handle their sale as a single transaction. This proposal introduces a standardized way of doing so. Nesting all of the tokens into a simple bundle and selling that bundle would transfer the control of all of the tokens to the buyer in a single transaction. - -### Collecting - -A lot of NFT consumers collect them based on countless criteria. Some aim for utility of the tokens, some for the uniqueness, some for the visual appeal, etc. There is no standardized way to group the NFTs tied to a specific account. By nesting NFTs based on their owner's preference, this proposal introduces the ability to do it. The root parent token could represent a certain group of tokens and all of the children nested into it would belong to it. - -The rise of soulbound, non-transferable, tokens, introduces another need for this proposal. Having a token with multiple soulbound traits (child tokens), allows for numerous use cases. One concrete example of this can be drawn from supply chains use case. A shipping container, represented by an NFT with its own traits, could have multiple child tokens denoting each leg of its journey. - -### Membership - -A common utility attached to NFTs is a membership to a Decentralised Autonomous Organization (DAO) or to some other closed-access group. Some of these organizations and groups occasionally mint NFTs to the current holders of the membership NFTs. With the ability to nest mint a token into a token, such minting could be simplified, by simply minting the bonus NFT directly into the membership one. - -### Delegation - -One of the core features of DAOs is voting and there are various approaches to it. One such mechanic is using fungible voting tokens where members can delegate their votes by sending these tokens to another member. Using this proposal, delegated voting could be handled by nesting your voting NFT into the one you are delegating your votes to and transferring it when the member no longer wishes to delegate their votes. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title EIP-6059 Parent-Governed Nestable Non-Fungible Tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-6059 -/// @dev Note: the ERC-165 identifier for this interface is 0x42b0e56f. - -pragma solidity ^0.8.16; - -interface IERC6059 /* is ERC165 */ { - /** - * @notice The core struct of ownership. - * @dev The `DirectOwner` struct is used to store information of the next immediate owner, be it the parent token, - * an `ERC721Receiver` contract or an externally owned account. - * @dev If the token is not owned by an NFT, the `tokenId` MUST equal `0`. - * @param tokenId ID of the parent token - * @param ownerAddress Address of the owner of the token. If the owner is another token, then the address MUST be - * the one of the parent token's collection smart contract. If the owner is externally owned account, the address - * MUST be the address of this account - */ - struct DirectOwner { - uint256 tokenId; - address ownerAddress; - } - - /** - * @notice Used to notify listeners that the token is being transferred. - * @dev Emitted when `tokenId` token is transferred from `from` to `to`. - * @param from Address of the previous immediate owner, which is a smart contract if the token was nested. - * @param to Address of the new immediate owner, which is a smart contract if the token is being nested. - * @param fromTokenId ID of the previous parent token. If the token was not nested before, the value MUST be `0` - * @param toTokenId ID of the new parent token. If the token is not being nested, the value MUST be `0` - * @param tokenId ID of the token being transferred - */ - event NestTransfer( - address indexed from, - address indexed to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 indexed tokenId - ); - - /** - * @notice Used to notify listeners that a new token has been added to a given token's pending children array. - * @dev Emitted when a child NFT is added to a token's pending array. - * @param tokenId ID of the token that received a new pending child token - * @param childIndex Index of the proposed child token in the parent token's pending children array - * @param childAddress Address of the proposed child token's collection smart contract - * @param childId ID of the child token in the child token's collection smart contract - */ - event ChildProposed( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - /** - * @notice Used to notify listeners that a new child token was accepted by the parent token. - * @dev Emitted when a parent token accepts a token from its pending array, migrating it to the active array. - * @param tokenId ID of the token that accepted a new child token - * @param childIndex Index of the newly accepted child token in the parent token's active children array - * @param childAddress Address of the child token's collection smart contract - * @param childId ID of the child token in the child token's collection smart contract - */ - event ChildAccepted( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - /** - * @notice Used to notify listeners that all pending child tokens of a given token have been rejected. - * @dev Emitted when a token removes all a child tokens from its pending array. - * @param tokenId ID of the token that rejected all of the pending children - */ - event AllChildrenRejected(uint256 indexed tokenId); - - /** - * @notice Used to notify listeners a child token has been transferred from parent token. - * @dev Emitted when a token transfers a child from itself, transferring ownership. - * @param tokenId ID of the token that transferred a child token - * @param childIndex Index of a child in the array from which it is being transferred - * @param childAddress Address of the child token's collection smart contract - * @param childId ID of the child token in the child token's collection smart contract - * @param fromPending A boolean value signifying whether the token was in the pending child tokens array (`true`) or - * in the active child tokens array (`false`) - */ - event ChildTransferred( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId, - bool fromPending - ); - - /** - * @notice The core child token struct, holding the information about the child tokens. - * @return tokenId ID of the child token in the child token's collection smart contract - * @return contractAddress Address of the child token's smart contract - */ - struct Child { - uint256 tokenId; - address contractAddress; - } - - /** - * @notice Used to retrieve the *root* owner of a given token. - * @dev The *root* owner of the token is the top-level owner in the hierarchy which is not an NFT. - * @dev If the token is owned by another NFT, it MUST recursively look up the parent's root owner. - * @param tokenId ID of the token for which the *root* owner has been retrieved - * @return owner The *root* owner of the token - */ - function ownerOf(uint256 tokenId) external view returns (address owner); - - /** - * @notice Used to retrieve the immediate owner of the given token. - * @dev If the immediate owner is another token, the address returned, MUST be the one of the parent token's - * collection smart contract. - * @param tokenId ID of the token for which the direct owner is being retrieved - * @return address Address of the given token's owner - * @return uint256 The ID of the parent token. MUST be `0` if the owner is not an NFT - * @return bool The boolean value signifying whether the owner is an NFT or not - */ - function directOwnerOf(uint256 tokenId) - external - view - returns ( - address, - uint256, - bool - ); - - /** - * @notice Used to burn a given token. - * @dev When a token is burned, all of its child tokens are recursively burned as well. - * @dev When specifying the maximum recursive burns, the execution MUST be reverted if there are more children to be - * burned. - * @dev Setting the `maxRecursiveBurn` value to 0 SHOULD only attempt to burn the specified token and MUST revert if - * there are any child tokens present. - * @param tokenId ID of the token to burn - * @param maxRecursiveBurns Maximum number of tokens to recursively burn - * @return uint256 Number of recursively burned children - */ - function burn(uint256 tokenId, uint256 maxRecursiveBurns) - external - returns (uint256); - - /** - * @notice Used to add a child token to a given parent token. - * @dev This adds the child token into the given parent token's pending child tokens array. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @dev This method MUST NOT be called directly. It MUST only be called from an instance of `IERC6059` as part of a - `nestTransfer` or `transferChild` to an NFT. - * @dev Requirements: - * - * - `directOwnerOf` on the child contract MUST resolve to the called contract. - * - the pending array of the parent contract MUST not be full. - * @param parentId ID of the parent token to receive the new child token - * @param childId ID of the new proposed child token - */ - function addChild(uint256 parentId, uint256 childId) external; - - /** - * @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of the child token to accept in the pending children array of a given token - * @param childAddress Address of the collection smart contract of the child token expected to be at the specified - * index - * @param childId ID of the child token expected to be located at the specified index - */ - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) external; - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev The children's ownership structures are not updated. - * @dev Requirements: - * - * - `parentId` MUST exist - * @param parentId ID of the parent token for which to reject all of the pending tokens - * @param maxRejections Maximum number of expected children to reject, used to prevent from - * rejecting children which arrive just before this operation. - */ - function rejectAllChildren(uint256 parentId, uint256 maxRejections) external; - - /** - * @notice Used to transfer a child token from a given parent token. - * @dev MUST remove the child from the parent's active or pending children. - * @dev When transferring a child token, the owner of the token MUST be set to `to`, or not updated in the event of `to` - * being the `0x0` address. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract - * @param childId ID of the child token in its own collection smart contract - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `to` - */ - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes data - ) external; - - /** - * @notice Used to retrieve the active child tokens of a given parent token. - * @dev Returns array of Child structs existing for parent token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the active child tokens - * @return struct[] An array of Child structs containing the parent token's active child tokens - */ - function childrenOf(uint256 parentId) - external - view - returns (Child[] memory); - - /** - * @notice Used to retrieve the pending child tokens of a given parent token. - * @dev Returns array of pending Child structs existing for given parent. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the pending child tokens - * @return struct[] An array of Child structs containing the parent token's pending child tokens - */ - function pendingChildrenOf(uint256 parentId) - external - view - returns (Child[] memory); - - /** - * @notice Used to retrieve a specific active child token for a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the child is being retrieved - * @param index Index of the child token in the parent token's active child tokens array - * @return struct A Child struct containing data about the specified child - */ - function childOf(uint256 parentId, uint256 index) - external - view - returns (Child memory); - - /** - * @notice Used to retrieve a specific pending child token from a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the pending child token is being retrieved - * @param index Index of the child token in the parent token's pending child tokens array - * @return struct A Child struct containing data about the specified child - */ - function pendingChildOf(uint256 parentId, uint256 index) - external - view - returns (Child memory); - - /** - * @notice Used to transfer the token into another token. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @param from Address of the direct owner of the token to be transferred - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token being transferred - * @param destinationId ID of the token to receive the token being transferred - */ - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId - ) external; -} -``` - -ID MUST never be a `0` value, as this proposal uses `0` values do signify that the token/destination is not an NFT. - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **How to name the proposal?**\ -In an effort to provide as much information about the proposal we identified the most important aspect of the proposal; the parent centered control over nesting. The child token's role is only to be able to be `Nestable` and support a token owning it. This is how we landed on the `Parent-Centered` part of the title. -2. **Why is automatically accepting a child using [EIP-712](./eip-712.md) permit-style signatures not a part of this proposal?**\ -For consistency. This proposal extends ERC-721 which already uses 1 transaction for approving operations with tokens. It would be inconsistent to have this and also support signing messages for operations with assets. -3. **Why use indexes?**\ -To reduce the gas consumption. If the token ID was used to find which token to accept or reject, iteration over arrays would be required and the cost of the operation would depend on the size of the active or pending children arrays. With the index, the cost is fixed. Lists of active and pending children per token need to be maintained, since methods to get them are part of the proposed interface.\ -To avoid race conditions in which the index of a token changes, the expected token ID as well as the expected token's collection smart contract is included in operations requiring token index, to verify that the token being accessed using the index is the expected one.\ -Implementation that would internally keep track of indices using mapping was attempted. The minimum cost of accepting a child token was increased by over 20% and the cost of minting has increased by over 15%. We concluded that it is not necessary for this proposal and can be implemented as an extension for use cases willing to accept the increased transaction cost this incurs. In the sample implementation provided, there are several hooks which make this possible. -4. **Why is the pending children array limited instead of supporting pagination?**\ -The pending child tokens array is not meant to be a buffer to collect the tokens that the root owner of the parent token wants to keep, but not enough to promote them to active children. It is meant to be an easily traversable list of child token candidates and should be regularly maintained; by either accepting or rejecting proposed child tokens. There is also no need for the pending child tokens array to be unbounded, because active child tokens array is.\ -Another benefit of having bounded child tokens array is to guard against spam and griefing. As minting malicious or spam tokens could be relatively easy and low-cost, the bounded pending array assures that all of the tokens in it are easy to identify and that legitimate tokens are not lost in a flood of spam tokens, if one occurs.\ -A consideration tied to this issue was also how to make sure, that a legitimate token is not accidentally rejected when clearing the pending child tokens array. We added the maximum pending children to reject argument to the clear pending child tokens array call. This assures that only the intended number of pending child tokens is rejected and if a new token is added to the pending child tokens array during the course of preparing such call and executing it, the clearing of this array SHOULD result in a reverted transaction. -5. **Should we allow tokens to be nested into one of its children?**\ -The proposal enforces that a parent token can't be nested into one of its child token, or downstream child tokens for that matter. A parent token and its children are all managed by the parent token's root owner. This means that if a token would be nested into one of its children, this would create the ownership loop and none of the tokens within the loop could be managed anymore. -6. **Why is there not a "safe" nest transfer method?**\ -`nestTransfer` is always "safe" since it MUST check for `IERC6059` compatibility on the destination. -7. **How does this proposal differ from the other proposals trying to address a similar problem?**\ -This interface allows for tokens to both be sent to and receive other tokens. The propose-accept and parent governed patterns allow for a more secure use. The backward compatibility is only added for ERC-721, allowing for a simpler interface. The proposal also allows for different collections to inter-operate, meaning that nesting is not locked to a single smart contract, but can be executed between completely separate NFT collections. - -### Propose-Commit pattern for child token management - -Adding child tokens to a parent token MUST be done in the form of propose-commit pattern to allow for limited mutability by a 3rd party. When adding a child token to a parent token, it is first placed in a *"Pending"* array, and MUST be migrated to the *"Active"* array by the parent token's root owner. The *"Pending"* child tokens array SHOULD be limited to 128 slots to prevent spam and griefing. - -The limitation that only the root owner can accept the child tokens also introduces a trust inherent to the proposal. This ensures that the root owner of the token has full control over the token. No one can force the user to accept a child if they don't want to. - -### Parent Governed pattern - -The parent NFT of a nested token and the parent's root owner are in all aspects the true owners of it. Once you send a token to another one you give up ownership. - -We continue to use ERC-721's `ownerOf` functionality which will now recursively look up through parents until it finds an address which is not an NFT, this is referred to as the *root owner*. Additionally we provide the `directOwnerOf` which returns the most immediate owner of a token using 3 values: the owner address, the tokenId which MUST be 0 if the direct owner is not an NFT, and a flag indicating whether or not the parent is an NFT. - -The root owner or an approved party MUST be able do the following operations on children: `acceptChild`, `rejectAllChildren` and `transferChild`. - -The root owner or an approved party MUST also be allowed to do these operations only when token is not owned by an NFT: `transferFrom`, `safeTransferFrom`, `nestTransferFrom`, `burn`. - -If the token is owned by an NFT, only the parent NFT itself MUST be allowed to execute the operations listed above. Transfers MUST be done from the parent token, using `transferChild`, this method in turn SHOULD call `nestTransferFrom` or `safeTransferFrom` in the child token's smart contract, according to whether the destination is an NFT or not. For burning, tokens must first be transferred to an EOA and then burned. - -We add this restriction to prevent inconsistencies on parent contracts, since only the `transferChild` method takes care of removing the child from the parent when it is being transferred out of it. - -### Child token management - -This proposal introduces a number of child token management functions. In addition to the permissioned migration from *"Pending"* to *"Active"* child tokens array, the main token management function from this proposal is the `tranferChild` function. The following state transitions of a child token are available with it: - -1. Reject child token -2. Abandon child token -3. Unnest child token -4. Transfer the child token to an EOA or an `ERC721Receiver` -5. Transfer the child token into a new parent token - -To better understand how these state transitions are achieved, we have to look at the available parameters passed to `transferChild`: - -```solidity - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes data - ) external; -``` - -Based on the desired state transitions, the values of these parameters have to be set accordingly (any parameters not set in the following examples depend on the child token being managed): - -1. **Reject child token**\ -![Reject child token](../assets/eip-6059/img/eip-6059-reject-child.png) -2. **Abandon child token**\ -![Abandon child token](../assets/eip-6059/img/eip-6059-abandon-child.png) -3. **Unnest child token**\ -![Unnest child token](../assets/eip-6059/img/eip-6059-unnest-child.png) -4. **Transfer the child token to an EOA or an `ERC721Receiver`**\ -![Transfer child token to EOA](../assets/eip-6059/img/eip-6059-transfer-child-to-eoa.png) -5. **Transfer the child token into a new parent token**\ -![Transfer child token to parent token](../assets/eip-6059/img/eip-6059-transfer-child-to-token.png)\ -This state change places the token in the pending array of the new parent token. The child token still needs to be accepted by the new parent token's root owner in order to be placed into the active array of that token. - -## Backwards Compatibility - -The Nestable token standard has been made compatible with [ERC-721](./eip-721.md) in order to take advantage of the robust tooling available for implementations of ERC-721 and to ensure compatibility with existing ERC-721 infrastructure. - -## Test Cases - -Tests are included in [`nestable.ts`](../assets/eip-6059/test/nestable.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-6059 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`NestableToken.sol`](../assets/eip-6059/contracts/NestableToken.sol). - - -## Security Considerations - -The same security considerations as with [ERC-721](./eip-721.md) apply: hidden logic may be present in any of the functions, including burn, add child, accept child, and more. - -Since the current owner of the token is allowed to manage the token, there is a possibility that after the parent token is listed for sale, the seller might remove a child token just before before the sale and thus the buyer would not receive the expected child token. This is a risk that is inherent to the design of this standard. Marketplaces should take this into account and provide a way to verify the expected child tokens are present when the parent token is being sold or to guard against such a malicious behaviour in another way. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6059.md diff --git a/EIPS/eip-6065.md b/EIPS/eip-6065.md index f36cf08b809a24..e30a2e060a9ddf 100644 --- a/EIPS/eip-6065.md +++ b/EIPS/eip-6065.md @@ -1,188 +1 @@ ---- -eip: 6065 -title: Real Estate Token -description: An interface for real estate NFTs that extends ERC-721 -author: Alex (@Alex-Klasma), Ben Fusek (@bfusek), Daniel Fallon-Cyr (@dfalloncyr) -discussions-to: https://ethereum-magicians.org/t/updated-eip-6065-real-estate-token/11936 -status: Review -type: Standards Track -category: ERC -created: 2022-11-29 -requires: 721 ---- - -## Abstract - -This proposal introduces an open structure for physical real estate and property to exist on the blockchain. This standard builds off of [ERC-721](./eip-721.md), adding important functionality necessary for representing real world assets such as real estate. The three objectives this standard aims to meet are: universal transferability of the NFT, private property rights attached to the NFT, and atomic transfer of property rights with the transfer of the NFT. The token contains a hash of the operating agreement detailing the NFT holder’s legal right to the property, unique identifiers for the property, a debt value and foreclosure status, and a manager address. - -## Motivation - -Real estate is the largest asset class in the world. By tokenizing real estate, barriers to entry are lowered, transaction costs are minimized, information asymmetry is reduced, ownership structures become more malleable, and a new building block for innovation is formed. However, in order to tokenize this asset class, a common standard is needed that accounts for its real world particularities while remaining flexible enough to adapt to various jurisdictions and regulatory environments. - -Ethereum tokens involving real world assets (RWAs) are notoriously tricky. This is because Ethereum tokens exist on-chain, while real estate exists off-chain. As such, the two are subject to entirely different consensus environments. For Ethereum tokens, consensus is reached through a formalized process of distributed validators. When a purely-digital NFT is transferred, the new owner has a cryptographic guarantee of ownership. For real estate, consensus is supported by legal contracts, property law, and enforced by the court system. With existing asset-backed ERC-721 tokens, a transfer of the token to another individual does not necessarily have any impact on the legal ownership of the physical asset. - -This standard attempts to solve the real world reconciliation issue, enabling real estate NFTs to function seamlessly on-chain, just like their purely-digital counterparts. - -## 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. - -In order to meet the above objectives and create an open standard for on-chain property ownership we have created a token structure that builds on the widely-used ERC-721 standard. - -### Token Components: - -1. Inherits ERC-721 - Allows for backwards compatibility with the most widely accepted NFT token standard. -2. operatingAgreementHashOf - immutable hash of the legal agreement detailing the right to ownership and conditions of use with regard to the property -3. Property Unique Identifiers - legal description (from physical deed), street address, GIS coordinates, parcel/tax ID, legal owning entity (on deed) -4. debtOf - readable debt value, currency, and foreclosure status of the NFT -5. managerOf - readable Ethereum address with managing control of property - -### Interfaces - -This EIP inherits the ERC-721 NFT token standard for all transfer and approval logic. All transfer and approval functions are inherited from this token standard without changes. Additionally, this EIP also inherits the ERC-721 Metadata standards for name, symbol, and metadata URI lookup. This allows an NFT under this EIP to become interoperable with preexisting NFT exchanges and services, however, some care must be taken. Please refer to [Backwards Compatibility](#backwards-compatibility) and [Security Considerations](#security-considerations). - - -#### Solidity Interface - -``` -pragma solidity ^0.8.13; - -import "forge-std/interfaces/IERC721.sol"; - -interface IERC6065 is IERC721 { - - // This event MUST emit if the asset is ever foreclosed. - event Foreclosed(uint256 id); - - /* - Next getter functions return immutable data for NFT. - */ - function legalDescriptionOf(uint256 _id) external view returns (string memory); - function addressOf(uint256 _id) external view returns (string memory); - function geoJsonOf(uint256 _id) external view returns (string memory); - function parcelIdOf(uint256 _id) external view returns (string memory); - function legalOwnerOf(uint256 _id) external view returns (string memory); - function operatingAgreementHashOf(uint256 _id) external view returns (bytes32); - - /* - Next getter function returns the debt denomination token of the NFT, the amount of debt (negative debt == credit), and if the underlying - asset backing the NFT has been foreclosed on. This should be utilized specifically for off-chain debt and required payments on the RWA asset. - It's recommended that administrators only use a single token type to denominate the debt. It's unrealistic to require integrating smart - contracts to implement possibly unbounded tokens denominating the off-chain debt of an asset. - - If the foreclosed status == true, then the RWA can be seen as severed from the NFT. The NFT is now "unbacked" by the RWA. - */ - function debtOf(uint256 _id) external view returns (address debtToken, int256 debtAmt, bool foreclosed); - - // Get the managerOf an NFT. The manager can have additional rights to the NFT or RWA on or off-chain. - function managerOf(uint256 _id) external view returns (address); -} -``` - -## Rationale - -### Introduction - -Real world assets operate in messy, non-deterministic environments. Because of this, validating the true state of an asset can be murky, expensive, or time-consuming. For example, in the U.S., change of property ownership is usually recorded at the County Recorder’s office, sometimes using pen and paper. It would be infeasible to continuously update this manual record every time an NFT transaction occurs on the blockchain. Additionally, since real world property rights are enforced by the court of law, it is essential that property ownership be documented in such a way that courts are able to interpret and enforce ownership if necessary. - -For these reasons, it is necessary to have a trusted party tasked with the responsibility of ensuring the state of the on-chain property NFT accurately mirrors its physical counterpart. By having an Administrator for the property who issues a legally-binding digital representation of the physical property, we are able to solve for both the atomic transfer of the property rights with the transfer of the NFT, as well as institute a seamless process for making the necessary payments and filings associated with property ownership. This is made possible by eliminating the change in legal ownership each time the NFT changes hands. An example Administrator legal structure implemented for property tokenization in the U.S. is provided in the [Reference Implementation](#reference-implementation). While a token that implements this standard must have a legal entity to conduct the off-chain dealings for the property, this implementation is not mandatory. - -### Guiding Objectives - -We have designed this EIP to achieve three primary objectives necessary for creating an NFT representation of physical real estate: - -#### 1. Real Estate NFTs are universally transferable - -A key aspect to private property is the right to transfer ownership to any legal person or entity that has the capacity to own that property. Therefore, an NFT representation of physical property should maintain that universal freedom of transfer. - -#### 2. All rights associated with property ownership are able to be maintained and guaranteed by the NFT - -The rights associated with private property ownership are the right to hold, occupy, rent, alter, resell, or transfer the property. It is essential that these same rights are able to be maintained and enforced with an NFT representation of real estate. - -#### 3. Property rights are transferred atomically with the transfer of the NFT - -Token ownership on any blockchain is atomic with the transfer of the digital token. To ensure the digital representation of a physical property is able to fully integrate the benefits of blockchain technology, it is essential the rights associated with the property are passed atomically with the transfer of the digital token. - -The following section specifies the technological components required to meet these three objectives. - -### operatingAgreementHashOf - -An immutable hash of the legal document issued by the legal entity that owns the property. The agreement is unique and contains the rights, terms, and conditions for the specific property represented by the NFT. The hash of the agreement attached to the NFT must be immutable to ensure the legitimacy and enforceability of these rights in the future for integrators or transferees. Upon transfer of the NFT, these legal rights are immediately enforceable by the new owner. For changes to the legal structure or rights and conditions with regard to the property the original token must be burned and a new token with the new hash must be minted. - -### Property Unique Identifiers - -The following unique identifiers of the property are contained within the NFT and are immutable: - -`legalDescriptionOf`: written description of the property taken from the physical property deed -`addressOf`: street address of the property -`geoJsonOf`: the GeoJSON format of the property’s geospatial coordinates -`parcelIdOf`: ID number used to identify the property by the local authority -`legalOwnerOf`: the legal entity that is named on the verifiable physical deed - -These unique identifiers ensure the physical property in question is clear and identifiable. These strings must be immutable to make certain that the identity of the property can not be changed in the future. This is necessary to provide confidence in the NFT holder in the event a dispute about the property arises. - -These identifiers, especially `legalOwnerOf`, allow for individuals to verify off-chain ownership and legitimacy of the legal agreement. These verification checks could be integrated with something like Chainlink functions in the future to be simplified and automatic. - -### debtOf - -A readable value of debt and denoted currency that is accrued to the property. A positive balance signifies a debt against the property, while a negative balance signifies a credit which can be claimed by the NFT owner. This is a way for the property administrator to charge the NFT holder for any necessary payments towards the property, like property tax, or other critical repairs or maintenance in the "real world". A credit might be given to the NFT holder via this same function, perhaps the administrator and the NFT holder had worked out a property management or tenancy revenue-sharing agreement. - -The `debtOf` function also returns the boolean foreclosure status of the asset represented by the NFT. A true result indicates the associated property is no longer backing the NFT, a false result indicates the associated property is still backing the NFT. An administrator can foreclose an asset for any reason as specified in the `Operating Agreement`, an example would be excessive unpaid debts. Smart contracts can check the foreclosure state by calling this function. If the asset is foreclosed, it should be understood that the RWA backing the NFT has been removed, and smart contracts should take this into account when doing any valuations or other calculations. - -There are no standard requirements for how these values are updated as those details will be decided by the implementor. This EIP does however standardize how these values are indicated and read for simplicity of integration. - -### managerOf - -A readable Ethereum address that can be granted a right to action on the property without being the underlying owner of the NFT. - -This function allows the token to be owned by one Ethereum address while granting particular rights to another. This enables protocols and smart contracts to own the underlying asset, such as a lending protocol, but still allow another Ethereum address, such as a depositor, to action on the NFT via other integrations, for example the Administrator management portal. The standard does not require a specific implementation of the manager role, only the value is required. In many instances the managerOf value will be the same as the owning address of the NFT. - -## Backwards Compatibility - -This EIP is backwards compatible with ERC-721. However, it is important to note that there are potential implementation considerations to take into account before any smart contract integration. See [Security Considerations](#security-considerations) for more details. - -## Reference Implementation - -Klasma Labs offers a work in progress [reference implementation](../assets/eip-6065/Implementation.sol). The technical implementation includes the following additional components for reference, this implementation is not required. - -Summary of this implementation: - -* NFT burn and mint function -* Immutable NFT data (unique identifiers and operating agreement hash) -* Simple debt tracking by Administrator -* Blocklist function to freeze asset held by fraudulent addresses (NOTE: to be implemented in the future) -* Simple foreclosure logic initiated by Administrator -* `managerOf` function implementation to chain this call to other supported smart contracts - -### Legal Structure Implementation - -This section explains the legal structure and implementation a company may employ as an Administrator of this token. The structure detailed below is specific to property tokenization in the U.S. in the 2023 regulatory environment. - -This section details an implementation of the legal standard that could be used by a company specifically for property tokenization in the U.S. in the 2022 regulatory environment. - -![Corporate Structure Image](../assets/eip-6065/corporate-structure.png) - - -The legal structure for this token is as follows: - -* A parent company and property Administrator, owns a bankruptcy remote LLC for each individual property they act as Administrator for. -* The bankruptcy remote LLC is the owner and manager of a DAO LLC. The DAO LLC is on the title and deed and issues the corresponding NFT and operating agreement for the property. -* This structure enables the following three outcomes: - - 1. Homeowners are shielded from any financial stress or bankruptcy their physical asset Administrator encounters. In the event of an Administrator bankruptcy or dissolution the owner of the NFT is entitled to transfer of the DAO LLC, or the sale and distribution of proceeds from the property. - 2. Transfer of the rights to the property are atomic with the transfer of the NFT. The NFT represents a right to claim the asset and have the title transferred to the NFT owner, as well as the right to use the asset. This ensures the rights to the physical property are passed digitally with the transfer of the NFT, without having to update the legal owner of the property after each transfer. - -Security note: In the event of a private key hack the company will likely not be able to reissue a Home NFT. Home NFT owners who are not confident in their ability to safely store their home NFT will have varying levels of security options (multi-sigs, custodians, etc.). For public, large protocol hacks, the company may freeze the assets using the Blocklist function and reissue the home NFTs to the original owners. Blocklist functionality is to-be-implemented in the reference implementation above. - -## Security Considerations - -The following are checks and recommendations for protocols integrating NFTs under this standard. These are of particular relevance to applications which lend against any asset utilizing this standard. - -* Protocol integrators are recommended to check that the unique identifiers for the property and the hash of the operating agreement are immutable for the specific NFTs they wish to integrate. For correct implementation of this standard these values must be immutable to ensure legitimacy for future transferees. -* Protocol integrators are recommended to check the debtOf value for an accurate representation of the value of this token. -* Protocol integrators are recommended to check the foreclose status to ensure this token is still backed by the asset it was originally tied to. -* For extra risk mitigation protocol integrators can implement a time-delay before performing irreversible actions. This is to protect against potential asset freezes if a hacked NFT is deposited into the protocol. Asset freezes are non-mandatory and subject to the implementation of the asset Administrator. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6065.md diff --git a/EIPS/eip-6066.md b/EIPS/eip-6066.md index 0b72c77d6374a5..8bfa3e633be1ef 100644 --- a/EIPS/eip-6066.md +++ b/EIPS/eip-6066.md @@ -1,139 +1 @@ ---- -eip: 6066 -title: Signature Validation Method for NFTs -description: A way to verify signatures when the signing entity is an ERC-721 or ERC-1155 NFT -author: Jack Boyuan Xu (@boyuanx) -discussions-to: https://ethereum-magicians.org/t/eip-6066-signature-validation-method-for-nfts/11943 -status: Final -type: Standards Track -category: ERC -created: 2022-11-29 -requires: 165, 721, 1155, 1271, 5750 ---- - -## Abstract - -While **E**xternally **O**wned **A**ccounts can validate signed messages with `ecrecover()` and smart contracts can validate signatures using specifications outlined in [ERC-1271](./eip-1271.md), currently there is no standard method to create or validate signatures made by NFTs. We propose a standard way for anyone to validate whether a signature made by an NFT is valid. This is possible via a modified signature validation function originally found in [ERC-1271](./eip-1271.md): `isValidSignature(tokenId, hash, data)`. - -## Motivation - -With billions of ETH in trading volume, the **N**on-**F**ungible **T**oken standard has exploded into tremendous popularity in recent years. Despite the far-reaching implications of having unique tokenized items on-chain, NFTs have mainly been used to represent artwork in the form of avatars or profile pictures. While this is certainly not a trivial use case for the [ERC-721](./eip-721.md) & [ERC-1155](./eip-1155.md) token standards, we reckon more can be done to aid the community in discovering alternative uses for NFTs. - -One of the alternative use cases for NFTs is using them to represent offices in an organization. In this case, tying signatures to transferrable NFTs instead of EOAs or smart contracts becomes crucial. Suppose there exists a DAO that utilizes NFTs as badges that represent certain administrative offices (i.e., CEO, COO, CFO, etc.) with a quarterly democratic election that potentially replaces those who currently occupy said offices. If the sitting COO has previously signed agreements or authorized certain actions, their past signatures would stay with the EOA who used to be the COO instead of the COO's office itself once they are replaced with another EOA as the new COO-elect. Although a multisig wallet for the entire DAO is one way to mitigate this problem, often it is helpful to generate signatures on a more intricate level so detailed separation of responsibilities are established and maintained. It is also feasible to appoint a smart contract instead of an EOA as the COO, but the complexities this solution brings are unnecessary. If a DAO uses ENS to establish their organizational hierarchy, this proposal would allow wrapped ENS subdomains (which are NFTs) to generate signatures. - -## 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. - -``` -pragma solidity ^0.8.0; - -interface IERC6066 { - /** - * @dev MUST return if the signature provided is valid for the provided tokenId and hash - * @param tokenId Token ID of the signing NFT - * @param hash Hash of the data to be signed - * @param data OPTIONAL arbitrary data that may aid verification - * - * MUST return the bytes4 magic value 0x12edb34f when function passes. - * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) - * MUST allow external calls - * - */ - function isValidSignature( - uint256 tokenId, - bytes32 hash, - bytes calldata data - ) external view returns (bytes4 magicValue); -} -``` - -`isValidSignature` can call arbitrary methods to validate a given signature. - -This function MAY be implemented by [ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md) compliant contracts that desire to enable its token holders to sign messages using their NFTs. Compliant callers wanting to support contract signatures MUST call this method if the signer is the holder of an NFT ([ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md)). - -## Rationale - -We have purposefully decided to not include a signature generation standard in this proposal as it would restrict flexibility of such mechanism, just as [ERC-1271](./eip-1271.md) does not enforce a signing standard for smart contracts. We also decided to reference Gnosis Safe's contract signing approach as it is both simplistic and proven to be adequate. The `bytes calldata data` parameter is considered optional if extra data is needed for signature verification, also conforming this EIP to [ERC-5750](./eip-5750.md) for future-proofing purposes. - -## Backwards Compatibility - -This EIP is incompatible with previous work on signature validation as it does not validate any cryptographically generated signatures. Instead, signature is merely a boolean flag indicating consent. This is consistent with Gnosis Safe's contract signature implementation. - -## Reference Implementation - -Example implementation of an [ERC-721](./eip-721.md) compliant contract that conforms to [ERC-6066](./eip-6066.md) with a custom signing function: - -``` -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./interfaces/IERC6066.sol"; - -contract ERC6066Reference is ERC721, IERC6066 { - // type(IERC6066).interfaceId - bytes4 public constant MAGICVALUE = 0x12edb34f; - bytes4 public constant BADVALUE = 0xffffffff; - - mapping(uint256 => mapping(bytes32 => bool)) internal _signatures; - - error ENotTokenOwner(); - - /** - * @dev Checks if the sender owns NFT with ID tokenId - * @param tokenId Token ID of the signing NFT - */ - modifier onlyTokenOwner(uint256 tokenId) { - if (ownerOf(tokenId) != _msgSender()) revert ENotTokenOwner(); - _; - } - - constructor(string memory name_, string memory symbol_) - ERC721(name_, symbol_) - {} - - /** - * @dev SHOULD sign the provided hash with NFT of tokenId given sender owns said NFT - * @param tokenId Token ID of the signing NFT - * @param hash Hash of the data to be signed - */ - function sign(uint256 tokenId, bytes32 hash) - external - onlyTokenOwner(tokenId) - { - _signatures[tokenId][hash] = true; - } - - /** - * @dev MUST return if the signature provided is valid for the provided tokenId, hash, and optionally data - */ - function isValidSignature(uint256 tokenId, bytes32 hash, bytes calldata data) - external - view - override - returns (bytes4 magicValue) - { - // The data parameter is unused in this example - return _signatures[tokenId][hash] ? MAGICVALUE : BADVALUE; - } - - /** - * @dev ERC-165 support - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override returns (bool) { - return - interfaceId == type(IERC6066).interfaceId || - super.supportsInterface(interfaceId); - } -} -``` - -## Security Considerations - -The revokable nature of contract-based signatures carries over to this EIP. Developers and users alike should take it into consideration. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6066.md diff --git a/EIPS/eip-6093.md b/EIPS/eip-6093.md index 5ac779721e92e0..7e671a95cb9ef0 100644 --- a/EIPS/eip-6093.md +++ b/EIPS/eip-6093.md @@ -1,429 +1 @@ ---- -eip: 6093 -title: Custom errors for commonly-used tokens -description: Lists custom errors for common token implementations -author: Ernesto García (@ernestognw), Francisco Giordano (@frangio), Hadrien Croubois (@Amxx) -discussions-to: https://ethereum-magicians.org/t/eip-6093-custom-errors-for-erc-tokens/12043 -status: Last Call -last-call-deadline: 2023-08-15 -type: Standards Track -category: ERC -created: 2022-12-06 -requires: 20, 721, 1155 ---- - -## Abstract - -This EIP defines a standard set of custom errors for commonly-used tokens, which are defined as [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), and [ERC-1155](./eip-1155.md) tokens. - -Ethereum applications and wallets have historically relied on revert reason strings to display the cause of transaction errors to users. Recent Solidity versions offer rich revert reasons with error-specific decoding (sometimes called "custom errors"). This EIP defines a standard set of errors designed to give at least the same relevant information as revert reason strings, but in a structured and expected way that clients can implement decoding for. - -## Motivation - -Since the introduction of Solidity custom errors in v0.8.4, these have provided a way to show failures in a more expressive and gas efficient manner with dynamic arguments, while reducing deployment costs. - -However, [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md) were already finalized when custom errors were released, so no errors are included in their specification. - -Standardized errors allow users to expect more consistent error messages across applications or testing environments, while exposing pertinent arguments and overall reducing the need of writing expensive revert strings in the deployment bytecode. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -The following errors were designed according to the criteria described in [Rationale](#rationale). - -This EIP defines standard errors that may be used by implementations in certain scenarios but it does not specify whether implementations should revert in those scenarios, which remains up to the implementers unless a revert is mandated by the corresponding EIPs. - -The names of the error arguments are defined in the [Parameter Glossary](#parameter-glossary) and MUST be used according to those definitions. - -### [ERC-20](./eip-20.md) - -#### `ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed)` - -Indicates an error related to the current `balance` of a `sender`. -Used in transfers. - -Usage guidelines: - -- `balance` MUST be less than `needed`. - -#### `ERC20InvalidSender(address sender)` - -Indicates a failure with the token `sender`. -Used in transfers. - -Usage guidelines: - -- RECOMMENDED for disallowed transfers from the zero address. -- MUST NOT be used for approval operations. -- MUST NOT be used for balance or allowance requirements. - - Use `ERC20InsufficientBalance` or `ERC20InsufficientAllowance` instead. - -#### `ERC20InvalidReceiver(address receiver)` - -Indicates a failure with the token `receiver`. -Used in transfers. - -Usage guidelines: - -- RECOMMENDED for disallowed transfers to the zero address. -- RECOMMENDED for disallowed transfers to non-compatible addresses (eg. contract addresses). -- MUST NOT be used for approval operations. - -#### `ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed)` - -Indicates a failure with the `spender`'s `allowance`. -Used in transfers. - -Usage guidelines: - -- `allowance` MUST be less than `needed`. - -#### `ERC20InvalidApprover(address approver)` - -Indicates a failure with the `approver` of a token to be approved. -Used in approvals. - -Usage guidelines: - -- RECOMMENDED for disallowed approvals from the zero address. -- MUST NOT be used for transfer operations. - -#### `ERC20InvalidSpender(address spender)` - -Indicates a failure with the `spender` to be approved. -Used in approvals. - -Usage guidelines: - -- RECOMMENDED for disallowed approvals to the zero address. -- RECOMMENDED for disallowed approvals to the owner itself. -- MUST NOT be used for transfer operations. - - Use `ERC20InsufficientAllowance` instead. - -### [ERC-721](./eip-721.md) - -#### `ERC721InvalidOwner(address owner)` - -Indicates that an address can't be an owner. -Used in balance queries. - -Usage guidelines: - -- RECOMMENDED for addresses whose ownership is disallowed (eg. ERC-721 explicitly disallows `address(0)` to be an owner). -- MUST NOT be used for transfers. - - Use `ERC721IncorrectOwner` instead. - -#### `ERC721NonexistentToken(uint256 tokenId)` - -Indicates a `tokenId` whose `owner` is the zero address. - -Usage guidelines: - -- The `tokenId` MUST BE a non-minted or burned token. - -#### `ERC721IncorrectOwner(address sender, uint256 tokenId, address owner)` - -Indicates an error related to the ownership over a particular token. -Used in transfers. - -Usage guidelines: - -- `sender` MUST NOT be `owner`. -- MUST NOT be used for approval operations. - -#### `ERC721InvalidSender(address sender)` - -Indicates a failure with the token `sender`. -Used in transfers. - -Usage guidelines: - -- RECOMMENDED for disallowed transfers from the zero address. -- MUST NOT be used for approval operations. -- MUST NOT be used for ownership or approval requirements. - - Use `ERC721IncorrectOwner` or `ERC721InsufficientApproval` instead. - -#### `ERC721InvalidReceiver(address receiver)` - -Indicates a failure with the token `receiver`. -Used in transfers. - -Usage guidelines: - -- RECOMMENDED for disallowed transfers to the zero address. -- RECOMMENDED for disallowed transfers to non-`ERC721TokenReceiver` contracts or those that reject a transfer. (eg. returning an invalid response in `onERC721Received`). -- MUST NOT be used for approval operations. - -#### `ERC721InsufficientApproval(address operator, uint256 tokenId)` - -Indicates a failure with the `operator`'s approval. -Used in transfers. - -Usage guidelines: - -- `isApprovedForAll(owner, operator)` MUST be false for the `tokenId`'s owner and `operator`. -- `getApproved(tokenId)` MUST not be `operator`. - -#### `ERC721InvalidApprover(address approver)` - -Indicates a failure with the `owner` of a token to be approved. -Used in approvals. - -Usage guidelines: - -- RECOMMENDED for disallowed approvals from the zero address. -- MUST NOT be used for transfer operations. - -#### `ERC721InvalidOperator(address operator)` - -Indicates a failure with the `operator` to be approved. -Used in approvals. - -Usage guidelines: - -- RECOMMENDED for disallowed approvals to the zero address. -- The `operator` MUST NOT be the owner of the approved token. -- MUST NOT be used for transfer operations. - - Use `ERC721InsufficientApproval` instead. - -### [ERC-1155](./eip-1155.md) - -#### `ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId)` - -Indicates an error related to the current `balance` of a `sender`. -Used in transfers. - -Usage guidelines: - -- `balance` MUST be less than `needed` for a `tokenId`. - -#### `ERC1155InvalidSender(address sender)` - -Indicates a failure with the token `sender`. -Used in transfers. - -Usage guidelines: - -- RECOMMENDED for disallowed transfers from the zero address. -- MUST NOT be used for approval operations. -- MUST NOT be used for balance or allowance requirements. - - Use `ERC1155InsufficientBalance` or `ERC1155MissingApprovalForAll` instead. - -#### `ERC1155InvalidReceiver(address receiver)` - -Indicates a failure with the token `receiver`. -Used in transfers. - -Usage guidelines: - -- RECOMMENDED for disallowed transfers to the zero address. -- RECOMMENDED for disallowed transfers to non-`ERC1155TokenReceiver` contracts or those that reject a transfer. (eg. returning an invalid response in `onERC1155Received`). -- MUST NOT be used for approval operations. - -#### `ERC1155MissingApprovalForAll(address operator, address owner)` - -Indicates a failure with the `operator`'s approval in a transfer. -Used in transfers. - -Usage guidelines: - -- `isApprovedForAll(owner, operator)` MUST be false for the `tokenId`'s owner and `operator`. - -#### `ERC1155InvalidApprover(address approver)` - -Indicates a failure with the `approver` of a token to be approved. -Used in approvals. - -Usage guidelines: - -- RECOMMENDED for disallowed approvals from the zero address. -- MUST NOT be used for transfer operations. - -#### `ERC1155InvalidOperator(address operator)` - -Indicates a failure with the `operator` to be approved. -Used in approvals. - -Usage guidelines: - -- RECOMMENDED for disallowed approvals to the zero address. -- MUST be used for disallowed approvals to the owner itself. -- MUST NOT be used for transfer operations. - - Use `ERC1155InsufficientApproval` instead. - -#### `ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength)` - -Indicates an array length mismatch between `ids` and `values` in a `safeBatchTransferFrom` operation. -Used in batch transfers. - -Usage guidelines: - -- `idsLength` MUST NOT be `valuesLength`. - -### Parameter Glossary - -| Name | Description | -| ----------- | --------------------------------------------------------------------------- | -| `sender` | Address whose tokens are being transferred. | -| `balance` | Current balance for the interacting account. | -| `needed` | Minimum amount required to perform an action. | -| `receiver` | Address to which tokens are being transferred. | -| `spender` | Address that may be allowed to operate on tokens without being their owner. | -| `allowance` | Amount of tokens a `spender` is allowed to operate with. | -| `approver` | Address initiating an approval operation. | -| `tokenId` | Identifier number of a token. | -| `owner` | Address of the current owner of a token. | -| `operator` | Same as `spender`. | -| `*Length` | Array length for the prefixed parameter. | - -### Error additions - -Any addition to this EIP or implementation-specific errors (such as extensions) SHOULD follow the guidelines presented in the [rationale](#rationale) section to keep consistency. - -## Rationale - -The chosen objectives for a standard for token errors are to provide context about the error, and to make moderate use of meaningful arguments (to maintain the code size benefits with respect to strings). - -Considering this, the error names are designed following a basic grammatical structure based on the standard actions that can be performed on each token and the [subjects](#actions-and-subjects) involved. - -### Actions and subjects - -An error is defined based on the following **actions** that can be performed on a token and its involved _subjects_: - -- **Transfer**: An operation in which a _sender_ moves to a _receiver_ any number of tokens (fungible _balance_ and/or non-fungible _token ids_). -- **Approval**: An operation in which an _approver_ grants any form of _approval_ to an _operator_. - -These attempt to exhaustively represent what can go wrong in a token operation. Therefore, the errors can be constructed by specifying which _subject_ failed during an **action** execution, and prefixing with an [error prefix](#error-prefixes). - -Note that the action is never seen as the subject of an error. - -If a subject is called different on a particular token standard, the error should be consistent with the standard's naming convention. - -### Error prefixes - -An error prefix is added to a subject to derive a concrete error condition. -Developers can think about an error prefix as the _why_ an error happened. - -A prefix can be `Invalid` for general incorrectness, or more specific like `Insufficient` for amounts. - -### Domain - -Each error's arguments may vary depending on the token domain. If there are errors with the same name and different arguments, the Solidity compiler currently fails with a `DeclarationError`. - -An example of this is: - -```solidity -InsufficientApproval(address spender, uint256 allowance, uint256 needed); -InsufficientApproval(address operator, uint256 tokenId); -``` - -For that reason, a domain prefix is proposed to avoid declaration clashing, which is the name of the ERC and its corresponding number appended at the beginning. - -Example: - -```solidity -ERC20InsufficientApproval(address spender, uint256 allowance, uint256 needed); -ERC721InsufficientApproval(address operator, uint256 tokenId); -``` - -### Arguments - -The selection of arguments depends on the subject involved, and it should follow the order presented below: - -1. _Who_ is involved with the error (eg. `address sender`) -2. _What_ failed (eg. `uint256 allowance`) -3. _Why_ it failed, expressed in additional arguments (eg. `uint256 needed`) - -A particular argument may fall into overlapping categories (eg. _Who_ may also be _What_), so not all of these will be present but the order shouldn't be broken. - -Some tokens may need a `tokenId`. This is suggested to include at the end as additional information instead of as a subject. - -### Error grammar rules - -Given the above, we can summarize the construction of error names with a grammar that errors will follow: - -``` -(); -``` - -Where: - -- _Domain_: `ERC20`, `ERC721` or `ERC1155`. Although other token standards may be suggested if not considered in this EIP. -- _ErrorPrefix_: `Invalid`, `Insufficient`, or another if it's more appropriate. -- _Subject_: `Sender`, `Receiver`, `Balance`, `Approver`, `Operator`, `Approval` or another if it's more appropriate, and must make adjustments based on the domain's naming convention. -- _Arguments_: Follow the [_who_, _what_ and _why_ order](#arguments). - -## Backwards Compatibility - -Tokens already deployed rely mostly on revert strings and make use of `require` instead of custom errors. Even most of the newly deployed tokens since Solidity's v0.8.4 release inherit from implementations using revert strings. - -This EIP can not be enforced on non-upgradeable already deployed tokens, however, these tokens generally use similar conventions with small variations such as: - -- including/removing the [domain](#domain). -- using different [error prefixes](#error-prefixes). -- including similar [subjects](#actions-and-subjects). -- changing the grammar order. - -Upgradeable contracts MAY be upgraded to implement this EIP. - -Implementers and DApp developers that implement special support for tokens that are compliant with this EIP, SHOULD tolerate different errors emitted by non-compliant contracts, as well as classic revert strings. - -## Reference Implementation - -### Solidity - -```solidity -pragma solidity ^0.8.4; - -/// @title Standard ERC20 Errors -/// @dev See https://eips.ethereum.org/EIPS/eip-20 -/// https://eips.ethereum.org/EIPS/eip-6093 -interface ERC20Errors { - error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); - error ERC20InvalidSender(address sender); - error ERC20InvalidReceiver(address receiver); - error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); - error ERC20InvalidApprover(address approver); - error ERC20InvalidSpender(address spender); -} - -/// @title Standard ERC721 Errors -/// @dev See https://eips.ethereum.org/EIPS/eip-721 -/// https://eips.ethereum.org/EIPS/eip-6093 -interface ERC721Errors { - error ERC721InvalidOwner(address owner); - error ERC721NonexistentToken(uint256 tokenId); - error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner); - error ERC721InvalidSender(address sender); - error ERC721InvalidReceiver(address receiver); - error ERC721InsufficientApproval(address operator, uint256 tokenId); - error ERC721InvalidApprover(address approver); - error ERC721InvalidOperator(address operator); -} - -/// @title Standard ERC1155 Errors -/// @dev See https://eips.ethereum.org/EIPS/eip-1155 -/// https://eips.ethereum.org/EIPS/eip-6093 -interface ERC1155Errors { - error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); - error ERC1155InvalidSender(address sender); - error ERC1155InvalidReceiver(address receiver); - error ERC1155MissingApprovalForAll(address operator, address owner) - error ERC1155InvalidApprover(address approver); - error ERC1155InvalidOperator(address operator); - error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); -} -``` - -## Security Considerations - -There are no known signature hash collisions for the specified errors. - -Tokens upgraded to implement this EIP may break assumptions in other systems relying on revert strings. - -Offchain applications should be cautious when dealing with untrusted contracts that may revert using these custom errors. For instance, if a user interface prompts actions based on error decoding, malicious contracts could exploit this to encourage untrusted and potentially harmful operations. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6093.md diff --git a/EIPS/eip-6105.md b/EIPS/eip-6105.md index 5c30480c1ceb39..7b07b622c1e3f4 100644 --- a/EIPS/eip-6105.md +++ b/EIPS/eip-6105.md @@ -1,570 +1 @@ ---- -eip: 6105 -title: No Intermediary NFT Trading Protocol -description: Adds a marketplace functionality with more diverse royalty schemes to ERC-721 -author: 5660-eth (@5660-eth), Silvere Heraudeau (@lambdalf-dev), Martin McConnell (@offgridgecko), Abu , Wizard Wang -discussions-to: https://ethereum-magicians.org/t/eip6105-no-intermediary-nft-trading-protocol/12171 -status: Final -type: Standards Track -category: ERC -created: 2022-12-02 -requires: 20, 165, 721, 2981 ---- - -## Abstract - -This ERC adds a marketplace functionality to [ERC-721](./eip-721.md) to enable non-fungible token trading without relying on an intermediary trading platform. At the same time, creators may implement more diverse royalty schemes. - -## Motivation - -Most current NFT trading relies on an NFT trading platform acting as an intermediary, which has the following problems: - -1. Security concerns arise from authorization via the `setApprovalForAll` function. The permissions granted to NFT trading platforms expose unnecessary risks. Should a problem occur with the trading platform contract, it would result in significant losses to the industry as a whole. Additionally, if a user has authorized the trading platform to handle their NFTs, it allows a phishing scam to trick the user into signing a message that allows the scammer to place an order at a low price on the NFT trading platform and designate themselves as the recipient. This can be difficult for ordinary users to guard against. -2. High trading costs are a significant issue. On one hand, as the number of trading platforms increases, the liquidity of NFTs becomes dispersed. If a user needs to make a deal quickly, they must authorize and place orders on multiple platforms, which increases the risk exposure and requires additional gas expenditures for each authorization. For example, taking BAYC as an example, with a total supply of 10,000 and over 6,000 current holders, the average number of BAYC held by each holder is less than 2. While `setApprovalForAll` saves on gas expenditure for pending orders on a single platform, authorizing multiple platforms results in an overall increase in gas expenditures for users. On the other hand, trading service fees charged by trading platforms must also be considered as a cost of trading, which are often much higher than the required gas expenditures for authorization. -3. Aggregators provide a solution by aggregating liquidity, but the decision-making process is centralized. Furthermore, as order information on trading platforms is off-chain, the aggregator's efficiency in obtaining data is affected by the frequency of the trading platform's API and, at times, trading platforms may suspend the distribution of APIs and limit their frequency. -4. The project parties' royalty income is dependent on centralized decision-making by NFT trading platforms. Some trading platforms implement optional royalty without the consent of project parties, which is a violation of their interests. -5. NFT trading platforms are not resistant to censorship. Some platforms have delisted a number of NFTs and the formulation and implementation of delisting rules are centralized and not transparent enough. In the past, some NFT trading platforms have failed and wrongly delisted certain NFTs, leading to market panic. - -## 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. - -Compliant contracts MUST implement the following interface: - -```solidity -interface IERC6105 { - - /// @notice Emitted when a token is listed for sale or delisted - /// @dev The zero `salePrice` indicates that the token is not for sale - /// The zero `expires` indicates that the token is not for sale - /// @param tokenId - identifier of the token being listed - /// @param from - address of who is selling the token - /// @param salePrice - the price the token is being sold for - /// @param expires - UNIX timestamp, the buyer could buy the token before expires - /// @param supportedToken - contract addresses of supported token or zero address - /// The zero address indicates that the supported token is ETH - /// Buyer needs to purchase item with supported token - /// @param benchmarkPrice - Additional price parameter, may be used when calculating royalties - event UpdateListing( - uint256 indexed tokenId, - address indexed from, - uint256 salePrice, - uint64 expires, - address supportedToken, - uint256 benchmarkPrice - ); - - /// @notice Emitted when a token is being purchased - /// @param tokenId - identifier of the token being purchased - /// @param from - address of who is selling the token - /// @param to - address of who is buying the token - /// @param salePrice - the price the token is being sold for - /// @param supportedToken - contract addresses of supported token or zero address - /// The zero address indicates that the supported token is ETH - /// Buyer needs to purchase item with supported token - /// @param royalties - The amount of royalties paid on this purchase - event Purchased( - uint256 indexed tokenId, - address indexed from, - address indexed to, - uint256 salePrice, - address supportedToken, - uint256 royalties - ); - - /// @notice Create or update a listing for `tokenId` - /// @dev `salePrice` MUST NOT be set to zero - /// @param tokenId - identifier of the token being listed - /// @param salePrice - the price the token is being sold for - /// @param expires - UNIX timestamp, the buyer could buy the token before expires - /// @param supportedToken - contract addresses of supported token or zero address - /// The zero address indicates that the supported token is ETH - /// Buyer needs to purchase item with supported token - /// Requirements: - /// - `tokenId` must exist - /// - Caller must be owner, authorised operators or approved address of the token - /// - `salePrice` must not be zero - /// - `expires` must be valid - /// - Must emit an {UpdateListing} event. - function listItem( - uint256 tokenId, - uint256 salePrice, - uint64 expires, - address supportedToken - ) external; - - /// @notice Create or update a listing for `tokenId` with `benchmarkPrice` - /// @dev `salePrice` MUST NOT be set to zero - /// @param tokenId - identifier of the token being listed - /// @param salePrice - the price the token is being sold for - /// @param expires - UNIX timestamp, the buyer could buy the token before expires - /// @param supportedToken - contract addresses of supported token or zero address - /// The zero address indicates that the supported token is ETH - /// Buyer needs to purchase item with supported token - /// @param benchmarkPrice - Additional price parameter, may be used when calculating royalties - /// Requirements: - /// - `tokenId` must exist - /// - Caller must be owner, authorised operators or approved address of the token - /// - `salePrice` must not be zero - /// - `expires` must be valid - /// - Must emit an {UpdateListing} event. - function listItem( - uint256 tokenId, - uint256 salePrice, - uint64 expires, - address supportedToken, - uint256 benchmarkPrice - ) external; - - /// @notice Remove the listing for `tokenId` - /// @param tokenId - identifier of the token being delisted - /// Requirements: - /// - `tokenId` must exist and be listed for sale - /// - Caller must be owner, authorised operators or approved address of the token - /// - Must emit an {UpdateListing} event - function delistItem(uint256 tokenId) external; - - /// @notice Buy a token and transfer it to the caller - /// @dev `salePrice` and `supportedToken` must match the expected purchase price and token to prevent front-running attacks - /// @param tokenId - identifier of the token being purchased - /// @param salePrice - the price the token is being sold for - /// @param supportedToken - contract addresses of supported token or zero address - /// Requirements: - /// - `tokenId` must exist and be listed for sale - /// - `salePrice` must matches the expected purchase price to prevent front-running attacks - /// - `supportedToken` must matches the expected purchase token to prevent front-running attacks - /// - Caller must be able to pay the listed price for `tokenId` - /// - Must emit a {Purchased} event - function buyItem(uint256 tokenId, uint256 salePrice, address supportedToken) external payable; - - /// @notice Return the listing for `tokenId` - /// @dev The zero sale price indicates that the token is not for sale - /// The zero expires indicates that the token is not for sale - /// The zero supported token address indicates that the supported token is ETH - /// @param tokenId identifier of the token being queried - /// @return the specified listing (sale price, expires, supported token, benchmark price) - function getListing(uint256 tokenId) external view returns (uint256, uint64, address, uint256); -} -``` - -### Optional collection offer extention - -```solidity -/// The collection offer extension is OPTIONAL for ERC-6105 smart contracts. This allows smart contract to support collection offer functionality. -interface IERC6105CollectionOffer { - - /// @notice Emitted when the collection receives an offer or an offer is canceled - /// @dev The zero `salePrice` indicates that the collection offer of the token is canceled - /// The zero `expires` indicates that the collection offer of the token is canceled - /// @param from - address of who make collection offer - /// @param amount - the amount the offerer wants to buy at `salePrice` per token - /// @param salePrice - the price of each token is being offered for the collection - /// @param expires - UNIX timestamp, the offer could be accepted before expires - /// @param supportedToken - contract addresses of supported ERC20 token - /// Buyer wants to purchase items with supported token - event UpdateCollectionOffer(address indexed from, uint256 amount, uint256 salePrice ,uint64 expires, address supportedToken); - - /// @notice Create or update an offer for the collection - /// @dev `salePrice` MUST NOT be set to zero - /// @param amount - the amount the offerer wants to buy at `salePrice` per token - /// @param salePrice - the price of each token is being offered for the collection - /// @param expires - UNIX timestamp, the offer could be accepted before expires - /// @param supportedToken - contract addresses of supported token - /// Buyer wants to purchase items with supported token - /// Requirements: - /// - The caller must have enough supported tokens, and has approved the contract a sufficient amount - /// - `salePrice` must not be zero - /// - `amount` must not be zero - /// - `expires` must be valid - /// - Must emit an {UpdateCollectionOffer} event - function makeCollectionOffer(uint256 amount, uint256 salePrice, uint64 expires, address supportedToken) external; - - /// @notice Accepts collection offer and transfers the token to the buyer - /// @dev `salePrice` and `supportedToken` must match the expected purchase price and token to prevent front-running attacks - /// When the trading is completed, the `amount` of NFTs the buyer wants to purchase needs to be reduced by 1 - /// @param tokenId - identifier of the token being offered - /// @param salePrice - the price the token is being offered for - /// @param supportedToken - contract addresses of supported token - /// @param buyer - address of who wants to buy the token - /// Requirements: - /// - `tokenId` must exist and and be offered for - /// - Caller must be owner, authorised operators or approved address of the token - /// - Must emit a {Purchased} event - function acceptCollectionOffer(uint256 tokenId, uint256 salePrice, address supportedToken, address buyer) external; - - /// @notice Accepts collection offer and transfers the token to the buyer - /// @dev `salePrice` and `supportedToken` must match the expected purchase price and token to prevent front-running attacks - /// When the trading is completed, the `amount` of NFTs the buyer wants to purchase needs to be reduced by 1 - /// @param tokenId - identifier of the token being offered - /// @param salePrice - the price the token is being offered for - /// @param supportedToken - contract addresses of supported token - /// @param buyer - address of who wants to buy the token - /// @param benchmarkPrice - additional price parameter, may be used when calculating royalties - /// Requirements: - /// - `tokenId` must exist and and be offered for - /// - Caller must be owner, authorised operators or approved address of the token - /// - Must emit a {Purchased} event - function acceptCollectionOffer(uint256 tokenId, uint256 salePrice, address supportedToken, address buyer, uint256 benchmarkPrice) external; - - /// @notice Removes the offer for the collection - /// Requirements: - /// - Caller must be the offerer - /// - Must emit an {UpdateCollectionOffer} event - function cancelCollectionOffer() external; - - /// @notice Returns the offer for `tokenId` maked by `buyer` - /// @dev The zero amount indicates there is no offer - /// The zero sale price indicates there is no offer - /// The zero expires indicates that there is no offer - /// @param buyer address of who wants to buy the token - /// @return the specified offer (amount, sale price, expires, supported token) - function getCollectionOffer(address buyer) external view returns (uint256, uint256, uint64, address); -} -``` - -### Optional item offer extention - -```solidity -/// The item offer extension is OPTIONAL for ERC-6105 smart contracts. This allows smart contract to support item offer functionality. -interface IERC6105ItemOffer { - - /// @notice Emitted when a token receives an offer or an offer is canceled - /// @dev The zero `salePrice` indicates that the offer of the token is canceled - /// The zero `expires` indicates that the offer of the token is canceled - /// @param tokenId - identifier of the token being offered - /// @param from - address of who wants to buy the token - /// @param salePrice - the price the token is being offered for - /// @param expires - UNIX timestamp, the offer could be accepted before expires - /// @param supportedToken - contract addresses of supported token - /// Buyer wants to purchase item with supported token - event UpdateItemOffer( - uint256 indexed tokenId, - address indexed from, - uint256 salePrice, - uint64 expires, - address supportedToken - ); - - /// @notice Create or update an offer for `tokenId` - /// @dev `salePrice` MUST NOT be set to zero - /// @param tokenId - identifier of the token being offered - /// @param salePrice - the price the token is being offered for - /// @param expires - UNIX timestamp, the offer could be accepted before expires - /// @param supportedToken - contract addresses of supported token - /// Buyer wants to purchase item with supported token - /// Requirements: - /// - `tokenId` must exist - /// - The caller must have enough supported tokens, and has approved the contract a sufficient amount - /// - `salePrice` must not be zero - /// - `expires` must be valid - /// - Must emit an {UpdateItemOffer} event. - function makeItemOffer(uint256 tokenId, uint256 salePrice, uint64 expires, address supportedToken) external; - - /// @notice Remove the offer for `tokenId` - /// @param tokenId - identifier of the token being canceled offer - /// Requirements: - /// - `tokenId` must exist and be offered for - /// - Caller must be the offerer - /// - Must emit an {UpdateItemOffer} event - function cancelItemOffer(uint256 tokenId) external; - - /// @notice Accept offer and transfer the token to the buyer - /// @dev `salePrice` and `supportedToken` must match the expected purchase price and token to prevent front-running attacks - /// When the trading is completed, the offer infomation needs to be removed - /// @param tokenId - identifier of the token being offered - /// @param salePrice - the price the token is being offered for - /// @param supportedToken - contract addresses of supported token - /// @param buyer - address of who wants to buy the token - /// Requirements: - /// - `tokenId` must exist and be offered for - /// - Caller must be owner, authorised operators or approved address of the token - /// - Must emit a {Purchased} event - function acceptItemOffer(uint256 tokenId, uint256 salePrice, address supportedToken, address buyer) external; - - /// @notice Accepts offer and transfers the token to the buyer - /// @dev `salePrice` and `supportedToken` must match the expected purchase price and token to prevent front-running attacks - /// When the trading is completed, the offer infomation needs to be removed - /// @param tokenId - identifier of the token being offered - /// @param salePrice - the price the token is being offered for - /// @param supportedToken - contract addresses of supported token - /// @param buyer - address of who wants to buy the token - /// @param benchmarkPrice - additional price parameter, may be used when calculating royalties - /// Requirements: - /// - `tokenId` must exist and be offered for - /// - Caller must be owner, authorised operators or approved address of the token - /// - Must emit a {Purchased} event - function acceptItemOffer(uint256 tokenId, uint256 salePrice, address supportedToken, address buyer, uint256 benchmarkPrice) external; - - /// @notice Return the offer for `tokenId` maked by `buyer` - /// @dev The zero sale price indicates there is no offer - /// The zero expires indicates that there is no offer - /// @param tokenId identifier of the token being queried - /// @param buyer address of who wants to buy the token - /// @return the specified offer (sale price, expires, supported token) - function getItemOffer(uint256 tokenId, address buyer) external view returns (uint256, uint64, address); -} -``` - -## Rationale - -### Considerations for some local variables - -The `salePrice` in the `listItem` function cannot be set to zero. Firstly, it is a rare occurrence for a caller to set the price to 0, and when it happens, it is often due to an operational error which can result in loss of assets. Secondly, a caller needs to spend gas to call this function, so if he can set the token price to 0, his income would be actually negative at this time, which does not conform to the concept of 'economic man' in economics. Additionally, a token price of 0 indicates that the item is not for sale, making the reference implementation more concise. - -Setting `expires` in the `listItem` function allows callers to better manage their listings. If a listing expires automatically, the token owner will no longer need to manually `delistItem`, thus saving gas. - -Setting `supportedToken` in the `listItem` function allows the caller or contract owner to choose which tokens they want to accept, rather than being limited to a single token. - -The rationales of variable setting in the `acceptCollectionOffer` and `acceptItemOffer` functions are the same as described above. - -### More diverse royalty schemes - -By introducing the parameter `benchmarkPrice` in the `listItem`, `acceptCollectionOffer` and `acceptItemOffer` functions, the `_salePrice` in the `royaltyInfo(uint256 _tokenId, uint256 _salePrice)` function in the [ERC-2981](./eip-2981.md) interface can be changed to `taxablePrice`, making the ERC-2981 royalty scheme more diverse. Here are several examples of royalty schemes: - -`(address royaltyRecipient, uint256 royalties) = royaltyInfo(tokenId, taxablePrice)` - -1. Value-added Royalty (VAR, royalties are only charged on the part of the seller's profit): `taxablePrice=max(salePrice- historicalPrice, 0)` -2. Sale Royalty (SR): `taxablePrice=salePrice` -3. Capped Royalty(CR): `taxablePrice=min(salePrice, constant)` -4. Quantitative Royalty(QR, each token trading pays a fixed royalties): `taxablePrice= constant` - -### Optional Blocklist - -Some viewpoints suggest that tokens should be prevented from trading on intermediary markets that do not comply with royalty schemes, but this standard only provides a functionality for non-intermediary NFT trading and does not offer a standardized interface to prevent tokens from trading on these markets. If deemed necessary to better protect the interests of the project team and community, they may consider adding a blocklist to their implementation contracts to prevent NFTs from being traded on platforms that do not comply with the project’s royalty scheme. - -## Backwards Compatibility - -This standard is compatible with [ERC-721](./eip-721.md) and [ERC-2981](./eip-2981.md). - -## Reference Implementation - -```solidity - // SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.8; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/common/ERC2981.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import "./IERC6105.sol"; - -/// @title No Intermediary NFT Trading Protocol with Value-added Royalty -/// @dev The royalty scheme used by this reference implementation is Value-Added Royalty -contract ERC6105 is ERC721, ERC2981, IERC6105, ReentrancyGuard{ - - /// @dev A structure representing a listed token - /// The zero `salePrice` indicates that the token is not for sale - /// The zero `expires` indicates that the token is not for sale - /// @param salePrice - the price the token is being sold for - /// @param expires - UNIX timestamp, the buyer could buy the token before expires - /// @param supportedToken - contract addresses of supported ERC20 token or zero address - /// The zero address indicates that the supported token is ETH - /// Buyer needs to purchase item with supported token - /// @param historicalPrice - The price at which the seller last bought this token - struct Listing { - uint256 salePrice; - uint64 expires; - address supportedToken; - uint256 historicalPrice; - } - - // Mapping from token Id to listing index - mapping(uint256 => Listing) private _listings; - - constructor(string memory name_, string memory symbol_) - ERC721(name_, symbol_) - { - } - - /// @notice Create or update a listing for `tokenId` - /// @dev `salePrice` MUST NOT be set to zero - /// @param tokenId - identifier of the token being listed - /// @param salePrice - the price the token is being sold for - /// @param expires - UNIX timestamp, the buyer could buy the token before expires - /// @param supportedToken - contract addresses of supported ERC20 token or zero address - /// The zero address indicates that the supported token is ETH - /// Buyer needs to purchase item with supported token - function listItem ( - uint256 tokenId, - uint256 salePrice, - uint64 expires, - address supportedToken - ) external virtual{ - listItem(tokenId, salePrice, expires, supportedToken, 0); - } - - /// @notice Create or update a listing for `tokenId` with `historicalPrice` - /// @dev `price` MUST NOT be set to zero - /// @param tokenId - identifier of the token being listed - /// @param salePrice - the price the token is being sold for - /// @param expires - UNIX timestamp, the buyer could buy the token before expires - /// @param supportedToken - contract addresses of supported ERC20 token or zero address - /// The zero address indicates that the supported token is ETH - /// Buyer needs to purchase item with supported token - /// @param historicalPrice - The price at which the seller last bought this token - function listItem ( - uint256 tokenId, - uint256 salePrice, - uint64 expires, - address supportedToken, - uint256 historicalPrice - ) public virtual{ - - address tokenOwner = ownerOf(tokenId); - require(salePrice > 0, "ERC6105: token sale price MUST NOT be set to zero"); - require(expires > block.timestamp, "ERC6105: invalid expires"); - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC6105: caller is not owner nor approved"); - - _listings[tokenId] = Listing(salePrice, expires, supportedToken, historicalPrice); - emit UpdateListing(tokenId, tokenOwner, salePrice, expires, supportedToken, historicalPrice); - } - - /// @notice Remove the listing for `tokenId` - /// @param tokenId - identifier of the token being listed - function delistItem(uint256 tokenId) external virtual{ - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC6105: caller is not owner nor approved"); - require(_isForSale(tokenId), "ERC6105: invalid listing" ); - - _removeListing(tokenId); - } - - /// @notice Buy a token and transfers it to the caller - /// @dev `salePrice` and `supportedToken` must match the expected purchase price and token to prevent front-running attacks - /// @param tokenId - identifier of the token being purchased - /// @param salePrice - the price the token is being sold for - /// @param supportedToken - contract addresses of supported token or zero address - function buyItem(uint256 tokenId, uint256 salePrice, address supportedToken) external nonReentrant payable virtual{ - address tokenOwner = ownerOf(tokenId); - address buyer = msg.sender; - uint256 historicalPrice = _listings[tokenId].historicalPrice; - - require(salePrice == _listings[tokenId].salePrice, "ERC6105: inconsistent prices"); - require(supportedToken == _listings[tokenId].supportedToken,"ERC6105: inconsistent tokens"); - require(_isForSale(tokenId), "ERC6105: invalid listing"); - - /// @dev Handle royalties - (address royaltyRecipient, uint256 royalties) = _calculateRoyalties(tokenId, salePrice, historicalPrice); - - uint256 payment = salePrice - royalties; - if(supportedToken == address(0)){ - require(msg.value == salePrice, "ERC6105: incorrect value"); - _processSupportedTokenPayment(royalties, buyer, royaltyRecipient, address(0)); - _processSupportedTokenPayment(payment, buyer, tokenOwner, address(0)); - } - else{ - uint256 num = IERC20(supportedToken).allowance(buyer, address(this)); - require (num >= salePrice, "ERC6105: insufficient allowance"); - _processSupportedTokenPayment(royalties, buyer, royaltyRecipient, supportedToken); - _processSupportedTokenPayment(payment, buyer, tokenOwner, supportedToken); - } - - _transfer(tokenOwner, buyer, tokenId); - emit Purchased(tokenId, tokenOwner, buyer, salePrice, supportedToken, royalties); - } - - /// @notice Return the listing for `tokenId` - /// @dev The zero sale price indicates that the token is not for sale - /// The zero expires indicates that the token is not for sale - /// The zero supported token address indicates that the supported token is ETH - /// @param tokenId identifier of the token being queried - /// @return the specified listing (sale price, expires, supported token, benchmark price) - function getListing(uint256 tokenId) external view virtual returns (uint256, uint64, address, uint256) { - if(_listings[tokenId].salePrice > 0 && _listings[tokenId].expires >= block.timestamp){ - uint256 salePrice = _listings[tokenId].salePrice; - uint64 expires = _listings[tokenId].expires; - address supportedToken = _listings[tokenId].supportedToken; - uint256 historicalPrice = _listings[tokenId].historicalPrice; - return (salePrice, expires, supportedToken, historicalPrice); - } - else{ - return (0, 0, address(0), 0); - } - } - - /// @dev Remove the listing for `tokenId` - /// @param tokenId - identifier of the token being delisted - function _removeListing(uint256 tokenId) internal virtual{ - address tokenOwner = ownerOf(tokenId); - delete _listings[tokenId]; - emit UpdateListing(tokenId, tokenOwner, 0, 0, address(0), 0); - } - - /// @dev Check if the token is for sale - function _isForSale(uint256 tokenId) internal virtual returns(bool){ - if(_listings[tokenId].salePrice > 0 && _listings[tokenId].expires >= block.timestamp){ - return true; - } - else{ - return false; - } - } - - /// @dev Handle Value Added Royalty - function _calculateRoyalties( - uint256 tokenId, - uint256 price, - uint256 historicalPrice - ) internal virtual returns(address, uint256){ - uint256 taxablePrice; - if(price > historicalPrice){ - taxablePrice = price - historicalPrice; - } - else{ - taxablePrice = 0 ; - } - - (address royaltyRecipient, uint256 royalties) = royaltyInfo(tokenId, taxablePrice); - return(royaltyRecipient, royalties); - } - - /// @dev Process a `supportedToken` of `amount` payment to `recipient`. - /// @param amount - the amount to send - /// @param from - the payment payer - /// @param recipient - the payment recipient - /// @param supportedToken - contract addresses of supported ERC20 token or zero address - /// The zero address indicates that the supported token is ETH - function _processSupportedTokenPayment( - uint256 amount, - address from, - address recipient, - address supportedToken - ) internal virtual{ - if(supportedToken == address(0)) - { - (bool success,) = payable(recipient).call{value: amount}(""); - require(success, "Ether Transfer Fail"); - } - else{ - (bool success) = IERC20(supportedToken).transferFrom(from, recipient, amount); - require(success, "Supported Token Transfer Fail"); - } - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override (ERC721, ERC2981) returns (bool) { - return interfaceId == type(IERC6105).interfaceId || super.supportsInterface(interfaceId); - } - - /// @dev Before transferring the NFT, need to delete listing - function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId, batchSize); - if(_isForSale(tokenId)){ - _removeListing(tokenId); - } - } -} -``` - -## Security Considerations - -The `buyItem` function, as well as the `acceptCollectionOffer` and `acceptItemOffer` functions, has a potential front-running risk. Must check that `salePrice` and `supportedToken` match the expected price and token to prevent front-running attacks - -There is a potential re-entrancy risk with the `acceptCollectionOffer` and `acceptItemOffer` functions. Make sure to obey the checks, effects, interactions pattern or use a reentrancy guard. - -If a buyer uses [ERC-20](./eip-20.md) tokens to purchase an NFT, the buyer needs to first call the `approve(address spender, uint256 amount)` function of the ERC-20 token to grant the NFT contract access to a certain `amount` of tokens. Please make sure to authorize an appropriate `amount`. Furthermore, caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6105.md diff --git a/EIPS/eip-6120.md b/EIPS/eip-6120.md index be248da2265e7b..114ea06b44f8ab 100644 --- a/EIPS/eip-6120.md +++ b/EIPS/eip-6120.md @@ -1,646 +1 @@ ---- -eip: 6120 -title: Universal Token Router -description: A single router contract enables tokens to be sent to application contracts in the transfer-and-call pattern instead of approve-then-call. -author: Derivable (@derivable-labs), Zergity (@Zergity), Ngo Quang Anh (@anhnq82), BerlinP (@BerlinP), Khanh Pham (@blackskin18), Hal Blackburn (@h4l) -discussions-to: https://ethereum-magicians.org/t/eip-6120-universal-token-router/12142 -status: Review -type: Standards Track -category: ERC -created: 2022-12-12 -requires: 20, 165, 721, 1014, 1155 ---- - -## Abstract - -ETH is designed with *transfer-and-call* as the default behavior in a transaction. Unfortunately, [ERC-20](./eip-20.md) is not designed with that pattern in mind and newer standards cannot apply to the token contracts that have already been deployed. - -Application and router contracts must use the *approve-then-call* pattern, which costs additional $n\times m\times l$ `approve` (or `permit`) signatures for $n$ contracts, $m$ tokens, and $l$ accounts. Not only these allowance transactions create a bad user experience, cost a lot of user fees and network storage, but they also put users at serious security risks as they often have to approve unaudited, unverified, and upgradable proxy contracts. The *approve-then-call* pattern is also quite error-prone, as many allowance-related bugs and exploits have been found recently. - -The Universal Token Router (UTR) separates the token allowance from the application logic, allowing any token to be spent in a contract call the same way with ETH, without approving any other application contracts. - -Tokens approved to the Universal Token Router can only be spent in transactions directly signed by their owner, and they have clearly visible token transfer behavior, including token types (ETH, [ERC-20](./eip-20.md), [ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md)), `amountIn`, `amountOutMin`, and `recipient`. - -The Universal Token Router contract is deployed using the [EIP-1014](./eip-1014.md) SingletonFactory contract at a single address across all EVM-compatible networks. This enables new token contracts to pre-configure it as a trusted spender, eliminating the need for approval transactions during their interactive usage. - -## Motivation - -When users approve their tokens to a contract, they trust that: - -* it only spends the tokens with their permission (from `msg.sender` or `ecrecover`) -* it does not use `delegatecall` (e.g. upgradable proxies) - -By ensuring the same security conditions above, the Universal Token Router can be shared by all interactive applications, saving most approval transactions for old tokens and **ALL** approval transactions for new tokens. - -Before this EIP, when users sign transactions to spend their approved tokens, they trust the front-end code entirely to construct those transactions honestly and correctly. This puts them at great risk of phishing sites. - -The Universal Token Router function arguments can act as a manifest for users when signing a transaction. With the support from wallets, users can see and review their expected token behavior instead of blindly trusting the application contracts and front-end code. Phishing sites will be much easier to detect and avoid for users. - -Most of the application contracts are already compatible with the Universal Token Router and can use it to have the following benefits: - -* Securely share the user token allowance with all other applications. -* Update their peripheral contracts as often as they want. -* Save development and security audit costs on router contracts. - -The Universal Token Router promotes the **security-by-result** model in decentralized applications instead of **security-by-process**. By directly querying token balance change for output verification, user transactions can be secured even when interacting with erroneous or malicious contracts. With non-token results, application helper contracts can provide additional result-checking functions for UTR's output verification. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -The main interface of the UTR contract: - -```solidity -interface IUniversalTokenRouter { - function exec( - Output[] memory outputs, - Action[] memory actions - ) payable; -} -``` - -### Output Verification - -`Output` defines the expected token balance change for verification. - -```solidity -struct Output { - address recipient; - uint eip; // token standard: 0 for ETH or EIP number - address token; // token contract address - uint id; // token id for ERC-721 and ERC-1155 - uint amountOutMin; -} -``` - -Token balances of the `recipient` address are recorded at the beginning and the end of the `exec` function for each item in `outputs`. Transaction will revert with `INSUFFICIENT_OUTPUT_AMOUNT` if any of the balance changes are less than its `amountOutMin`. - -A special id `ERC_721_BALANCE` is reserved for ERC-721, which can be used in output actions to verify the total amount of all ids owned by the `recipient` address. - -```solidity -ERC_721_BALANCE = keccak256('UniversalTokenRouter.ERC_721_BALANCE') -``` - -### Action - -`Action` defines the token inputs and the contract call. - -```solidity -struct Action { - Input[] inputs; - address code; // contract code address - bytes data; // contract input data -} -``` -The action code contract MUST implement the [ERC-165](./eip-165.md) interface with the ID `0x61206120` in order to be called by the UTR. This interface check prevents direct invocation of token *allowance-spending* functions (e.g., `transferFrom`) by the UTR. Therefore, new token contracts MUST NOT implement this interface ID. - -```solidity -abstract contract NotToken is ERC165 { - // IERC165-supportsInterface - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return - interfaceId == 0x61206120 || - super.supportsInterface(interfaceId); - } -} - -contract Application is NotToken { - // this contract can be used with the UTR -} -``` - -### Input - -`Input` defines the input token to transfer or prepare before the action contract is executed. - -```solidity -struct Input { - uint mode; - address recipient; - uint eip; // token standard: 0 for ETH or EIP number - address token; // token contract address - uint id; // token id for ERC-721 and ERC-1155 - uint amountIn; -} -``` - -`mode` takes one of the following values: - -* `PAYMENT = 0`: pend a payment for the token to be transferred from `msg.sender` to the `recipient` by calling `UTR.pay` from anywhere in the same transaction. -* `TRANSFER = 1`: transfer the token directly from `msg.sender` to the `recipient`. -* `CALL_VALUE = 2`: record the `ETH` amount to pass to the action as the call `value`. - -Each input in the `inputs` argument is processed sequentially. For simplicity, duplicated `PAYMENT` and `CALL_VALUE` inputs are valid, but only the last `amountIn` value is used. - -#### Payment Input - -`PAYMENT` is the recommended mode for application contracts that use the *transfer-in-callback* pattern. E.g., flashloan contracts, Uniswap/v3-core, Derivable, etc. - -For each `Input` with `PAYMENT` mode, at most `amountIn` of the token can be transferred from `msg.sender` to the `recipient` by calling `UTR.pay` from anywhere in the same transaction. - -``` -UTR - | - | PAYMENT - | (payments pended for UTR.pay) - | - | Application Contracts -action.code.call ---------------------> | - | -UTR.pay <----------------------- (call) | - | - | <-------------------------- (return) | - | - | (clear all pending payments) - | -END -``` - -Token's allowance and `PAYMENT` are essentially different as: - -* allowance: allow a specific `spender` to transfer the token to anyone at any time. -* `PAYMENT`: allow anyone to transfer the token to a specific `recipient` only in that transaction. - -##### Spend Payment - -```solidity -interface IUniversalTokenRouter { - function pay(bytes memory payment, uint amount); -} -``` - -To call `pay`, the `payment` param must be encoded as follows: - -```solidity -payment = abi.encode( - payer, // address - recipient, // address - eip, // uint256 - token, // address - id // uint256 -); -``` - -The `payment` bytes can also be used by adapter UTR contracts to pass contexts and payloads for performing custom payment logic. - -##### Discard Payment - -Sometimes, it's useful to discard the payment instead of performing the transfer, for example, when the application contract wants to burn its own token from `payment.payer`. The following function can be used to verify the payment to the caller's address and discard a portion of it. - -```solidity -interface IUniversalTokenRouter { - function discard(bytes memory payment, uint amount); -} -``` - -Please refer to the [Discard Payment](#discard-payment-1) section in the **Security Considerations** for an important security note. - -##### Payment Lifetime - -Payments are recorded in the UTR storage and intended to be spent by `input.action` external calls only within that transaction. All payment storages will be cleared before the `UTR.exec` ends. - -### Native Token Tranfer - -The `UTR` SHOULD have a `receive()` function for user execution logic that requires transferring ETH in. The `msg.value` transferred into the router can be spent in multiple inputs across different actions. While the caller takes full responsibility for the movement of `ETH` in and out of the router, the `exec` function SHOULD refund any remaining `ETH` before the function ends. - -Please refer to the [Reentrancy](#reentrancy) section in the **Security Considerations** for information on reentrancy risks and mitigation. - -### Usage Examples - -#### Uniswap V2 Router - -Legacy function: - -```solidity -UniswapV2Router01.swapExactTokensForTokens( - uint amountIn, - uint amountOutMin, - address[] calldata path, - address to, - uint deadline -) -``` - -`UniswapV2Helper01.swapExactTokensForTokens` is a modified version of it without the token transfer part. - -This transaction is signed by users to execute the swap instead of the legacy function: - -```javascript -UniversalTokenRouter.exec([{ - recipient: to, - eip: 20, - token: path[path.length-1], - id: 0, - amountOutMin, -}], [{ - inputs: [{ - mode: TRANSFER, - recipient: UniswapV2Library.pairFor(factory, path[0], path[1]), - eip: 20, - token: path[0], - id: 0, - amountIn: amountIn, - }], - code: UniswapV2Helper01.address, - data: encodeFunctionData("swapExactTokensForTokens", [ - amountIn, - amountOutMin, - path, - to, - deadline, - ]), -}]) -``` - -#### Uniswap V3 Router - -Legacy router contract: - -```solidity -contract SwapRouter { - // this function is called by pool to pay the input tokens - function pay( - address token, - address payer, - address recipient, - uint256 value - ) internal { - ... - // pull payment - TransferHelper.safeTransferFrom(token, payer, recipient, value); - } -} -``` - -The helper contract to use with the `UTR`: - -```solidity -contract SwapHelper { - // this function is called by pool to pay the input tokens - function pay( - address token, - address payer, - address recipient, - uint256 value - ) internal { - ... - // pull payment - bytes memory payment = abi.encode(payer, recipient, 20, token, 0); - UTR.pay(payment, value); - } -} -``` - -This transaction is signed by users to execute the `exactInput` functionality using `PAYMENT` mode: - -```javascript -UniversalTokenRouter.exec([{ - eip: 20, - token: tokenOut, - id: 0, - amountOutMin: 1, - recipient: to, -}], [{ - inputs: [{ - mode: PAYMENT, - eip: 20, - token: tokenIn, - id: 0, - amountIn: amountIn, - recipient: pool.address, - }], - code: SwapHelper.address, - data: encodeFunctionData("exactInput", [...]), -}]) -``` - -#### Allowance Adapter - -A simple non-reentrancy ERC-20 adapter for aplication and router contracts that use direct allowance. - -```solidity -contract AllowanceAdapter is ReentrancyGuard { - struct Input { - address token; - uint amountIn; - } - - function approveAndCall( - Input[] memory inputs, - address spender, - bytes memory data, - address leftOverRecipient - ) external payable nonReentrant { - for (uint i = 0; i < inputs.length; ++i) { - Input memory input = inputs[i]; - IERC20(input.token).approve(spender, input.amountIn); - } - - (bool success, bytes memory result) = spender.call{value: msg.value}(data); - if (!success) { - assembly { - revert(add(result, 32), mload(result)) - } - } - - for (uint i = 0; i < inputs.length; ++i) { - Input memory input = inputs[i]; - // clear all allowance - IERC20(input.token).approve(spender, 0); - uint leftOver = IERC20(input.token).balanceOf(address(this)); - if (leftOver > 0) { - TransferHelper.safeTransfer(input.token, leftOverRecipient, leftOver); - } - } - } -} -``` - -This transaction is constructed to utilize the `UTR` to interact with Uniswap V2 Router without approving any token to it: - -```javascript -const { data: routerData } = await uniswapRouter.populateTransaction.swapExactTokensForTokens( - amountIn, - amountOutMin, - path, - to, - deadline, -) - -const { data: adapterData } = await adapter.populateTransaction.approveAndCall( - [{ - token: path[0], - amountIn, - }], - uniswapRouter.address, - routerData, - leftOverRecipient, -) - -await utr.exec([], [{ - inputs: [{ - mode: TRANSFER, - recipient: adapter.address, - eip: 20, - token: path[0], - id: 0, - amountIn, - }], - code: adapter.address, - data: adapterData, -}]) -``` - -## Rationale - -The `Permit` type signature is not supported since the purpose of the Universal Token Router is to eliminate all interactive `approve` signatures for new tokens, and *most* for old tokens. - -## Backwards Compatibility - -### Tokens - -Old token contracts (ERC-20, ERC-721 and ERC-1155) require approval for the Universal Token Router once for each account. - -New token contracts can pre-configure the Universal Token Router as a trusted spender, and no approval transaction is required for interactive usage. - -### Applications - -The only application contracts **INCOMPATIBLE** with the UTR are contracts that use `msg.sender` as the beneficiary address in their internal storage without any function for ownership transfer. - -All application contracts that accept `recipient` (or `to`) argument as the beneficiary address are compatible with the UTR out of the box. - -Application contracts that transfer tokens (ERC-20, ERC-721, and ERC-1155) to `msg.sender` need additional adapters to add a `recipient` to their functions. - -```solidity -// sample adapter contract for WETH -contract WethAdapter { - function deposit(address recipient) external payable { - IWETH(WETH).deposit(){value: msg.value}; - TransferHelper.safeTransfer(WETH, recipient, msg.value); - } -} -``` - -Additional helper and adapter contracts might be needed, but they're mostly peripheral and non-intrusive. They don't hold any tokens or allowances, so they can be frequently updated and have little to no security impact on the core application contracts. - -## Reference Implementation - -A reference implementation by Derivable Labs, verified by Hacken. - -```solidity -/// @title The implemetation of the EIP-6120. -/// @author Derivable Labs -contract UniversalTokenRouter is ERC165, IUniversalTokenRouter { - uint256 constant PAYMENT = 0; - uint256 constant TRANSFER = 1; - uint256 constant CALL_VALUE = 2; - - uint256 constant EIP_ETH = 0; - - uint256 constant ERC_721_BALANCE = uint256(keccak256('UniversalTokenRouter.ERC_721_BALANCE')); - - /// @dev transient pending payments - mapping(bytes32 => uint256) t_payments; - - /// @dev accepting ETH for user execution (e.g. WETH.withdraw) - receive() external payable {} - - /// The main entry point of the router - /// @param outputs token behaviour for output verification - /// @param actions router actions and inputs for execution - function exec( - Output[] memory outputs, - Action[] memory actions - ) external payable virtual override { - unchecked { - // track the expected balances before any action is executed - for (uint256 i = 0; i < outputs.length; ++i) { - Output memory output = outputs[i]; - uint256 balance = _balanceOf(output); - uint256 expected = output.amountOutMin + balance; - require(expected >= balance, 'UTR: OUTPUT_BALANCE_OVERFLOW'); - output.amountOutMin = expected; - } - - address sender = msg.sender; - - for (uint256 i = 0; i < actions.length; ++i) { - Action memory action = actions[i]; - uint256 value; - for (uint256 j = 0; j < action.inputs.length; ++j) { - Input memory input = action.inputs[j]; - uint256 mode = input.mode; - if (mode == CALL_VALUE) { - // eip and id are ignored - value = input.amountIn; - } else { - if (mode == PAYMENT) { - bytes32 key = keccak256(abi.encode(sender, input.recipient, input.eip, input.token, input.id)); - t_payments[key] = input.amountIn; - } else if (mode == TRANSFER) { - _transferToken(sender, input.recipient, input.eip, input.token, input.id, input.amountIn); - } else { - revert('UTR: INVALID_MODE'); - } - } - } - if (action.code != address(0) || action.data.length > 0 || value > 0) { - require( - ERC165Checker.supportsInterface(action.code, 0x61206120), - "UTR: NOT_CALLABLE" - ); - (bool success, bytes memory result) = action.code.call{value: value}(action.data); - if (!success) { - assembly { - revert(add(result,32),mload(result)) - } - } - } - // clear all transient storages - for (uint256 j = 0; j < action.inputs.length; ++j) { - Input memory input = action.inputs[j]; - if (input.mode == PAYMENT) { - // transient storages - bytes32 key = keccak256(abi.encodePacked( - sender, input.recipient, input.eip, input.token, input.id - )); - delete t_payments[key]; - } - } - } - - // refund any left-over ETH - uint256 leftOver = address(this).balance; - if (leftOver > 0) { - TransferHelper.safeTransferETH(sender, leftOver); - } - - // verify balance changes - for (uint256 i = 0; i < outputs.length; ++i) { - Output memory output = outputs[i]; - uint256 balance = _balanceOf(output); - // NOTE: output.amountOutMin is reused as `expected` - require(balance >= output.amountOutMin, 'UTR: INSUFFICIENT_OUTPUT_AMOUNT'); - } - } } - - /// Spend the pending payment. Intended to be called from the input.action. - /// @param payment encoded payment data - /// @param amount token amount to pay with payment - function pay(bytes memory payment, uint256 amount) external virtual override { - discard(payment, amount); - ( - address sender, - address recipient, - uint256 eip, - address token, - uint256 id - ) = abi.decode(payment, (address, address, uint256, address, uint256)); - _transferToken(sender, recipient, eip, token, id, amount); - } - - /// Discard a part of a pending payment. Can be called from the input.action - /// to verify the payment without transfering any token. - /// @param payment encoded payment data - /// @param amount token amount to pay with payment - function discard(bytes memory payment, uint256 amount) public virtual override { - bytes32 key = keccak256(payment); - require(t_payments[key] >= amount, 'UTR: INSUFFICIENT_PAYMENT'); - unchecked { - t_payments[key] -= amount; - } - } - - // IERC165-supportsInterface - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return - interfaceId == type(IUniversalTokenRouter).interfaceId || - super.supportsInterface(interfaceId); - } - - function _transferToken( - address sender, - address recipient, - uint256 eip, - address token, - uint256 id, - uint256 amount - ) internal virtual { - if (eip == 20) { - TransferHelper.safeTransferFrom(token, sender, recipient, amount); - } else if (eip == 1155) { - IERC1155(token).safeTransferFrom(sender, recipient, id, amount, ""); - } else if (eip == 721) { - IERC721(token).safeTransferFrom(sender, recipient, id); - } else { - revert("UTR: INVALID_EIP"); - } - } - - function _balanceOf( - Output memory output - ) internal view virtual returns (uint256 balance) { - uint256 eip = output.eip; - if (eip == 20) { - return IERC20(output.token).balanceOf(output.recipient); - } - if (eip == 1155) { - return IERC1155(output.token).balanceOf(output.recipient, output.id); - } - if (eip == 721) { - if (output.id == ERC_721_BALANCE) { - return IERC721(output.token).balanceOf(output.recipient); - } - try IERC721(output.token).ownerOf(output.id) returns (address currentOwner) { - return currentOwner == output.recipient ? 1 : 0; - } catch { - return 0; - } - } - if (eip == EIP_ETH) { - return output.recipient.balance; - } - revert("UTR: INVALID_EIP"); - } -} -``` - -## Security Considerations - -### ERC-165 Tokens - -Token contracts must **NEVER** support the ERC-165 interface with the ID `0x61206120`, as it is reserved for non-token contracts to be called with the UTR. Any token with the interface ID `0x61206120` approved to the UTR can be spent by anyone, without any restrictions. - -### Reentrancy - -Tokens transferred to the UTR contract will be permanently lost, as there is no way to transfer them out. Applications that require an intermediate address to hold tokens should use their own Helper contract with a reentrancy guard for secure execution. - -ETH must be transferred to the UTR contracts before the value is spent in an action call (using `CALL_VALUE`). This ETH value can be siphoned out of the UTR using a re-entrant call inside an action code or rogue token functions. This exploit will not be possible if users don't transfer more ETH than they will spend in that transaction. - -```solidity -// transfer 100 in, but spend only 60, -// so at most 40 wei can be exploited in this transaction -UniversalTokenRouter.exec([ - ... -], [{ - inputs: [{ - mode: CALL_VALUE, - eip: 20, - token: 0, - id: 0, - amountIn: 60, // spend 60 - recipient: AddressZero, - }], - ... -}], { - value: 100, // transfer 100 in -}) -``` - -### Discard Payment - -The result of the `pay` function can be checked by querying the balance after the call, allowing the UTR contract to be called in a trustless manner. However, due to the inability to verify the execution of the `discard` function, it should only be used with a trusted UTR contract. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6120.md diff --git a/EIPS/eip-6123.md b/EIPS/eip-6123.md index 9a92aa3ec2f084..38d9d8f6ec01b3 100644 --- a/EIPS/eip-6123.md +++ b/EIPS/eip-6123.md @@ -1,236 +1 @@ ---- -eip: 6123 -title: Smart Derivative Contract -description: A deterministic protocol for frictionless post-trade processing of OTC financial contracts -author: Christian Fries (@cfries), Peter Kohl-Landgraf (@pekola), Alexandros Korpis (@kourouta) -discussions-to: https://ethereum-magicians.org/t/eip-6123-smart-derivative-contract-frictionless-processing-of-financial-derivatives/12134 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-12-13 ---- - -## Abstract - -The Smart Derivative Contract is a deterministic protocol to trade and process -financial derivative contracts frictionless and scalable in a completely automated way. Counterparty credit risk ís removed. -Known operational risks and complexities in post-trade processing are removed by construction as all process states -are fully specified and are known to the counterparties. - -## Motivation - -### Rethinking Financial Derivatives - -By their very nature, so-called "over-the-counter (OTC)" financial contracts are bilateral contractual agreements on the exchange of long-dated cash flow schedules. -Since these contracts change their intrinsic market value due to changing market environments, they are subject to counterparty credit risk when one counterparty is subject to default. -The initial white paper describes the concept of a Smart Derivative Contract with the central aim -to detach bilateral financial transactions from counterparty credit risk and to remove complexities -in bilateral post-trade processing by a complete redesign. - -### Concept of a Smart Derivative Contract - -A Smart Derivative Contract is a deterministic settlement protocol which has the same economic behaviour as a collateralized OTC -Derivative. Every process state is specified; therefore, the entire post-trade process is known in advance. -A Smart Derivative Contract (SDC) settles outstanding net present value of the underlying financial contract on a frequent basis. With each settlement cycle net present value of the underlying contract is -exchanged, and the value of the contract is reset to zero. Pre-Agreed margin buffers are locked at the beginning of each settlement cycle such that settlement will be guaranteed up to a certain amount. -If a counterparty fails to obey contract rules, e.g. not provide sufficient prefunding, SDC will terminate automatically with the guaranteed transfer of a termination fee by the causing party. -These features enable two counterparties to process their financial contract fully decentralized without relying on a third central intermediary agent. -The process logic of SDC can be implemented as a finite state machine on solidity. An [EIP-20](./eip-20.md) token can be used for frictionless decentralized settlement, see reference implementation. -Combined with an appropriate external market data and valuation oracle which calculates net present values, each known OTC derivative contract is able to be processed using this standard protocol. - - -## Specification - -### Methods - -The following methods specify inception and post-trade live cycle of a Smart Derivative Contract. For futher information also please look at the interface documentation ISDC.sol. - -#### inceptTrade - -A counterparty can initiate a trade by providing trade data as string and calling inceptTrade and initial settlement data. Only registered counteparties are allowed to use that function. - -```solidity -function inceptTrade(string memory _tradeData, string memory _initialSettlementData) external; -``` - -#### confirmTrade - -A counterparty can confirm a trade by providing the identical trade data and initial settlement information, which are already stored from inceptTrade call. - -```solidity -function confirmTrade(string memory _tradeData, string memory _initialSettlementData) external; -``` - -#### initiatePrefunding - -This method checks whether contractual prefunding is provided by both counterparties as agreed in the contract terms. Triggers a contract termination if not. - -```solidity -function initiatePrefunding() external; -``` - -#### initiateSettlement - -Allows eligible participants (such as counterparties or a delegated agent) to initiate a settlement. - -```solidity -function initiateSettlement() external; -``` - -#### performSettlement - -Valuation may be provided off-chain via an external oracle service that calculates net present value and uses external market data. -Method serves as callback called from an external oracle providing settlement amount and used settlement data which also get stored. -Settlement amount will be checked according to contract terms resulting in either a reqular settlement or a termination of the trade. - -```solidity -function performSettlement(int256 settlementAmount, string memory settlementData) external; -``` - -#### requestTermination - -Allows an eligible party to request a mutual termination - -```js -function requestTradeTermination(string memory tradeId) external; -``` - -#### confirmTradeTermination - -Allows eligible parties to confirm a formerly-requested mutual trade termination. - -```solidity -function confirmTradeTermination(string memory tradeId) external; -``` - -### Trade Events - -The following events are emitted during an SDC trade livecycle. - -#### TradeIncepted - -Emitted on trade inception - method 'inceptTrade' - -```solidity -event TradeIncepted(address initiator, string tradeId, string tradeData); -``` - -#### TradeConfirmed - -Emitted on trade confirmation - method 'confirmTrade' - -```solidity -event TradeConfirmed(address confirmer, string tradeId); -``` - -#### TradeActivated - -Emitted when trade is activated - -```solidity -event TradeActivated(string tradeId); -``` - -#### TradeTerminationRequest - -Emitted when termination request is initiated by a counterparty - -```solidity -event TradeTerminationRequest(address cpAddress, string tradeId); -``` - -#### TradeTerminationConfirmed - -Emitted when termination request is confirmed by a counterparty - -```solidity -event TradeTerminationConfirmed(address cpAddress, string tradeId); -``` - -#### TradeTerminated - -Emitted when trade is terminated - -```solidity -event TradeTerminated(string cause); -``` - -### Process Events - -The following events are emitted during SDC's process livecycle. - -#### ProcessAwaitingFunding - -Emitted when funding phase is initiated - -```solidity -event ProcessAwaitingFunding(); -``` - -#### ProcessFunded - -Emitted when funding has completed successfully - method 'initiatePrefunding' - -```solidity -event ProcessFunded(); -``` - -#### ProcessSettlementRequest - -Emitted when a settlement is initiated - method 'initiateSettlement' - -```solidity -event ProcessSettlementRequest(string tradeData, string lastSettlementData); -``` - -#### ProcessSettled - -Emitted when settlement was processed successfully - method 'performSettlement' - -```solidity -event ProcessSettled(); -``` - -## Rationale - -The interface design and reference implementation are based on the following considerations: - -- A SDC protocol is supposed to be used by two counterparties and enables them to initiate and process a derivative transaction in a bilateral and digital manner. -- The provided interface specification is supposed to completely reflect the entire trade livecycle. -- The interface specification is generic enough to handle the case that two counterparties process one or even multiple derivative transactions (on a netted base) -- Usually, the valuation of an OTC trade will require advanced valuation methodology. This is why the concept will in most cases rely on external market data and valuation algorithms -- A pull-based valuation based oracle pattern is specified by a simple callback pattern (methods: initiateSettlement, performSettlement) -- The reference implementation `SDC.sol` is based on a state-machine pattern where the states also serve as guards (via modifiers) to check which method is allowed to be called at a particular given process and trade state -- Java based state machine and contract implementations are also available. See the github repo link below. - -### State diagram of trade and process states - -![image info](../assets/eip-6123/doc/sdc_trade_and_process_states.png) - -### Sequence diagram of trade initiation and settlement livecycle - -![image info](../assets/eip-6123/doc/sdc_livecycle_sequence_diagram.png) - -## Test Cases - -Live-cycle unit tests based on the sample implementation and usage of [EIP-20](./eip-20.md) token is provided. See file [test/SDC.js](../assets/eip-6123/test/SDC.js) -). - -## Reference Implementation - -A reference implementation SDC.sol is provided and is based on the [EIP-20](./eip-20.md) token standard. -See folder /assets/contracts, more explanation on the implementation is provided inline. - -### Trade Data Specification (suggestion) - -Please take a look at the provided xml file as a suggestion on how trade parameters could be stored. - -## Security Considerations - -No known security issues up to now. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6123.md diff --git a/EIPS/eip-6147.md b/EIPS/eip-6147.md index 3769c0f78f1d9c..a63b8d1629ea7e 100644 --- a/EIPS/eip-6147.md +++ b/EIPS/eip-6147.md @@ -1,318 +1 @@ ---- -eip: 6147 -title: Guard of NFT/SBT, an Extension of ERC-721 -description: A new management role with an expiration date of NFT/SBT is defined, achieving the separation of transfer right and holding right. -author: 5660-eth (@5660-eth), Wizard Wang -discussions-to: https://ethereum-magicians.org/t/guard-of-nft-sbt-an-extension-of-eip-721/12052 -status: Final -type: Standards Track -category: ERC -created: 2022-12-07 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It separates the holding right and transfer right of non-fungible tokens (NFTs) and Soulbound Tokens (SBTs) and defines a new role, `guard` with `expires`. The flexibility of the `guard` setting enables the design of NFT anti-theft, NFT lending, NFT leasing, SBT, etc. - -## Motivation - -NFTs are assets that possess both use and financial value. - -Many cases of NFT theft currently exist, and current NFT anti-theft schemes, such as transferring NFTs to cold wallets, make NFTs inconvenient to be used. - -In current NFT lending, the NFT owner needs to transfer the NFT to the NFT lending contract, and the NFT owner no longer has the right to use the NFT while he has obtained the loan. In the real world, for example, if a person takes out a mortgage on his own house, he still has the right to use that house. - -For SBT, the current mainstream view is that an SBT is not transferable, which makes an SBT bound to an Ether address. However, when the private key of the user address is leaked or lost, retrieving SBT will become a complicated task and there is no corresponding standard. The SBTs essentially realizes the separation of NFT holding right and transfer right. When the wallet where SBT is located is stolen or unavailable, SBT should be able to be recoverable. - -In addition, SBTs still need to be managed in use. For example, if a university issues diploma-based SBTs to its graduates, and if the university later finds that a graduate has committed academic misconduct or jeopardized the reputation of the university, it should have the ability to retrieve the diploma-based SBTs. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -ERC-721 compliant contracts MAY implement this EIP. - -A guard Must be valid only before expires. - -When a token has no guard or the guard is expired, `guardInfo` MUST return `(address(0), 0)`. - -When a token has no guard or the guard is expired, owner, authorised operators and approved address of the token MUST have permission to set guard and expires. - -When a token has a valid guard, owner, authorised operators and approved address of the token MUST NOT be able to change guard and expires, and they MUST NOT be able to transfer the token. - -When a token has a valid guard, `guardInfo` MUST return the address and expires of the guard. - -When a token has a valid guard, the guard MUST be able to remove guard and expires, change guard and expires, and transfer the token. - -When a token has a valid guard, if the token burns, the guard MUST be deleted. - -If issuing or minting SBTs, the guard MAY be uniformly set to the designated address to facilitate management. - -### Contract Interface - -```solidity - interface IERC6147 { - - /// Logged when the guard of an NFT is changed or expires is changed - /// @notice Emitted when the `guard` is changed or the `expires` is changed - /// The zero address for `newGuard` indicates that there currently is no guard address - event UpdateGuardLog(uint256 indexed tokenId, address indexed newGuard, address oldGuard, uint64 expires); - - /// @notice Owner, authorised operators and approved address of the NFT can set guard and expires of the NFT and - /// valid guard can modifiy guard and expires of the NFT - /// If the NFT has a valid guard role, the owner, authorised operators and approved address of the NFT - /// cannot modify guard and expires - /// @dev The `newGuard` can not be zero address - /// The `expires` need to be valid - /// Throws if `tokenId` is not valid NFT - /// @param tokenId The NFT to get the guard address for - /// @param newGuard The new guard address of the NFT - /// @param expires UNIX timestamp, the guard could manage the token before expires - function changeGuard(uint256 tokenId, address newGuard, uint64 expires) external; - - /// @notice Remove the guard and expires of the NFT - /// Only guard can remove its own guard role and expires - /// @dev The guard address is set to 0 address - /// The expires is set to 0 - /// Throws if `tokenId` is not valid NFT - /// @param tokenId The NFT to remove the guard and expires for - function removeGuard(uint256 tokenId) external; - - /// @notice Transfer the NFT and remove its guard and expires - /// @dev The NFT is transferred to `to` and the guard address is set to 0 address - /// Throws if `tokenId` is not valid NFT - /// @param from The address of the previous owner of the NFT - /// @param to The address of NFT recipient - /// @param tokenId The NFT to get transferred for - function transferAndRemove(address from, address to, uint256 tokenId) external; - - /// @notice Get the guard address and expires of the NFT - /// @dev The zero address indicates that there is no guard - /// @param tokenId The NFT to get the guard address and expires for - /// @return The guard address and expires for the NFT - function guardInfo(uint256 tokenId) external view returns (address, uint64); -} - ``` - -The `changeGuard(uint256 tokenId, address newGuard, uint64 expires)` function MAY be implemented as `public` or `external`. - -The `removeGuard(uint256 tokenId)` function MAY be implemented as `public` or `external`. - -The `transferAndRemove(address from,address to,uint256 tokenId)` function MAY be implemented as `public` or `external`. - -The `guardInfo(uint256 tokenId)` function MAY be implemented as `pure` or `view`. - -The `UpdateGuardLog` event MUST be emitted when a guard is changed. - -The `supportsInterface` method MUST return `true` when called with `0xb61d1057`. - -## Rationale - -### Universality - -There are many application scenarios for NFT/SBT, and there is no need to propose a dedicated EIP for each one, which would make the overall number of EIPS inevitably increase and add to the burden of developers. The standard is based on the analysis of the right attached to assets in the real world, and abstracts the right attached to NFT/SBT into holding right and transfer right making the standard more universal. - -For example, the standard has more than the following use cases: - -SBTs. The SBTs issuer can assign a uniform role of `guard` to the SBTs before they are minted, so that the SBTs cannot be transferred by the corresponding holders and can be managed by the SBTs issuer through the `guard`. - -NFT anti-theft. If an NFT holder sets a `guard` address of an NFT as his or her own cold wallet address, the NFT can still be used by the NFT holder, but the risk of theft is greatly reduced. - -NFT lending. The borrower sets the `guard` of his or her own NFT as the lender's address, the borrower still has the right to use the NFT while obtaining the loan, but at the same time cannot transfer or sell the NFT. If the borrower defaults on the loan, the lender can transfer and sell the NFT. - -Additionally, by setting an `expires` for the `guard`, the scalability of the protocol is further enhanced, as demonstrated in the following examples: - -More flexible NFT issuance. During NFT minting, discounts can be offered for NFTs that are locked for a certain period of time, without affecting the NFTs' usability. - -More secure NFT management. Even if the `guard` address becomes inaccessible due to lost private keys, the `owner` can still retrieve the NFT after the `guard` has expired. - -Valid SBTs. Some SBTs have a period of use. More effective management can be achieved through `guard` and `expires`. - -### Extensibility - -This standard only defines a `guard` and its `expires`. For complex functions needed by NFTs and SBTs, such as social recovery and multi-signature, the `guard` can be set as a third-party protocol address. Through the third-party protocol, more flexible and diverse functions can be achieved based on specific application scenarios. - -### Naming - -The alternative names are `guardian` and `guard`, both of which basically match the permissions corresponding to the role: protection of NFT or necessary management according to its application scenarios. The `guard` has fewer characters than the `guardian` and is more concise. - -## Backwards Compatibility - -This standard can be fully ERC-721 compatible by adding an extension function set. - -If an NFT issued based on the above standard does not set a `guard`, then it is no different in the existing functions from the current NFT issued based on the ERC-721 standard. - -## Reference Implementation - -```solidity - -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.8; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC6147.sol"; - -abstract contract ERC6147 is ERC721, IERC6147 { - - /// @dev A structure representing a token of guard address and expires - /// @param guard address of guard role - /// @param expirs UNIX timestamp, the guard could manage the token before expires - struct GuardInfo{ - address guard; - uint64 expires; - } - - mapping(uint256 => GuardInfo) internal _guardInfo; - - /// @notice Owner, authorised operators and approved address of the NFT can set guard and expires of the NFT and - /// valid guard can modifiy guard and expires of the NFT - /// If the NFT has a valid guard role, the owner, authorised operators and approved address of the NFT - /// cannot modify guard and expires - /// @dev The `newGuard` can not be zero address - /// The `expires` need to be valid - /// Throws if `tokenId` is not valid NFT - /// @param tokenId The NFT to get the guard address for - /// @param newGuard The new guard address of the NFT - /// @param expires UNIX timestamp, the guard could manage the token before expires - function changeGuard(uint256 tokenId, address newGuard, uint64 expires) public virtual{ - require(expires > block.timestamp, "ERC6147: invalid expires"); - _updateGuard(tokenId, newGuard, expires, false); - } - - /// @notice Remove the guard and expires of the NFT - /// Only guard can remove its own guard role and expires - /// @dev The guard address is set to 0 address - /// The expires is set to 0 - /// Throws if `tokenId` is not valid NFT - /// @param tokenId The NFT to remove the guard and expires for - function removeGuard(uint256 tokenId) public virtual { - _updateGuard(tokenId, address(0), 0, true); - } - - /// @notice Transfer the NFT and remove its guard and expires - /// @dev The NFT is transferred to `to` and the guard address is set to 0 address - /// Throws if `tokenId` is not valid NFT - /// @param from The address of the previous owner of the NFT - /// @param to The address of NFT recipient - /// @param tokenId The NFT to get transferred for - function transferAndRemove(address from, address to, uint256 tokenId) public virtual { - safeTransferFrom(from, to, tokenId); - removeGuard(tokenId); - } - - /// @notice Get the guard address and expires of the NFT - /// @dev The zero address indicates that there is no guard - /// @param tokenId The NFT to get the guard address and expires for - /// @return The guard address and expires for the NFT - function guardInfo(uint256 tokenId) public view virtual returns (address, uint64) { - if(_guardInfo[tokenId].expires >= block.timestamp){ - return (_guardInfo[tokenId].guard, _guardInfo[tokenId].expires); - } - else{ - return (address(0), 0); - } - } - - /// @notice Update the guard of the NFT - /// @dev Delete function: set guard to 0 address and set expires to 0; - /// and update function: set guard to new address and set expires - /// Throws if `tokenId` is not valid NFT - /// @param tokenId The NFT to update the guard address for - /// @param newGuard The newGuard address - /// @param expires UNIX timestamp, the guard could manage the token before expires - /// @param allowNull Allow 0 address - function _updateGuard(uint256 tokenId, address newGuard, uint64 expires, bool allowNull) internal { - (address guard,) = guardInfo(tokenId); - if (!allowNull) { - require(newGuard != address(0), "ERC6147: new guard can not be null"); - } - if (guard != address(0)) { - require(guard == _msgSender(), "ERC6147: only guard can change it self"); - } else { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC6147: caller is not owner nor approved"); - } - - if (guard != address(0) || newGuard != address(0)) { - _guardInfo[tokenId] = GuardInfo(newGuard,expires); - emit UpdateGuardLog(tokenId, newGuard, guard, expires); - } - } - - /// @notice Check the guard address - /// @dev The zero address indicates there is no guard - /// @param tokenId The NFT to check the guard address for - /// @return The guard address - function _checkGuard(uint256 tokenId) internal view returns (address) { - (address guard, ) = guardInfo(tokenId); - address sender = _msgSender(); - if (guard != address(0)) { - require(guard == sender, "ERC6147: sender is not guard of the token"); - return guard; - }else{ - return address(0); - } - } - - /// @dev Before transferring the NFT, need to check the gurard address - function transferFrom(address from, address to, uint256 tokenId) public virtual override { - address guard; - address new_from = from; - if (from != address(0)) { - guard = _checkGuard(tokenId); - new_from = ownerOf(tokenId); - } - if (guard == address(0)) { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "ERC721: transfer caller is not owner nor approved" - ); - } - _transfer(new_from, to, tokenId); - } - - /// @dev Before safe transferring the NFT, need to check the gurard address - function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override { - address guard; - address new_from = from; - if (from != address(0)) { - guard = _checkGuard(tokenId); - new_from = ownerOf(tokenId); - } - if (guard == address(0)) { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "ERC721: transfer caller is not owner nor approved" - ); - } - _safeTransfer(from, to, tokenId, _data); - } - - /// @dev When burning, delete `token_guard_map[tokenId]` - /// This is an internal function that does not check if the sender is authorized to operate on the token. - function _burn(uint256 tokenId) internal virtual override { - (address guard, )=guardInfo(tokenId); - super._burn(tokenId); - delete _guardInfo[tokenId]; - emit UpdateGuardLog(tokenId, address(0), guard, 0); - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC6147).interfaceId || super.supportsInterface(interfaceId); - } -} - -``` - -## Security Considerations - -Make sure to set an appropriate `expires` for the `guard`, based on the specific application scenario. - -When an NFT has a valid guard, even if an address is authorized as an operator through `approve` or `setApprovalForAll`, the operator still has no right to transfer the NFT. - -When an NFT has a valid guard, the `owner` cannot sell the NFT. Some trading platforms list NFTs through `setApprovalForAll` and owners' signature. It is recommended to prevent listing these NFTs by checking `guardInfo`. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6147.md diff --git a/EIPS/eip-6150.md b/EIPS/eip-6150.md index 32fd6f19a69fa8..25d3a04d83815c 100644 --- a/EIPS/eip-6150.md +++ b/EIPS/eip-6150.md @@ -1,291 +1 @@ ---- -eip: 6150 -title: Hierarchical NFTs -description: Hierarchical NFTs, an extension to EIP-721. -author: Keegan Lee (@keeganlee), msfew , Kartin , qizhou (@qizhou) -discussions-to: https://ethereum-magicians.org/t/eip-6150-hierarchical-nfts-an-extension-to-erc-721/12173 -status: Final -type: Standards Track -category: ERC -created: 2022-12-15 -requires: 165, 721 ---- - -## Abstract - -This standard is an extension to [EIP-721](./eip-721.md). It proposes a multi-layer filesystem-like hierarchical NFTs. This standard provides interfaces to get parent NFT or children NFTs and whether NFT is a leaf node or root node, maintaining the hierarchical relationship among them. - -## Motivation - -This EIP standardizes the interface of filesystem-like hierarchical NFTs and provides a reference implementation. - -Hierarchy structure is commonly implemented for file systems by operating systems such as Linux Filesystem Hierarchy (FHS). - -![Linux Hierarchical File Structure](../assets/eip-6150/linux-hierarchy.png) - -Websites often use a directory and category hierarchy structure, such as eBay (Home -> Electronics -> Video Games -> Xbox -> Products), and Twitter (Home -> Lists -> List -> Tweets), and Reddit (Home -> r/ethereum -> Posts -> Hot). - -![Website Hierarchical Structure](../assets/eip-6150/website-hierarchy.png) - -A single smart contract can be the `root`, managing every directory/category as individual NFT and hierarchy relations of NFTs. Each NFT's `tokenURI` may be another contract address, a website link, or any form of metadata. - -The advantages and the advancement of the Ethereum ecosystem of using this standard include: - -- Complete on-chain storage of hierarchy, which can also be governed on-chain by additional DAO contract -- Only need a single contract to manage and operate the hierarchical relations -- Transferrable directory/category ownership as NFT, which is great for use cases such as on-chain forums -- Easy and permissionless data access to the hierarchical structure by front-end -- Ideal structure for traditional applications such as e-commerce, or forums -- Easy-to-understand interfaces for developers, which are similar to Linux filesystem commands in concept - -The use cases can include: - -- On-chain forum, like Reddit -- On-chain social media, like Twitter -- On-chain corporation, for managing organizational structures -- On-chain e-commerce platforms, like eBay or individual stores -- Any application with tree-like structures - -In the future, with the development of the data availability solutions of Ethereum and an external permissionless data retention network, the content (posts, listed items, or tweets) of these platforms can also be entirely stored on-chain, thus realizing fully decentralized applications. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -Every compliant contract must implement this proposal, [EIP-721](./eip-721.md) and [EIP-165](./eip-165.md) interfaces. - -```solidity -pragma solidity ^0.8.0; - -// Note: the ERC-165 identifier for this interface is 0x897e2c73. -interface IERC6150 /* is IERC721, IERC165 */ { - /** - * @notice Emitted when `tokenId` token under `parentId` is minted. - * @param minter The address of minter - * @param to The address received token - * @param parentId The id of parent token, if it's zero, it means minted `tokenId` is a root token. - * @param tokenId The id of minted token, required to be greater than zero - */ - event Minted( - address indexed minter, - address indexed to, - uint256 parentId, - uint256 tokenId - ); - - /** - * @notice Get the parent token of `tokenId` token. - * @param tokenId The child token - * @return parentId The Parent token found - */ - function parentOf(uint256 tokenId) external view returns (uint256 parentId); - - /** - * @notice Get the children tokens of `tokenId` token. - * @param tokenId The parent token - * @return childrenIds The array of children tokens - */ - function childrenOf( - uint256 tokenId - ) external view returns (uint256[] memory childrenIds); - - /** - * @notice Check the `tokenId` token if it is a root token. - * @param tokenId The token want to be checked - * @return Return `true` if it is a root token; if not, return `false` - */ - function isRoot(uint256 tokenId) external view returns (bool); - - /** - * @notice Check the `tokenId` token if it is a leaf token. - * @param tokenId The token want to be checked - * @return Return `true` if it is a leaf token; if not, return `false` - */ - function isLeaf(uint256 tokenId) external view returns (bool); -} -``` - -Optional Extension: Enumerable - -```solidity -// Note: the ERC-165 identifier for this interface is 0xba541a2e. -interface IERC6150Enumerable is IERC6150 /* IERC721Enumerable */ { - /** - * @notice Get total amount of children tokens under `parentId` token. - * @dev If `parentId` is zero, it means get total amount of root tokens. - * @return The total amount of children tokens under `parentId` token. - */ - function childrenCountOf(uint256 parentId) external view returns (uint256); - - /** - * @notice Get the token at the specified index of all children tokens under `parentId` token. - * @dev If `parentId` is zero, it means get root token. - * @return The token ID at `index` of all chlidren tokens under `parentId` token. - */ - function childOfParentByIndex( - uint256 parentId, - uint256 index - ) external view returns (uint256); - - /** - * @notice Get the index position of specified token in the children enumeration under specified parent token. - * @dev Throws if the `tokenId` is not found in the children enumeration. - * If `parentId` is zero, means get root token index. - * @param parentId The parent token - * @param tokenId The specified token to be found - * @return The index position of `tokenId` found in the children enumeration - */ - function indexInChildrenEnumeration( - uint256 parentId, - uint256 tokenId - ) external view returns (uint256); -} -``` - -Optional Extension: Burnable - -```solidity -// Note: the ERC-165 identifier for this interface is 0x4ac0aa46. -interface IERC6150Burnable is IERC6150 { - /** - * @notice Burn the `tokenId` token. - * @dev Throws if `tokenId` is not a leaf token. - * Throws if `tokenId` is not a valid NFT. - * Throws if `owner` is not the owner of `tokenId` token. - * Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this token. - * @param tokenId The token to be burnt - */ - function safeBurn(uint256 tokenId) external; - - /** - * @notice Batch burn tokens. - * @dev Throws if one of `tokenIds` is not a leaf token. - * Throws if one of `tokenIds` is not a valid NFT. - * Throws if `owner` is not the owner of all `tokenIds` tokens. - * Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for all `tokenIds`. - * @param tokenIds The tokens to be burnt - */ - function safeBatchBurn(uint256[] memory tokenIds) external; -} -``` - -Optional Extension: ParentTransferable - -```solidity -// Note: the ERC-165 identifier for this interface is 0xfa574808. -interface IERC6150ParentTransferable is IERC6150 { - /** - * @notice Emitted when the parent of `tokenId` token changed. - * @param tokenId The token changed - * @param oldParentId Previous parent token - * @param newParentId New parent token - */ - event ParentTransferred( - uint256 tokenId, - uint256 oldParentId, - uint256 newParentId - ); - - /** - * @notice Transfer parentship of `tokenId` token to a new parent token - * @param newParentId New parent token id - * @param tokenId The token to be changed - */ - function transferParent(uint256 newParentId, uint256 tokenId) external; - - /** - * @notice Batch transfer parentship of `tokenIds` to a new parent token - * @param newParentId New parent token id - * @param tokenIds Array of token ids to be changed - */ - function batchTransferParent( - uint256 newParentId, - uint256[] memory tokenIds - ) external; -} -``` - -Optional Extension: Access Control - -```solidity -// Note: the ERC-165 identifier for this interface is 0x1d04f0b3. -interface IERC6150AccessControl is IERC6150 { - /** - * @notice Check the account whether a admin of `tokenId` token. - * @dev Each token can be set more than one admin. Admin have permission to do something to the token, like mint child token, - * or burn token, or transfer parentship. - * @param tokenId The specified token - * @param account The account to be checked - * @return If the account has admin permission, return true; otherwise, return false. - */ - function isAdminOf(uint256 tokenId, address account) - external - view - returns (bool); - - /** - * @notice Check whether the specified parent token and account can mint children tokens - * @dev If the `parentId` is zero, check whether account can mint root nodes - * @param parentId The specified parent token to be checked - * @param account The specified account to be checked - * @return If the token and account has mint permission, return true; otherwise, return false. - */ - function canMintChildren( - uint256 parentId, - address account - ) external view returns (bool); - - /** - * @notice Check whether the specified token can be burnt by specified account - * @param tokenId The specified token to be checked - * @param account The specified account to be checked - * @return If the tokenId can be burnt by account, return true; otherwise, return false. - */ - function canBurnTokenByAccount(uint256 tokenId, address account) - external - view - returns (bool); -} -``` - -## Rationale - -As mentioned in the abstract, this EIP's goal is to have a simple interface for supporting Hierarchical NFTs. Here are a few design decisions and why they were made: - -### Relationship between NFTs - -All NFTs will make up a hierarchical relationship tree. Each NFT is a node of the tree, maybe as a root node or a leaf node, as a parent node or a child node. - -This proposal standardizes the event `Minted` to indicate the parent and child relationship when minting a new node. When a root node is minted, parentId should be zero. That means a token id of zero could not be a real node. So a real node token id must be greater than zero. - -In a hierarchical tree, it's common to query upper and lower nodes. So this proposal standardizes function `parentOf` to get the parent node of the specified node and standardizes function `childrenOf` to get all children nodes. - -Functions `isRoot` and `isLeaf` can check if one node is a root node or a leaf node, which would be very useful for many cases. - -### Enumerable Extension - -This proposal standardizes three functions as an extension to support enumerable queries involving children nodes. Each function all have param `parentId`, for compatibility, when the `parentId` specified zero means query root nodes. - -### ParentTransferable Extension - -In some cases, such as filesystem, a directory or a file could be moved from one directory to another. So this proposal adds ParentTransferable Extension to support this situation. - -### Access Control - -In a hierarchical structure, usually, there is more than one account has permission to operate a node, like mint children nodes, transfer node, burn node. This proposal adds a few functions as standard to check access control permissions. - -## Backwards Compatibility - -This proposal is fully backward compatible with [EIP-721](./eip-721.md). - -## Reference Implementation - -Implementation: [EIP-6150](../assets/eip-6150/contracts/ERC6150.sol) - -## Security Considerations - -No security considerations were found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6150.md diff --git a/EIPS/eip-6170.md b/EIPS/eip-6170.md index ed5fdd2dc45f9e..0fe37dc1bf4f13 100644 --- a/EIPS/eip-6170.md +++ b/EIPS/eip-6170.md @@ -1,108 +1 @@ ---- -eip: 6170 -title: Cross-Chain Messaging Interface -description: A common smart contract interface for interacting with messaging protocols. -author: Sujith Somraaj (@sujithsomraaj) -discussions-to: https://ethereum-magicians.org/t/cross-chain-messaging-standard/12197 -status: Draft -type: Standards Track -category: ERC -created: 2022-12-19 ---- - -## Abstract - -This EIP introduces a common interface for cross-chain arbitrary message bridges (AMBs) to send and receive a cross-chain message (state). - -## Motivation - -Currently, cross-chain arbitrary message bridges lack standardization, resulting in complex competing implementations: Layerzero, Hyperlane, Axelar, Wormhole, Matic State Tunnel and others. Either chain native (or) seperate message bridge, the problem prevails. Adding a common standardized interface to the arbitrary message bridges provides these benefits: - -- **Ease Of Development:** A common standard interface would help developers build scalable cross-chain applications with ease. - -- **Improved Scalability:** Cross-chain applications can efficiently use multiple message bridges. - -- **Improved Security:** Confronting security to specific parameters. At present, every message bridge has its diverse security variable. E.g., In Layerzero, the nonce is used to prevent a replay attack, whereas Hyperlane uses the Merkle root hash. - -- **Improved Robustness:** Message bridges involving off-chain components are not censorship-resistant and are prone to downtimes. Hence, apps built on top of them have no choice but to migrate their entire state (which is highly impossible for large complex applications). - -## Specification - -The keywords "MUST," "MUST NOT," "REQUIRED," "SHALL," "SHALL NOT," "SHOULD," "SHOULD NOT," "RECOMMENDED," "MAY," and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -Every compliant cross-chain arbitrary message bridge must implement the following interface. - -``` solidity -// SPDX-License-Identifier: Apache-3.0 - -pragma solidity >=0.8.0; - -/// @title Cross-Chain Messaging interface -/// @dev Allows seamless interchain messaging. -/// @author Sujith Somraaj -/// Note: Bytes are used throughout the implementation to support non-evm chains. - -interface IEIP6170 { - /// @dev This emits when a cross-chain message is sent. - /// Note: MessageSent MUST trigger when a message is sent, including zero bytes transfers. - event MessageSent( - bytes to, - bytes toChainId, - bytes message, - bytes extraData - ); - - /// @dev This emits when a cross-chain message is received. - /// MessageReceived MUST trigger on any successful call to receiveMessage(bytes chainId, bytes sender, bytes message) function. - event MessageReceived(bytes from, bytes fromChainId, bytes message); - - /// @dev Sends a message to a receiving address on a different blockchain. - /// @param chainId_ is the unique identifier of receiving blockchain. - /// @param receiver_ is the address of the receiver. - /// @param message_ is the arbitrary message to be delivered. - /// @param data_ is a bridge-specific encoded data for off-chain relayer infrastructure. - /// @return the status of the process on the sending chain. - /// Note: this function is designed to support both evm and non-evm chains - /// Note: proposing chain-ids be the bytes encoding their native token name string. For eg., abi.encode("ETH"), abi.encode("SOL") imagining they cannot override. - function sendMessage( - bytes memory chainId_, - bytes memory receiver_, - bytes memory message_, - bytes memory data_ - ) external payable returns (bool); - - /// @dev Receives a message from a sender on a different blockchain. - /// @param chainId_ is the unique identifier of the sending blockchain. - /// @param sender_ is the address of the sender. - /// @param message_ is the arbitrary message sent by the sender. - /// @param data_ is an additional parameter to be used for security purposes. E.g, can send nonce in layerzero. - /// @return the status of message processing/storage. - /// Note: sender validation (or) message validation should happen before processing the message. - function receiveMessage( - bytes memory chainId_, - bytes memory sender_, - bytes memory message_, - bytes memory data_ - ) external payable returns (bool); -} -``` - -## Rationale - -The cross-chain arbitrary messaging interface will optimize the interoperability layer between blockchains with a feature-complete yet minimal interface. The light-weighted approach also provides arbitrary message bridges, and the freedom of innovating at the relayer level, to show their technical might. - -The EIP will make blockchains more usable and scalable. It opens up the possibilities for building cross-chain applications by leveraging any two blockchains, not just those limited to Ethereum and compatible L2s. To put this into perspective, an easy-to-communicate mechanism will allow developers to build cross-chain applications across Ethereum and Solana, leveraging their unique advantages. - -The interface also aims to reduce the risks of a single point of failure (SPOF) for applications/protocols, as they can continue operating by updating their AMB address. - -## Security Considerations - -Fully permissionless messaging could be a security threat to the protocol. It is recommended that all the integrators review the implementation of messaging tunnels before integrating. - -Without sender authentication, anyone could write arbitrary messages into the receiving smart contract. - -This EIP focuses only on how the messages should be sent and received with a specific standard. But integrators can implement any authentication (or) message tunnel-specific operations inside the receive function leveraging `data_` parameter. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md) +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6170.md diff --git a/EIPS/eip-6220.md b/EIPS/eip-6220.md index b744da0d7c49e6..f20cffdb680f67 100644 --- a/EIPS/eip-6220.md +++ b/EIPS/eip-6220.md @@ -1,475 +1 @@ ---- -eip: 6220 -title: Composable NFTs utilizing Equippable Parts -description: An interface for Composable non-fungible tokens through fixed and slot parts equipping. -author: Bruno Škvorc (@Swader), Cicada (@CicadaNCR), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/eip-6220-composable-nfts-utilizing-equippable-parts/12289 -status: Final -type: Standards Track -category: ERC -created: 2022-12-20 -requires: 165, 721, 5773, 6059 ---- - -## Abstract - -The Composable NFTs utilizing equippable parts standard extends [ERC-721](./eip-721.md) by allowing the NFTs to selectively add parts to themselves via equipping. - -Tokens can be composed by cherry picking the list of parts from a Catalog for each NFT instance, and are able to equip other NFTs into slots, which are also defined within the Catalog. Catalogs contain parts from which NFTs can be composed. - -This proposal introduces two types of parts; slot type of parts and fixed type of parts. The slot type of parts allow for other NFT collections to be equipped into them, while fixed parts are full components with their own metadata. - -Equipping a part into an NFT doesn't generate a new token, but rather adds another component to be rendered when retrieving the token. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability for tokens to equip other tokens and be composed from a set of available parts allows for greater utility, usability and forward compatibility. - -In the four years since [ERC-721](./eip-721.md) was published, the need for additional functionality has resulted in countless extensions. This EIP improves upon ERC-721 in the following areas: - -- [Composing](#composing) -- [Token progression](#token-progression) -- [Merit tracking](#merit-tracking) -- [Provable Digital Scarcity](#provable-digital-scarcity) - -### Composing - -NFTs can work together to create a greater construct. Prior to this proposal, multiple NFTs could be composed into a single construct either by checking all of the compatible NFTs associated with a given account and used indiscriminately (which could result in unexpected result if there was more than one NFT intended to be used in the same slot), or by keeping a custom ledger of parts to compose together (either in a smart contract or an off-chain database). This proposal establishes a standardized framework for composable NFTs, where a single NFT can select which parts should be a part of the whole, with the information being on chain. Composing NFTs in such a way allows for virtually unbounded customization of the base NFT. An example of this could be a movie NFT. Some parts, like credits, should be fixed. Other parts, like scenes, should be interchangeable, so that various releases (base version, extended cuts, anniversary editions,...) can be replaced. - -### Token progression - -As the token progresses through various stages of its existence, it can attain or be awarded various parts. This can be explained in terms of gaming. A character could be represented by an NFT utilizing this proposal and would be able to equip gear acquired through the gameplay activities and as it progresses further in the game, better items would be available. In stead of having numerous NFTs representing the items collected through its progression, equippable parts can be unlocked and the NFT owner would be able to decide which items to equip and which to keep in the inventory (not equipped) without need of a centralized party. - -### Merit tracking - -An equippable NFT can also be used to track merit. An example of this is academic merit. The equippable NFT in this case would represent a sort of digital portfolio of academic achievements, where the owner would be able to equip their diplomas, published articles and awards for all to see. - -### Provable Digital Scarcity - -The majority of current NFT projects are only mock-scarce. Even with a limited supply of tokens, the utility of these (if any) is uncapped. As an example, you can log into 500 different instances of the same game using the same wallet and the same NFT. You can then equip the same hat onto 500 different in-game avatars at the same time, because its visual representation is just a client-side mechanic. - -This proposal adds the ability to enforce that, if a hat is equipped on one avatar (by being sent into it and then equipped), it cannot be equipped on another. This provides real digital scarcity. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Equippable tokens - -The interface of the core smart contract of the equippable tokens. - -```solidity -/// @title EIP-6220 Composable NFTs utilizing Equippable Parts -/// @dev See https://eips.ethereum.org/EIPS/eip-6220 -/// @dev Note: the ERC-165 identifier for this interface is 0x28bc9ae4. - -pragma solidity ^0.8.16; - -import "./IERC5773.sol"; - -interface IERC6220 is IERC5773 /*, ERC165 */ { - /** - * @notice Used to store the core structure of the `Equippable` component. - * @return assetId The ID of the asset equipping a child - * @return childAssetId The ID of the asset used as equipment - * @return childId The ID of token that is equipped - * @return childEquippableAddress Address of the collection to which the child asset belongs to - */ - struct Equipment { - uint64 assetId; - uint64 childAssetId; - uint256 childId; - address childEquippableAddress; - } - - /** - * @notice Used to provide a struct for inputing equip data. - * @dev Only used for input and not storage of data. - * @return tokenId ID of the token we are managing - * @return childIndex Index of a child in the list of token's active children - * @return assetId ID of the asset that we are equipping into - * @return slotPartId ID of the slot part that we are using to equip - * @return childAssetId ID of the asset that we are equipping - */ - struct IntakeEquip { - uint256 tokenId; - uint256 childIndex; - uint64 assetId; - uint64 slotPartId; - uint64 childAssetId; - } - - /** - * @notice Used to notify listeners that a child's asset has been equipped into one of its parent assets. - * @param tokenId ID of the token that had an asset equipped - * @param assetId ID of the asset associated with the token we are equipping into - * @param slotPartId ID of the slot we are using to equip - * @param childId ID of the child token we are equipping into the slot - * @param childAddress Address of the child token's collection - * @param childAssetId ID of the asset associated with the token we are equipping - */ - event ChildAssetEquipped( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed slotPartId, - uint256 childId, - address childAddress, - uint64 childAssetId - ); - - /** - * @notice Used to notify listeners that a child's asset has been unequipped from one of its parent assets. - * @param tokenId ID of the token that had an asset unequipped - * @param assetId ID of the asset associated with the token we are unequipping out of - * @param slotPartId ID of the slot we are unequipping from - * @param childId ID of the token being unequipped - * @param childAddress Address of the collection that a token that is being unequipped belongs to - * @param childAssetId ID of the asset associated with the token we are unequipping - */ - event ChildAssetUnequipped( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed slotPartId, - uint256 childId, - address childAddress, - uint64 childAssetId - ); - - /** - * @notice Used to notify listeners that the assets belonging to a `equippableGroupId` have been marked as - * equippable into a given slot and parent - * @param equippableGroupId ID of the equippable group being marked as equippable into the slot associated with - * `slotPartId` of the `parentAddress` collection - * @param slotPartId ID of the slot part of the catalog into which the parts belonging to the equippable group - * associated with `equippableGroupId` can be equipped - * @param parentAddress Address of the collection into which the parts belonging to `equippableGroupId` can be - * equipped - */ - event ValidParentEquippableGroupIdSet( - uint64 indexed equippableGroupId, - uint64 indexed slotPartId, - address parentAddress - ); - - /** - * @notice Used to equip a child into a token. - * @dev The `IntakeEquip` stuct contains the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data An `IntakeEquip` struct specifying the equip data - */ - function equip( - IntakeEquip memory data - ) external; - - /** - * @notice Used to unequip child from parent token. - * @dev This can only be called by the owner of the token or by an account that has been granted permission to - * manage the given token by the current owner. - * @param tokenId ID of the parent from which the child is being unequipped - * @param assetId ID of the parent's asset that contains the `Slot` into which the child is equipped - * @param slotPartId ID of the `Slot` from which to unequip the child - */ - function unequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) external; - - /** - * @notice Used to check whether the token has a given child equipped. - * @dev This is used to prevent from transferring a child that is equipped. - * @param tokenId ID of the parent token for which we are querying for - * @param childAddress Address of the child token's smart contract - * @param childId ID of the child token - * @return bool The boolean value indicating whether the child token is equipped into the given token or not - */ - function isChildEquipped( - uint256 tokenId, - address childAddress, - uint256 childId - ) external view returns (bool); - - /** - * @notice Used to verify whether a token can be equipped into a given parent's slot. - * @param parent Address of the parent token's smart contract - * @param tokenId ID of the token we want to equip - * @param assetId ID of the asset associated with the token we want to equip - * @param slotId ID of the slot that we want to equip the token into - * @return bool The boolean indicating whether the token with the given asset can be equipped into the desired - * slot - */ - function canTokenBeEquippedWithAssetIntoSlot( - address parent, - uint256 tokenId, - uint64 assetId, - uint64 slotId - ) external view returns (bool); - - /** - * @notice Used to get the Equipment object equipped into the specified slot of the desired token. - * @dev The `Equipment` struct consists of the following data: - * [ - * assetId, - * childAssetId, - * childId, - * childEquippableAddress - * ] - * @param tokenId ID of the token for which we are retrieving the equipped object - * @param targetCatalogAddress Address of the `Catalog` associated with the `Slot` part of the token - * @param slotPartId ID of the `Slot` part that we are checking for equipped objects - * @return struct The `Equipment` struct containing data about the equipped object - */ - function getEquipment( - uint256 tokenId, - address targetCatalogAddress, - uint64 slotPartId - ) external view returns (Equipment memory); - - /** - * @notice Used to get the asset and equippable data associated with given `assetId`. - * @param tokenId ID of the token for which to retrieve the asset - * @param assetId ID of the asset of which we are retrieving - * @return metadataURI The metadata URI of the asset - * @return equippableGroupId ID of the equippable group this asset belongs to - * @return catalogAddress The address of the catalog the part belongs to - * @return partIds An array of IDs of parts included in the asset - */ - function getAssetAndEquippableData(uint256 tokenId, uint64 assetId) - external - view - returns ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - uint64[] calldata partIds - ); -} -``` - -### Catalog - -The interface of the Catalog containing the equippable parts. Catalogs are collections of equippable fixed and slot parts and are not restricted to a single collection, but can support any number of NFT collections. - -```solidity -/** - * @title ICatalog - * @notice An interface Catalog for equippable module. - * @dev Note: the ERC-165 identifier for this interface is 0xd912401f. - */ - -pragma solidity ^0.8.16; - -interface ICatalog /* is IERC165 */ { - /** - * @notice Event to announce addition of a new part. - * @dev It is emitted when a new part is added. - * @param partId ID of the part that was added - * @param itemType Enum value specifying whether the part is `None`, `Slot` and `Fixed` - * @param zIndex An uint specifying the z value of the part. It is used to specify the depth which the part should - * be rendered at - * @param equippableAddresses An array of addresses that can equip this part - * @param metadataURI The metadata URI of the part - */ - event AddedPart( - uint64 indexed partId, - ItemType indexed itemType, - uint8 zIndex, - address[] equippableAddresses, - string metadataURI - ); - - /** - * @notice Event to announce new equippables to the part. - * @dev It is emitted when new addresses are marked as equippable for `partId`. - * @param partId ID of the part that had new equippable addresses added - * @param equippableAddresses An array of the new addresses that can equip this part - */ - event AddedEquippables( - uint64 indexed partId, - address[] equippableAddresses - ); - - /** - * @notice Event to announce the overriding of equippable addresses of the part. - * @dev It is emitted when the existing list of addresses marked as equippable for `partId` is overwritten by a new - * one. - * @param partId ID of the part whose list of equippable addresses was overwritten - * @param equippableAddresses The new, full, list of addresses that can equip this part - */ - event SetEquippables(uint64 indexed partId, address[] equippableAddresses); - - /** - * @notice Event to announce that a given part can be equipped by any address. - * @dev It is emitted when a given part is marked as equippable by any. - * @param partId ID of the part marked as equippable by any address - */ - event SetEquippableToAll(uint64 indexed partId); - - /** - * @notice Used to define a type of the item. Possible values are `None`, `Slot` or `Fixed`. - * @dev Used for fixed and slot parts. - */ - enum ItemType { - None, - Slot, - Fixed - } - - /** - * @notice The integral structure of a standard RMRK catalog item defining it. - * @dev Requires a minimum of 3 storage slots per catalog item, equivalent to roughly 60,000 gas as of Berlin hard fork - * (April 14, 2021), though 5-7 storage slots is more realistic, given the standard length of an IPFS URI. This - * will result in between 25,000,000 and 35,000,000 gas per 250 assets--the maximum block size of Ethereum - * mainnet is 30M at peak usage. - * @return itemType The item type of the part - * @return z The z value of the part defining how it should be rendered when presenting the full NFT - * @return equippable The array of addresses allowed to be equipped in this part - * @return metadataURI The metadata URI of the part - */ - struct Part { - ItemType itemType; //1 byte - uint8 z; //1 byte - address[] equippable; //n Collections that can be equipped into this slot - string metadataURI; //n bytes 32+ - } - - /** - * @notice The structure used to add a new `Part`. - * @dev The part is added with specified ID, so you have to make sure that you are using an unused `partId`, - * otherwise the addition of the part vill be reverted. - * @dev The full `IntakeStruct` looks like this: - * [ - * partID, - * [ - * itemType, - * z, - * [ - * permittedCollectionAddress0, - * permittedCollectionAddress1, - * permittedCollectionAddress2 - * ], - * metadataURI - * ] - * ] - * @return partId ID to be assigned to the `Part` - * @return part A `Part` to be added - */ - struct IntakeStruct { - uint64 partId; - Part part; - } - - /** - * @notice Used to return the metadata URI of the associated catalog. - * @return string Base metadata URI - */ - function getMetadataURI() external view returns (string memory); - - /** - * @notice Used to return the `itemType` of the associated catalog - * @return string `itemType` of the associated catalog - */ - function getType() external view returns (string memory); - - /** - * @notice Used to check whether the given address is allowed to equip the desired `Part`. - * @dev Returns true if a collection may equip asset with `partId`. - * @param partId The ID of the part that we are checking - * @param targetAddress The address that we are checking for whether the part can be equipped into it or not - * @return bool The status indicating whether the `targetAddress` can be equipped into `Part` with `partId` or not - */ - function checkIsEquippable(uint64 partId, address targetAddress) - external - view - returns (bool); - - /** - * @notice Used to check if the part is equippable by all addresses. - * @dev Returns true if part is equippable to all. - * @param partId ID of the part that we are checking - * @return bool The status indicating whether the part with `partId` can be equipped by any address or not - */ - function checkIsEquippableToAll(uint64 partId) external view returns (bool); - - /** - * @notice Used to retrieve a `Part` with id `partId` - * @param partId ID of the part that we are retrieving - * @return struct The `Part` struct associated with given `partId` - */ - function getPart(uint64 partId) external view returns (Part memory); - - /** - * @notice Used to retrieve multiple parts at the same time. - * @param partIds An array of part IDs that we want to retrieve - * @return struct An array of `Part` structs associated with given `partIds` - */ - function getParts(uint64[] calldata partIds) - external - view - returns (Part[] memory); -} -``` - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **Why are we using a Catalog in stead of supporting direct NFT equipping?**\ -If NFTs could be directly equipped into other NFTs without any oversight, the resulting composite would be unpredictable. Catalog allows for parts to be pre-verified in order to result in a composite that composes as expected. Another benefit of Catalog is the ability of defining reusable fixed parts. -2. **Why do we propose two types of parts?**\ -Some parts, that are the same for all of the tokens, don't make sense to be represented by individual NFTs, so they can be represented by fixed parts. This reduces the clutter of the owner's wallet as well as introduces an efficient way of disseminating repetitive assets tied to NFTs.\ -The slot parts allow for equipping NFTs into them. This provides the ability to equip unrelated NFT collections into the base NFT after the unrelated collection has been verified to compose properly.\ -Having two parts allows for support of numerous use cases and, since the proposal doesn't enforce the use of both it can be applied in any configuration needed. -3. **Why is a method to get all of the equipped parts not included?**\ -Getting all parts might not be an operation necessary for all implementers. Additionally, it can be added either as an extension, doable with hooks, or can be emulated using an indexer. -4. **Should Catalog be limited to support one NFT collection at a time or be able to support any nunmber of collections?**\ -As the Catalog is designed in a way that is agnostic to the use case using it. It makes sense to support as wide reusability as possible. Having one Catalog supporting multiple collections allows for optimized operation and reduced gas prices when deploying it and setting fixed as well as slot parts. - -### Fixed parts - -Fixed parts are defined and contained in the Catalog. They have their own metadata and are not meant to change through the lifecycle of the NFT. - -A fixed part cannot be replaced. - -The benefit of fixed parts is that they represent equippable parts that can be equipped by any number of tokens in any number of collections and only need to be defined once. - -### Slot parts - -Slot parts are defined and contained in the Catalog. They don't have their own metadata, but rather support equipping of selected NFT collections into them. The tokens equipped into the slots however, contain their own metadata. This allows for an equippable modifialbe content of the base NFT controlled by its owner. As they can be equipped into any number of tokens of any number of collections, they allow for reliable composing of the final tokens by vetting which NFTs can be equipped by a given slot once and then reused any number of times. - -## Backwards Compatibility - -The Equippable token standard has been made compatible with [ERC-721](./eip-721.md) in order to take advantage of the robust tooling available for implementations of ERC-721 and to ensure compatibility with existing ERC-721 infrastructure. - -## Test Cases - -Tests are included in [`equippableFixedParts.ts`](../assets/eip-6220/test/equippableFixedParts.ts) and [`equippableSlotParts.ts`](../assets/eip-6220/test/equippableSlotParts.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-6220 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`EquippableToken.sol`](../assets/eip-6220/contracts/EquippableToken.sol). - - -## Security Considerations - -The same security considerations as with [ERC-721](./eip-721.md) apply: hidden logic may be present in any of the functions, including burn, add resource, accept resource, and more. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6220.md diff --git a/EIPS/eip-6224.md b/EIPS/eip-6224.md index 4b6f22ee29e7a7..dfa60d9350f338 100644 --- a/EIPS/eip-6224.md +++ b/EIPS/eip-6224.md @@ -1,255 +1 @@ ---- -eip: 6224 -title: Contracts Dependencies Registry -description: An interface for managing smart contracts with their dependencies. -author: Artem Chystiakov (@arvolear) -discussions-to: https://ethereum-magicians.org/t/eip-6224-contracts-dependencies-registry/12316 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-12-27 -requires: 1967, 5750 ---- - -## Abstract - -The EIP standardizes the management of smart contracts within the decentralized application ecosystem. It enables protocols to become upgradeable and reduces their maintenance threshold. This EIP additionally introduces a smart contract dependency injection mechanism to audit dependency usage, to aid larger composite projects. - -## Motivation - -In the ever-growing Ethereum, projects tend to become more and more complex. Modern protocols require portability and agility to satisfy customer needs by continuously delivering new features and staying on pace with the industry. However, the requirement is hard to achieve due to the immutable nature of blockchains and smart contracts. Moreover, the increased complexity and continuous delivery bring bugs and entangle the dependencies between the contracts, making systems less supportable. - -Applications that have a clear facade and transparency upon their dependencies are easier to develop and maintain. The given EIP tries to solve the aforementioned problems by presenting two concepts: the **contracts registry** and the **dependant**. - -The advantages of using the provided pattern might be: - -- Structured smart contracts management via specialized contract. -- Ad-hoc upgradeability provision. -- Runtime smart contracts addition, removal, and substitution. -- Dependency injection mechanism to keep smart contracts' dependencies under control. - -## 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. - -### ContractsRegistry - -The `ContractsRegistry` MUST implement the following interface: - -```solidity -pragma solidity ^0.8.0; - -interface IContractsRegistry { - /** - * @notice REQUIRED The event that is emitted when the contract gets added to the registry - * @param name the name of the contract - * @param contractAddress the address of the added contract - * @param isProxy whether the added contract is a proxy - */ - event AddedContract(string name, address contractAddress, bool isProxy); - - /** - * @notice REQUIRED The event that is emitted when the contract get removed from the registry - * @param name the name of the removed contract - */ - event RemovedContract(string name); - - /** - * @notice REQUIRED The function that returns an associated contract by the name - * @param name the name of the contract - * @return the address of the contract - */ - function getContract(string memory name) external view returns (address); - - /** - * @notice OPTIONAL The function that checks if a contract with a given name has been added - * @param name the name of the contract - * @return true if the contract is present in the registry - */ - function hasContract(string memory name) external view returns (bool); - - /** - * @notice RECOMMENDED The function that returns the admin of the added proxy contracts - * @return the proxy admin address - */ - function getProxyUpgrader() external view returns (address); - - /** - * @notice RECOMMENDED The function that returns an implementation of the given proxy contract - * @param name the name of the contract - * @return the implementation address - */ - function getImplementation(string memory name) external view returns (address); - - /** - * @notice REQUIRED The function that injects dependencies into the given contract. - * MUST call the setDependencies() with address(this) and bytes("") as arguments on the substituted contract - * @param name the name of the contract - */ - function injectDependencies(string memory name) external; - - /** - * @notice REQUIRED The function that injects dependencies into the given contract with extra data. - * MUST call the setDependencies() with address(this) and given data as arguments on the substituted contract - * @param name the name of the contract - * @param data the extra context data - */ - function injectDependenciesWithData( - string calldata name, - bytes calldata data - ) external; - - /** - * @notice REQUIRED The function that upgrades added proxy contract with a new implementation - * @param name the name of the proxy contract - * @param newImplementation the new implementation the proxy will be upgraded to - * - * It is the Owner's responsibility to ensure the compatibility between implementations - */ - function upgradeContract(string memory name, address newImplementation) external; - - /** - * @notice RECOMMENDED The function that upgrades added proxy contract with a new implementation, providing data - * @param name the name of the proxy contract - * @param newImplementation the new implementation the proxy will be upgraded to - * @param data the data that the new implementation will be called with. This can be an ABI encoded function call - * - * It is the Owner's responsibility to ensure the compatibility between implementations - */ - function upgradeContractAndCall( - string memory name, - address newImplementation, - bytes memory data - ) external; - - /** - * @notice REQUIRED The function that adds pure (non-proxy) contracts to the ContractsRegistry. The contracts MAY either be - * the ones the system does not have direct upgradeability control over or the ones that are not upgradeable by design - * @param name the name to associate the contract with - * @param contractAddress the address of the contract - */ - function addContract(string memory name, address contractAddress) external; - - /** - * @notice REQUIRED The function that adds the contracts and deploys the Transaprent proxy above them. - * It MAY be used to add contract that the ContractsRegistry has to be able to upgrade - * @param name the name to associate the contract with - * @param contractAddress the address of the implementation - */ - function addProxyContract(string memory name, address contractAddress) external; - - /** - * @notice RECOMMENDED The function that adds an already deployed proxy to the ContractsRegistry. It MAY be used - * when the system migrates to the new ContractRegistry. In that case, the new ProxyUpgrader MUST have the - * credentials to upgrade the newly added proxies - * @param name the name to associate the contract with - * @param contractAddress the address of the proxy - */ - function justAddProxyContract(string memory name, address contractAddress) external; - - /** - * @notice REQUIRED The function to remove contracts from the ContractsRegistry - * @param name the associated name with the contract - */ - function removeContract(string memory name) external; -} -``` - -- The `ContractsRegistry` MUST deploy the `ProxyUpgrader` contract in the constructor that MUST be set as an admin of `Transparent` proxies deployed via `addProxyContract` method. -- It MUST NOT be possible to add the zero address to the `ContractsRegistry`. -- The `ContractsRegistry` MUST use the `IDependant` interface in the `injectDependencies` and `injectDependenciesWithData` methods. - -### Dependant - -The `Dependant` contract is the one that depends on other contracts present in the system. In order to support dependency injection mechanism, the dependant contract MUST implement the following interface: - -```solidity -pragma solidity ^0.8.0; - -interface IDependant { - /** - * @notice The function that is called from the ContractsRegistry (or factory) to inject dependencies. - * @param contractsRegistry the registry to pull dependencies from - * @param data the extra data that might provide additional application-specific context/behavior - * - * The Dependant MUST perform a dependency injector access check to this method - */ - function setDependencies(address contractsRegistry, bytes calldata data) external; - - /** - * @notice The function that sets the new dependency injector. - * @param injector the new dependency injector - * - * The Dependant MUST perform a dependency injector access check to this method - */ - function setInjector(address injector) external; - - /** - * @notice The function that gets the current dependency injector - * @return the current dependency injector - */ - function getInjector() external view returns (address); -} -``` - -- The `Dependant` contract MUST pull its dependencies in the `setDependencies` method from the passed `contractsRegistry` address. -- The `Dependant` contract MAY store the dependency injector address in the special slot `0x3d1f25f1ac447e55e7fec744471c4dab1c6a2b6ffb897825f9ea3d2e8c9be583` (obtained as `bytes32(uint256(keccak256("eip6224.dependant.slot")) - 1)`). - - -## Rationale - -There are a few design decisions that have to be specified explicitly: - -### ContractsRegistry Rationale - -#### Usage - -The extensions of this EIP SHOULD add proper access control checks to the described non-view methods. - -The `getContract` and `getImplementation` methods MUST revert if the nonexistent contracts are queried. - -The `ContractsRegistry` MAY be set behind the proxy to enable runtime addition of custom methods. Applications MAY also leverage the pattern to develop custom tree-like `ContractsRegistry` data structures. - -#### Contracts identifier - -The `string` contracts identifier is chosen over the `uint256` and `bytes32` to maintain code readability and reduce the human-error chances when interacting with the `ContractsRegistry`. Being the topmost smart contract, it MAY be typical for the users to interact with it via block explorers or DAOs. Clarity was prioritized over gas usage. - -#### Proxy - -The `Transparent` proxy is chosen over the `UUPS` proxy to hand the upgradeability responsibility to the `ContractsRegistry` itself. The extensions of this EIP MAY use the proxy of their choice. - -### Dependant Rationale - -#### Dependencies - -The required dependencies MUST be set in the overridden `setDependencies` method, not in the `constructor` or `initializer` methods. - -The `data` parameter is provided to carry additional application-specific context. It MAY be used to extend the method's behavior. - -#### Injector - -Only the injector MUST be able to call the `setDependencies` and `setInjector` methods. The initial injector will be a zero address, in that case, the call MUST NOT revert on access control checks. The `setInjector` function is made `external` to support the dependency injection mechanism for factory-made contracts. However, the method SHOULD be used with extra care. - -The injector address MAY be stored in the dedicated slot `0x3d1f25f1ac447e55e7fec744471c4dab1c6a2b6ffb897825f9ea3d2e8c9be583` to exclude the chances of storage collision. - -## Reference Implementation - -*0xdistributedlab-solidity-library dev-modules* provides a reference implementation. - -## Security Considerations - -The described EIP must be used with extra care as the loss/leakage of credentials to the `ContractsRegistry` leads to the application's point of no return. The `ContractRegistry` is a cornerstone of the protocol, access must be granted to the trusted parties only. - -### ContractsRegistry Security Considerations - -- The non-view methods of `ContractsRegistry` contract MUST be overridden with proper access control checks. -- The `ContractsRegistry` does not perform any upgradeability checks between the proxy upgrades. It is the user's responsibility to make sure that the new implementation is compatible with the old one. - -### Dependant Security Considerations - -- The non-view methods of `Dependant` contract MUST be overridden with proper access control checks. Only the dependency injector MUST be able to call them. -- The `Dependant` contract MUST set its dependency injector no later than the first call to the `setDependencies` function is made. That being said, it is possible to front-run the first dependency injection. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6224.md diff --git a/EIPS/eip-6239.md b/EIPS/eip-6239.md index cb06821a920f52..e6f6aeb0fb7698 100644 --- a/EIPS/eip-6239.md +++ b/EIPS/eip-6239.md @@ -1,302 +1 @@ ---- -eip: 6239 -title: Semantic Soulbound Tokens -description: Adding RDF triples to ERC-5192 token metadata to capture social meaning -author: Jessica Chang (@JessicaChg) -discussions-to: https://ethereum-magicians.org/t/eip-6239-semantic-soulbound-tokens/12334 -status: Final -type: Standards Track -category: ERC -created: 2022-12-30 -requires: 165, 721, 5192 ---- - -## Abstract - -This proposal extends [ERC-721](./eip-721.md) and [ERC-5192](./eip-5192.md) by introducing Resource Description Framework (RDF) triples to Soulbound Tokens' (‘SBTs‘) metadata. - - - -## Motivation - -A Soulbound Token represents the commitments, credentials, and affiliations of accounts. RDF is a standard data model developed by the World Wide Web Consortium (‘W3C’) and is used to represent information in a structured format. Semantic SBTs are built on existing [ERC-721](./eip-721.md) and [ERC-5192](./eip-5192.md) standards to include RDF triples in metadata to capture and store the meaning of social metadata as a network of accounts and attributes. - -Semantic SBT provides a foundation for publishing, linking, and integrating data from multiple sources, and enables the ability to query and retrieve information across these sources, using inference to uncover new insights from existing social relations. For example, form the on-chain united social graph, assign trusted contacts for social recovery, and supports fair governance. - -While the existence of SBTs can create a decentralized social framework, there still needs to specify a common data model to manage the social metadata on-chain in a trustless manner, describing social metadata in an interconnected way, make it easy to be exchanged, integrated and discovered. And to further fuel the boom of the SBTs ecosystem, we need a bottom-up and decentralized way to maintain people’s social identity related information. - -Semantic SBTs address this by storing social metadata, attestations, and access permissions on-chain to bootstrap the social identity layer and a linked data layer natively on Ethereum, and bring semantic meanings to the tons of bits of on-chain data. - -### Connectedness - -Semantic SBTs store social data as RDF triples in the Subject-Predicate-Object format, making it easy to create relationships between accounts and attributes. RDF is a standard for data interchange used to represent highly interconnected data. Representing data in RDF triples makes it simpler for automated systems to identify, clarify, and connect information. - -### Linked Data - -Semantic SBTs allow the huge amount of social data on-chain to be available in a standard format (RDF) and be reachable and manageable. The interrelated datasets on-chain can create the linked data layer that allows social data to be mixed, exposed, and shared across different applications, providing a convenient, cheap, and reliable way to retrieve data, regardless of the number of users. - -### Social Identity - -Semantic SBTs allow people to publish or attest their own identity-related data in a bottom-up and decentralized way, without reliance on any centralized intermediaries while setting every party free. The data is fragmentary in each Semantic SBT and socially interrelated. RDF triples enable various community detection algorithms to be built on top. - -This proposal outlines the semantic data modeling of SBTs that allows implementers to model the social relations among Semantic SBTs, especially in the social sector. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -- The token **MUST** implement the following interfaces: - 1. [ERC-165](./eip-165.md)’s `ERC165` (`0x01ffc9a7`) - 1. [ERC-721](./eip-721.md)’s `ERC721` (`0x80ac58cd`) - 1. [ERC-721](./eip-721.md)’s `ERC721Metadata` (`0x5b5e139f`) - 1. [ERC-5192](./eip-5192.md)’s `ERC5192` (`0xb45a3c0e`) - -### RDF Statement - -RDF statements come in various formats, we have selected the six most commonly used formats: `nt(N-Triples)`,`ttl(Turtle)`,`rdf(RDF/XML)`,`rj(RDF/JSON)`,`nq(N-Quads)` and `trig(TriG)`. - -The complete format of an RDF statement: - -```text -rdfStatements = {[format]} -``` - -In the following section, fragments surrounded by `{}` characters are OPTIONAL. - -In the following section, fragments surrounded by `<>` characters are REQUIRED. - -format: nt/ttl/rdf/rj/nq/trig - -When no format is selected: statements = [ttl]statements - -- `nt(n-triples)` - -`nt` uses space to separate the subject, predicate, object of a triple, and a period . to indicate the end of a triple. - -The basic structure is: - -```text -subject predicate object . -``` - -In this format, the subject is in the format of IRIREF or BLANK_NODE_LABEL, the predicate is in the format of IRIREF, and the object is in the format of IRIREF, BLANK_NODE_LABEL, or STRING_LITERAL_QUOTE. - -For example: - -```text - . - "Alice" . -``` - -- `ttl(Turtle)` - -Compared to `nt`, `ttl` uses prefixes to simplify the IRIREF format, and the same predicate under the same subject can be merged without repeating it. "a" can be used to represent ``. - - -For example: - -```text -@prefix : . -@prefix p: . - -:user1 a :User; - p:name ”Alice” . -``` - -- `rdf(RDF/XML)` - -`rdf` describes RDF in XML format, using rdf:RDF as the top-level element, and xmlns to describe prefixes. rdf:Description begins describing a node, rdf:about defines the node to be described, and rdf:resource fills in the property value in the format of IRI. If the property value is a string, the property value can be directly written as the text of the property node. - -The basic structure is: - -```xml - - - - - - object - - -``` - -For example: - -```xml - - - - - - Alice - - -``` - -- `rj(RDF/JSON)` - - -`rj` describes RDF in JSON format. A triple is described as: - - -```text - {"subject":{"predicate":[object]}} -``` - -Note that each root object is a unique primary key and duplicates are not allowed. There will be no duplicate subjects as keys, and there will be no duplicate predicates under a single subject. - -For example: - -```json - { - "http://example.org/entity/user1": { - "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": [ - "http://example.org/entity/User" - ], - "http://example.org/property/name": [ - "Alice" - ] - } -} - -``` - -- `nq(N-Quads)` - -`nq` is based on `nt` but includes a graph label that describes the dataset to which an RDF triple belongs. The graph label can be in the format of IRIREF or BLANK_NODE_LABEL. - -The basic structure is: - -```text -subject predicate object graphLabel. -``` - -For example: - -```text - . - "Alice" . -``` - -- `trig(TriG)` - - -`trig` is an extension of `ttl` that includes a graph label to describe the dataset to which an RDF triple belongs. The triple statements are enclosed in curly braces {}. - -For example: - -```text -@prefix : . -@prefix p: . - - - { - :user1 a :User; - p:name ”Alice” . - - } -``` - -In the contract events: `CreateRDF`, `UpdateRDF`, `RemoveRDF`, and the `rdfOf method`, the `rdfStatements` is used in `ttl` format by default. If other formats listed above are used, a format identifier needs to be added for identification. - -The format identifier starts with `[` and ends with `]` with the format in the middle, i.e., `[format]`. - -For example, the `rdfStatements` in `nt` format should include the prefix `[nt]`. - -```text -[nt]subject predicate object . -``` - - -### Contract Interface - -```solidity -/** - * @title Semantic Soulbound Token - * Note: the ERC-165 identifier for this interface is 0xfbafb698 - */ -interface ISemanticSBT{ - /** - * @dev This emits when minting a Semantic Soulbound Token. - * @param tokenId The identifier for the Semantic Soulbound Token. - * @param rdfStatements The RDF statements for the Semantic Soulbound Token. - */ - event CreateRDF ( - uint256 indexed tokenId, - string rdfStatements - ); - /** - * @dev This emits when updating the RDF data of Semantic Soulbound Token. RDF data is a collection of RDF statements that are used to represent information about resources. - * @param tokenId The identifier for the Semantic Soulbound Token. - * @param rdfStatements The RDF statements for the semantic soulbound token. - */ - event UpdateRDF ( - uint256 indexed tokenId, - string rdfStatements - ); - /** - * @dev This emits when burning or revoking Semantic Soulbound Token. - * @param tokenId The identifier for the Semantic Soulbound Token. - * @param rdfStatements The RDF statements for the Semantic Soulbound Token. - */ - event RemoveRDF ( - uint256 indexed tokenId, - string rdfStatements - ); - /** - * @dev Returns the RDF statements of the Semantic Soulbound Token. - * @param tokenId The identifier for the Semantic Soulbound Token. - * @return rdfStatements The RDF statements for the Semantic Soulbound Token. - */ - function rdfOf(uint256 tokenId) external view returns (string memory rdfStatements); -} -``` - -`ISemanticRDFSchema`, an extension of ERC-721 Metadata, is **OPTIONAL** for this standard, it is used to get the Schema URI for the RDF data. - -```solidity -interface ISemanticRDFSchema{ - /** - * @notice Get the URI of schema for this contract. - * @return The URI of the contract which point to a configuration profile. - */ - function schemaURI() external view returns (string memory); -} -``` - - -### Method Specification - -`rdfOf (uint256 tokenId)`: Query the RDF data for the Semantic Soulbound Token by `tokenId`. The returned RDF data format conforms to the W3C RDF standard. RDF data is a collection of RDF statements that are used to represent information about resources. An RDF statement, also known as a triple, is a unit of information in the RDF data model. It consists of three parts: a subject, a predicate, and an object. - -`schemaURI()`: This **OPTIONAL** method is used to query the URIs of the schema for the RDF data. RDF Schema is an extension of the basic RDF vocabulary and provides a data-modelling vocabulary for RDF data. It is **RECOMMENDED** to store the RDF Schema in decentralized storage such as Arweave or IPFS. The URIs are then stored in the contract and can be queried by this method. - -### Event Specification - -`CreateRDF`: When minting a Semantic Soulbound Token, this event **MUST** be triggered to notify the listener to perform operations with the created RDF data. When calling the event, the input RDF data **MUST** be RDF statements, which are units of information consisting of three parts: a subject, a predicate, and an object. - -`UpdateRDF`: When updating RDF data for a Semantic Soulbound Token, this event **MUST** be triggered to notify the listener to perform update operations accordingly with the updated RDF data. When calling the event, the input RDF data **MUST** be RDF statements, which are units of information consisting of three parts: a subject, a predicate, and an object. - -`RemoveRDF`: When burning or revoking a Semantic Soulbound Token, this event **MUST** be triggered to notify the listener to perform operations with the removed RDF data for the Semantic SBT. When calling the event, the input RDF data **MUST** be RDF statements, which are units of information consisting of three parts: a subject, a predicate, and an object. - -## Rationale - - -RDF is a flexible and extensible data model based on creating subject-predicate-object relationships, often used to model graph data due to its semantic web standards, Linked Data concept, flexibility, and query capabilities. RDF allows graph data to be easily integrated with other data sources on the web, making it possible to create more comprehensive and interoperable models. The advantage of using RDF for semantic description is that it can describe richer information, including terms, categories, properties, and relationships. RDF uses standard formats and languages to describe metadata, making the expression of semantic information more standardized and unified. This helps to establish more accurate and reliable semantic networks, promoting interoperability between different systems. Additionally, RDF supports semantic reasoning, which allows the system to automatically infer additional relationships and connections between nodes in the social graph based on the existing data. - - -There are multiple formats for RDF statements. We list six most widely adopted RDF statement formats in the EIP: `Turtle`, `N-Triples`, `RDF/XML`, `RDF/JSON`,`N-Quads`, and `TriG`. These formats have different advantages and applicability in expressing, storing, and parsing RDF statements. Among these, `Turtle` is a popular format in RDF statements, due to its good human-readability and concision. It is typically used as the default format in this EIP for RDF statements. Using the Turtle format can make RDF statements easier to understand and maintain, while reducing the need for storage, suitable for representing complex RDF graphs. - - -## Backwards Compatibility - -This proposal is fully backward compatible with [ERC-721](./eip-721.md) and [ERC-5192](./eip-5192.md). - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6239.md diff --git a/EIPS/eip-6268.md b/EIPS/eip-6268.md index d2afdf0fcdf984..644814a8a296fe 100644 --- a/EIPS/eip-6268.md +++ b/EIPS/eip-6268.md @@ -1,88 +1 @@ ---- -eip: 6268 -title: Untransferability Indicator for EIP-1155 -description: An extension of EIP-1155 for indicating the transferability of the token. -author: Yuki Aoki (@yuki-js) -discussions-to: https://ethereum-magicians.org/t/sbt-implemented-in-erc1155/12182 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-01-06 -requires: 165, 1155 ---- - -## Abstract - -This EIP standardizes an interface indicating [EIP-1155](./eip-1155.md)-compatible token non-transferability using [EIP-165](./eip-165.md) feature detection. - -## Motivation - -Soulbound Tokens (SBT) are non-transferable tokens. While [EIP-5192](./eip-5192.md) standardizes non-fungible SBTs, a standard for Soulbound semi-fungible or fungible tokens does not yet exist. The introduction of a standard non-transferability indicator that is agnostic to fungibility promotes the usage of Souldbound semi-fungible or fungible tokens. - -## 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. - -Smart contracts implementing this standard MUST comform to the [EIP-1155](./eip-1155.md) specification. - -Smart contracts implementing this standard MUST implement all of the functions in the `IERC6268` interface. - -Smart contracts implementing this standard MUST implement the [EIP-165](./eip-165.md) supportsInterface function and MUST return the constant value true if `0xd87116f3` is passed through the interfaceID argument. - -For the token identifier `_id` that is marked as `locked`, `locked(_id)` MUST return the constant value true and any functions that try transferring the token, including `safeTransferFrom` and `safeBatchTransferFrom` function MUST throw. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC6268 { - /// @notice Either `LockedSingle` or `LockedBatch` MUST emit when the locking status is changed to locked. - /// @dev If a token is minted and the status is locked, this event should be emitted. - /// @param _id The identifier for a token. - event LockedSingle(uint256 _id); - - /// @notice Either `LockedSingle` or `LockedBatch` MUST emit when the locking status is changed to locked. - /// @dev If a token is minted and the status is locked, this event should be emitted. - /// @param _ids The list of identifiers for tokens. - event LockedBatch(uint256[] _ids); - - /// @notice Either `UnlockedSingle` or `UnlockedBatch` MUST emit when the locking status is changed to unlocked. - /// @dev If a token is minted and the status is unlocked, this event should be emitted. - /// @param _id The identifier for a token. - event UnlockedSingle(uint256 _id); - - /// @notice Either `UnlockedSingle` or `UnlockedBatch` MUST emit when the locking status is changed to unlocked. - /// @dev If a token is minted and the status is unlocked, this event should be emitted. - /// @param _ids The list of identifiers for tokens. - event UnlockedBatch(uint256[] _ids); - - - /// @notice Returns the locking status of the token. - /// @dev SBTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param _id The identifier for a token. - function locked(uint256 _id) external view returns (bool); - - /// @notice Returns the locking statuses of the multiple tokens. - /// @dev SBTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param _ids The list of identifiers for tokens - function lockedBatch(uint256[] _ids) external view returns (bool); -} -``` - -## Rationale - -Needs discussion. - -## Backwards Compatibility - -This proposal is fully backward compatible with [EIP-1155](./eip-1155.md). - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6268.md diff --git a/EIPS/eip-6315.md b/EIPS/eip-6315.md index 66faa61f06bc05..f5335a8b61988b 100644 --- a/EIPS/eip-6315.md +++ b/EIPS/eip-6315.md @@ -1,72 +1 @@ ---- -eip: 6315 -title: ERC-2771 Namespaced Account Abstraction -description: Introducing per-forwarder namespaced addresses to facilitate meta-transactions under a namespacing framework -author: Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/trustless-eip-2771/12497 -status: Review -type: Standards Track -category: ERC -created: 2023-01-11 -requires: 165, 2771 ---- - -## Abstract - -[ERC-2771](./eip-2771.md) is a prevalent standard for handling meta-transactions via trusted forwarders. This EIP proposes an extension to [ERC-2771](./eip-2771.md) to introduce a namespacing mechanism, facilitating trustless account abstraction through per-forwarder namespaced addresses. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -The key words "Forwarder" and "Recipient" in this document are to be interpreted as described in [ERC-2771](./eip-2771.md). - -### Namespaced Forwarder Interface - -```solidity -pragma solidity ^0.8.0; - -interface INamespacedForwarder { - function isNamespacedTransaction() external view returns (bool); -} -``` - -### Determining the Sender and Forwarder - -Upon function invocation on a Recipient, the Recipient MUST execute a `STATICCALL` to the `isNamespacedTransaction()` method of the caller. If this operation reverts or returns the boolean value `false`, the transaction MUST proceed normally, identifying the caller as the sender, and the Forwarder as the zero address. However, if the boolean value `true` is returned, the transaction is acknowledged as a namespaced transaction, with the sender identified using the procedure outlined in [ERC-2771](./eip-2771.md#extracting-the-transaction-signer-address), and the Forwarder identified as the caller. - -### Recipient Extensions - -Whenever a Recipient contract has a function with one or more function parameters of type address, it MUST also provide a new function, mirroring the name of the original function but appending `Namespaced` at the end, which accepts two addresses instead. The initial address denotes the Forwarder, while the latter represents the address managed by that Forwarder. If a function accepts multiple address parameters (e.g., [ERC-20](./eip-20.md)'s `transferFrom`), a version of the function accepting two addresses per original address parameter MUST be provided. The original function MUST exhibit identical behavior to the new function when Forwarder addresses are the zero address. - -For instance, [ERC-20](./eip-20.md) would be extended with these functions: - -```solidity -function transferNamespaced(address toForwarder, address toAddress, uint256 amount); -function approveNamespaced(address spenderForwarder, address spenderAddress, uint256 amount); -function transferFromNamespaced(address fromForwarder, address fromAddress, address toForwarder, address toAddress, uint256 amount); -``` - -#### [ERC-165](./eip-165.md) - -Recipient contracts MUST implement ERC-165. When an ERC-165 interface ID is registered, a second interface ID corresponding to the XOR of the Namespaced function selectors of the original interface must also be registered. - -## Rationale - -The approach of simply augmenting existing EIP functions with new `address` parameters, rather than crafting new interfaces for the most commonly used EIPs, is employed to ensure broader applicability of this namespacing proposal. - -## Backwards Compatibility - -Contracts already deployed cannot not benefit from this namespacing proposal. This limitation also extends to ERC-2771. - -### Using this EIP in standards - -When using this EIP in another standard, both the original and the Namespaced interface IDs SHOULD be provided. Interfaces MUST NOT include namespaced versions of functions in their interfaces. - -## Security Considerations - -This proposal alters trust dynamics: Forwarders no longer require Recipient trust, but instead require the trust of their users. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6315.md diff --git a/EIPS/eip-6327.md b/EIPS/eip-6327.md index d7e8703f605ae7..eb74c6a98cd336 100644 --- a/EIPS/eip-6327.md +++ b/EIPS/eip-6327.md @@ -1,316 +1 @@ ---- -eip: 6327 -title: Elastic Signature -description: Use password to sign data as private key -author: George (@JXRow) -discussions-to: https://ethereum-magicians.org/t/eip-6327-elastic-signature-es/12554 -status: Draft -type: Standards Track -category: ERC -created: 2023-01-13 ---- - - -## Abstract - -Elastic signature (ES) aims to sign data with a human friendly secret. The secret will be verified fully on-chain and is not stored anywhere. A user can change the secret as often as they need to. The secret does not have a fixed length. The secret will be like a password, which is a better understood concept than private key. This is specifically true for non-technical users. This EIP defines a smart contract interface to verify and authorize operations with ES. - - -## Motivation - -What would a changeable "private key" enable us? For years, we have been looking for ways to lower on-boarding barrier for users, especially those with less technical experiences. Private key custody solutions seem to provide an user friendly on-boarding experience, but it is vendor dependent and is not decentralized. ES makes a breakthrough with Zero-knowledge technology. Users generate proof of knowing the secret and a smart contract will verify the proof. - -### Use case - -ES is an alternative signing algorithm. It is not an either-or solution to the private key. It is designed to serve as an additional signing mechanism on top of the private key signature. - -- A DeFi app can utilize ES into their transfer fund process. Users will be required to provide their passwords to complete the transaction. This gives an extra protection even if the private key is compromised. -- ES can also be used as a plugin to a smart contract wallet, like Account Abstraction [ERC-4337](./eip-4337.md). A decentralized password is picked instead of the private key. This could lead to a smooth onboarding experiences for new Ethereum Dapp users. - - -## Specification - -Let: - -- `pwdhash` represents the hash of the private secret (password). -- `datahash` represents the hash of an intended transaction data. -- `fullhash` represents the hash of `datahash` and all the well-known variables. -- `expiration` is the timestamp after which the intended transaction expires. -- `allhash` represents the hash of `fullhash` and `pwdhash`. - - -There are three parties involved, Verifier, Requester and Prover. - -- A verifier, - - SHOULD compute `fullhash` from a `datahash`, which is provided by the requester. - - SHOULD derive `pwdhash` for a given address. The address can be an EOA or a smart contract wallet. - - SHOULD verify the proof with the derived `pwdhash`, the computed `fullhash` and a `allhash`, which is submitted by the requester. -- A requester - - SHOULD generate `datahash` and decide an `expiration`. - - SHALL request a verification from the verifier with, - - `proof` and `allhash` which are provided by the prover; - - `datahash`; - - `expiration`. -- A prover - - SHOULD generate the `proof` and `allhash` from, - - `datahash` and `expiration` which are agreed with the requester; - - `nonce` and other well-known variables. - -There are also some requirements. - -- well-known variable SHOULD be available to all parties. - - SHOULD include a `nonce`. - - SHOULD include a `chainid`. - - MAY include any variable that is specific to the verifier. -- public statements SHOULD include, - - one reflecting the `pwdhash`; - - one reflecting the `fullhash`; - - one reflecting the `allhash`. -- The computation of `fullhash` SHOULD be agreed by both the verifier and the prover. -- The computation of `datahash` - -### `IElasticSignature` Interface - -This is the verifier interface. - -```solidity -pragma solidity ^0.8.0; - -interface IElasticSignature { - /** - * Event emitted after user set/reset their password - * @param user - an user's address, for whom the password hash is set. It could be a smart contract wallet address - * or an EOA wallet address. - * @param pwdhash - a password hash - */ - event SetPassword(address indexed user, uint indexed pwdhash); - - /** - * Event emitted after a successful verification performed for an user - * @param user - an user's address, for whom the submitted `proof` is verified. It could be a smart contract wallet - * address or an EOA wallet address. - * @param nonce - a new nonce, which is newly generated to replace the last used nonce. - */ - event Verified(address indexed user, uint indexed nonce); - - /** - * Get `pwdhash` for a user - * @param user - a user's address - * @return - the `pwdhash` for the given address - */ - function pwdhashOf(address user) external view returns (uint); - - /** - * Update an user's `pwdhash` - * @param proof1 - proof generated by the old password - * @param expiration1 - old password signing expiry seconds - * @param allhash1 - allhash generated with the old password - * @param proof2 - proof generated by the new password - * @param pwdhash2 - hash of the new password - * @param expiration2 - new password signing expiry seconds - * @param allhash2 - allhash generated with the new password - */ - function resetPassword( - uint[8] memory proof1, - uint expiration1, - uint allhash1, - uint[8] memory proof2, - uint pwdhash2, - uint expiration2, - uint allhash2 - ) external; - - /** - * Verify a proof for a given user - * It should be invoked by other contracts. The other contracts provide the `datahash`. The `proof` is generated by - * the user. - * @param user - a user's address, for whom the verification will be carried out. - * @param proof - a proof generated by the password - * @param datahash - the data what user signing, this is the hash of the data - * @param expiration - number of seconds from now, after which the proof is expired - * @param allhash - public statement, generated along with the `proof` - */ - function verify( - address user, - uint[8] memory proof, - uint datahash, - uint expiration, - uint allhash - ) external; -} -``` - -`verify` function SHOULD be called by another contract. The other contract SHOULD generate the `datahash` to call this. The function SHOULD verify if the `allhash` is computed correctly and honestly with the password. - - -## Rationale - -The contract will store everyone's `pwdhash`. - -![verifier-contract](../assets/eip-6327/zkpass-1.png) - -The chart below shows ZK circuit logic. - -![circuit-logic](../assets/eip-6327/zkpass-2.png) - -To verify the signature, it needs `proof`, `allhash`, `pwdhash` and `fullhash`. - -![workflow](../assets/eip-6327/zkpass-3.png) - -The prover generates `proof` along with the public outputs. They will send all of them to a third-party requester contract. The requester will generate the `datahash`. It sends `datahash`, `proof`, `allhash`, `expiration` and prover's address to the verifier contract. The contract verifies that the `datahash` is from the prover, which means the withdrawal operation is signed by the prover's password. - - -## Backwards Compatibility - -This EIP is backward compatible with previous work on signature validation since this method is specific to password based signatures and not EOA signatures. - - -## Reference Implementation - -Example implementation of a signing contract: - -```solidity -pragma solidity ^0.8.0; - -import "../interfaces/IElasticSignature.sol"; -import "./verifier.sol"; - -contract ZKPass is IElasticSignature { - Verifier verifier = new Verifier(); - - mapping(address => uint) public pwdhashOf; - - mapping(address => uint) public nonceOf; - - constructor() { - } - - function resetPassword( - uint[8] memory proof1, - uint expiration1, - uint allhash1, - uint[8] memory proof2, - uint pwdhash2, - uint expiration2, - uint allhash2 - ) public override { - uint nonce = nonceOf[msg.sender]; - - if (nonce == 0) { - //init password - - pwdhashOf[msg.sender] = pwdhash2; - nonceOf[msg.sender] = 1; - verify(msg.sender, proof2, 0, expiration2, allhash2); - } else { - //reset password - - // check old pwdhash - verify(msg.sender, proof1, 0, expiration1, allhash1); - - // check new pwdhash - pwdhashOf[msg.sender] = pwdhash2; - verify(msg.sender, proof2, 0, expiration2, allhash2); - } - - emit SetPassword(msg.sender, pwdhash2); - } - - function verify( - address user, - uint[8] memory proof, - uint datahash, - uint expiration, - uint allhash - ) public override { - require( - block.timestamp < expiration, - "ZKPass::verify: expired" - ); - - uint pwdhash = pwdhashOf[user]; - require( - pwdhash != 0, - "ZKPass::verify: user not exist" - ); - - uint nonce = nonceOf[user]; - uint fullhash = uint(keccak256(abi.encodePacked(expiration, block.chainid, nonce, datahash))) / 8; // 256b->254b - require( - verifyProof(proof, pwdhash, fullhash, allhash), - "ZKPass::verify: verify proof fail" - ); - - nonceOf[user] = nonce + 1; - - emit Verified(user, nonce); - } - - /////////// util //////////// - - function verifyProof( - uint[8] memory proof, - uint pwdhash, - uint fullhash, //254b - uint allhash - ) internal view returns (bool) { - return - verifier.verifyProof( - [proof[0], proof[1]], - [[proof[2], proof[3]], [proof[4], proof[5]]], - [proof[6], proof[7]], - [pwdhash, fullhash, allhash] - ); - } -} -``` - -verifier.sol is auto generated by snarkjs, the source code circuit.circom is below - -```javascript -pragma circom 2.0.0; - -include "../../node_modules/circomlib/circuits/poseidon.circom"; - -template Main() { - signal input in[3]; - signal output out[3]; - - component poseidon1 = Poseidon(2); - component poseidon2 = Poseidon(2); - - poseidon1.inputs[0] <== in[0]; //pwd - poseidon1.inputs[1] <== in[1]; //address - out[0] <== poseidon1.out; //pwdhash - - poseidon2.inputs[0] <== poseidon1.out; - poseidon2.inputs[1] <== in[2]; //fullhash - out[1] <== in[2]; //fullhash - out[2] <== poseidon2.out; //allhash -} - -component main = Main(); -``` - - -## Security Considerations - -Since the pwdhash is public, it is possible to be crack the password. We estimate the Poseidon hash rate of RTX3090 would be 100Mhash/s, this is the estimate of crack time: - -8 chars (number) : 1 secs - -8 chars (number + english) : 25 days - -8 chars (number + english + symbol) : 594 days - -12 chars (number) : 10000 secs - -12 chars (number + english) : 1023042 years - -12 chars (number + english + symbol) : 116586246 years - -The crack difficulty of private key is 2^256, the crack difficulty of 40 chars (number + english + symbol) is 92^40, 92^40 > 2^256, so when password is 40 chars , it is more difficult to be crack than private key. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6327.md diff --git a/EIPS/eip-634.md b/EIPS/eip-634.md index f53462f899c9d0..f2e7f9cb297d38 100644 --- a/EIPS/eip-634.md +++ b/EIPS/eip-634.md @@ -1,129 +1 @@ ---- -eip: 634 -title: Storage of text records in ENS -description: Profiles for ENS resolvers to store arbitrary text key/value pairs. -author: Richard Moore (@ricmoo) -type: Standards Track -discussions-to: https://github.com/ethereum/EIPs/issues/2439 -category: ERC -status: Stagnant -created: 2017-05-17 -requires: 137, 165 ---- - -## Abstract -This EIP defines a resolver profile for ENS that permits the lookup of arbitrary key-value -text data. This allows ENS name holders to associate e-mail addresses, URLs and other -informational data with a ENS name. - - -## Motivation -There is often a desire for human-readable metadata to be associated with otherwise -machine-driven data; used for debugging, maintenance, reporting and general information. - -In this EIP we define a simple resolver profile for ENS that permits ENS names to -associate arbitrary key-value text. - - -## Specification - -### Resolver Profile - -A new resolver interface is defined, consisting of the following method: - -```solidity -interface IERC634 { - /// @notice Returns the text data associated with a key for an ENS name - /// @param node A nodehash for an ENS name - /// @param key A key to lookup text data for - /// @return The text data - function text(bytes32 node, string key) view returns (string text); -} -``` - -The [EIP-165](./eip-165.md) interface ID of this interface is `0x59d1d43c`. - -The `text` data may be any arbitrary UTF-8 string. If the key is not present, the empty string -must be returned. - - -### Global Keys - -Global Keys must be made up of lowercase letters, numbers and -the hyphen (-). - -- **avatar** - a URL to an image used as an avatar or logo -- **description** - A description of the name -- **display** - a canonical display name for the ENS name; this MUST match the ENS name when its case is folded, and clients should ignore this value if it does not (e.g. `"ricmoo.eth"` could set this to `"RicMoo.eth"`) -- **email** - an e-mail address -- **keywords** - A list of comma-separated keywords, ordered by most significant first; clients that interpresent this field may choose a threshold beyond which to ignore -- **mail** - A physical mailing address -- **notice** - A notice regarding this name -- **location** - A generic location (e.g. `"Toronto, Canada"`) -- **phone** - A phone number as an E.164 string -- **url** - a website URL - -### Service Keys - -Service Keys must be made up of a *reverse dot notation* for -a namespace which the service owns, for example, DNS names -(e.g. `.com`, `.io`, etc) or ENS name (i.e. `.eth`). Service -Keys must contain at least one dot. - -This allows new services to start using their own keys without -worrying about colliding with existing services and also means -new services do not need to update this document. - -The following services are common, which is why recommendations are -provided here, but ideally a service would declare its own key. - -- **com.github** - a GitHub username -- **com.peepeth** - a Peepeth username -- **com.linkedin** - a LinkedIn username -- **com.twitter** - a Twitter username -- **io.keybase** - a Keybase username -- **org.telegram** - a Telegram username - -This technique also allows for a service owner to specify a hierarchy -for their keys, such as: - -- **com.example.users** -- **com.example.groups** -- **com.example.groups.public** -- **com.example.groups.private** - - -### Legacy Keys - -The following keys were specified in earlier versions of this EIP, -which is still in draft. - -Their use is not likely very wide, but applications attempting -maximal compatibility may wish to query these keys as a fallback -if the above replacement keys fail. - -- **vnd.github** - a GitHub username (renamed to `com.github`) -- **vnd.peepeth** - a peepeth username (renamced to `com.peepeth`) -- **vnd.twitter** - a twitter username (renamed to `com.twitter`) - - -## Rationale - -### Application-specific vs general-purpose record types - -Rather than define a large number of specific record types (each for generally human-readable -data) such as `url` and `email`, we follow an adapted model of DNS's `TXT` records, which allow -for a general keys and values, allowing future extension without adjusting the resolver, while -allowing applications to use custom keys for their own purposes. - - -## Backwards Compatibility -Not applicable. - - -## Security Considerations -None. - - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-634.md diff --git a/EIPS/eip-6353.md b/EIPS/eip-6353.md index b678c56917990a..4ba7033a33f171 100644 --- a/EIPS/eip-6353.md +++ b/EIPS/eip-6353.md @@ -1,265 +1 @@ ---- -eip: 6353 -title: Charity token -description: Extension of EIP-20 token that can be partially donated to a charity project -author: Aubay , BOCA Jeabby (@bjeabby1507), EL MERSHATI Laith (@lth-elm), KEMP Elia (@eliakemp) -discussions-to: https://ethereum-magicians.org/t/erc20-charity-token/12617 -status: Stagnant -type: Standards Track -category: ERC -created: 2022-05-13 -requires: 20 ---- - -## Abstract - -An extension to [EIP-20](./eip-20.md) that can automatically send an additional percentage of each transfer to a third party, and that provides an interface for retrieving this information. This can allow token owners to make donations to a charity with every transfer. This can also be used to allow automated savings programs. - -## Motivation - -There are charity organizations with addresses on-chain, and there are token holders who want to make automated donations. Having a standardized way of collecting and managing these donations helps users and user interface developers. Users can make an impact with their token and can contribute to achieving sustainable blockchain development. Projects can easily retrieve charity donations addresses and rate for a given [EIP-20](./eip-20.md) token, token holders can compare minimum rate donation offers allowed by token contract owners. This standard provides functionality that allows token holders to donate easily. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Owner of the contract **MAY**, after review, register charity address in `whitelistedRate` and set globally a default rate of donation. To register the address, the rate **MUST** not be null. - -Token holders **MAY** choose and specify a default charity address from `_defaultAddress`, this address **SHOULD** be different from the null address for the donation to be activated. - -The donation is a percentage-based rate model, but the calculation can be done differently. Applications and individuals can implement this standard by retrieving information with `charityInfo()` , which specifies an assigned rate for a given address. - -This standard provides functionality that allows token holders to donate easily. The donation when activated is done directly in the overridden `transfer`, `transferFrom`, and `approve` functions. - -When `transfer`, `transferFrom` are called the sender's balance is reduced by the initial amount and a donation amount is deduced. The initial transfered amount is transferred to the recipient's balance and an additional donation amount is transfered to a third party (charity). The two transfer are done at the same time and emit two `Transfer` events. -Also, if the account has an insufficient balance to cover the transfer and the donation the whole transfer would revert. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.4; - -/// -/// @dev Required interface of an ERC20 Charity compliant contract. -/// -interface IERC20charity is IERC165 { - /// The EIP-165 identifier for this interface is 0x557512b6 - - - /** - * @dev Emitted when `toAdd` charity address is added to `whitelistedRate`. - */ - event AddedToWhitelist (address toAdd); - - /** - * @dev Emitted when `toRemove` charity address is deleted from `whitelistedRate`. - */ - event RemovedFromWhitelist (address toRemove); - - /** - * @dev Emitted when `_defaultAddress` charity address is modified and set to `whitelistedAddr`. - */ - event DonnationAddressChanged (address whitelistedAddr); - - /** - * @dev Emitted when `_defaultAddress` charity address is modified and set to `whitelistedAddr` - * and _donation is set to `rate`. - */ - event DonnationAddressAndRateChanged (address whitelistedAddr,uint256 rate); - - /** - * @dev Emitted when `whitelistedRate` for `whitelistedAddr` is modified and set to `rate`. - */ - event ModifiedCharityRate(address whitelistedAddr,uint256 rate); - - /** - *@notice Called with the charity address to determine if the contract whitelisted the address - *and if it is the rate assigned. - *@param addr - the Charity address queried for donnation information. - *@return whitelisted - true if the contract whitelisted the address to receive donnation - *@return defaultRate - the rate defined by the contract owner by default , the minimum rate allowed different from 0 - */ - function charityInfo( - address addr - ) external view returns ( - bool whitelisted, - uint256 defaultRate - ); - - /** - *@notice Add address to whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toAdd` cannot be the zero address. - * - * @param toAdd The address to whitelist. - */ - function addToWhitelist(address toAdd) external; - - /** - *@notice Remove the address from the whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toRemove` cannot be the zero address. - * - * @param toRemove The address to remove from whitelist. - */ - function deleteFromWhitelist(address toRemove) external; - - /** - *@notice Get all registered charity addresses. - */ - function getAllWhitelistedAddresses() external ; - - /** - *@notice Display for a user the rate of the default charity address that will receive donation. - */ - function getRate() external view returns (uint256); - - /** - *@notice Set personlised rate for charity address in {whitelistedRate}. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificRate(address whitelistedAddr , uint256 rate) external; - - /** - *@notice Set for a user a default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - * @param whitelistedAddr The address to set as default. - */ - function setSpecificDefaultAddress(address whitelistedAddr) external; - - /** - *@notice Set for a user a default charity address that will receive donation. - * The rate is specified by the user. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be less than to the default rate - * or to the rate specified by the owner of this contract in {whitelistedRate}. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificDefaultAddressAndRate(address whitelistedAddr , uint256 rate) external; - - /** - *@notice Display for a user the default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - */ - function specificDefaultAddress() external view returns ( - address defaultAddress - ); - - /** - *@notice Delete The Default Address and so deactivate donnations . - */ - function deleteDefaultAddress() external; -} - -``` - -### Functions - -#### **addToWhitelist** - -Add address to whitelist and set the rate to the default rate. - -| Parameter | Description | -| ---------|-------------| -| toAdd | The address to the whitelist. - -#### **deleteFromWhitelist** - -Remove the address from the whitelist and set rate to the default rate. - -| Parameter | Description | -| ---------|-------------| -| toRemove | The address to remove from whitelist. - -#### **getAllWhitelistedAddresses** - -Get all registered charity addresses. - -#### **getRate** - -Display for a user the rate of the default charity address that will receive donation. - -#### **setSpecificRate** - -Set personalized rate for charity address in {whitelistedRate}. - -| Parameter | Description | -| ---------|-------------| -| whitelistedAddr | The address to set as default. | -| rate | The personalised rate for donation. | - -#### **setSpecificDefaultAddress** - -Set for a user a default charity address that will receive donations. The default rate specified in {whitelistedRate} will be applied. - -| Parameter | Description | -| ---------|-------------| -| whitelistedAddr | The address to set as default. - -#### **setSpecificDefaultAddressAndRate** - -Set for a user a default charity address that will receive donations. The rate is specified by the user. - -| Parameter | Description | -| ---------|-------------| -| whitelistedAddr | The address to set as default. | -| rate | The personalized rate for donation. - -#### **specificDefaultAddress** - -Display for a user the default charity address that will receive donations. The default rate specified in {whitelistedRate} will be applied. - -#### **deleteDefaultAddress** - -Delete The Default Address and so deactivate donations. - -#### **charityInfo** - -Called with the charity address to determine if the contract whitelisted the address and if it is, the rate assigned. - -| Parameter | Description | -| ---------|-------------| -| addr | The Charity address queried for donnation information. - -## Rationale - - This EIP chooses to whitelist charity addresses by using an array and keeping track of the "active" status with a mapping `whitelistedRate` to allow multiple choice of recipient and for transparence. The donation address can also be a single address chosen by the owner of the contract and modified by period. - - If the sender balance is insuficent i.e total amount of token (initial transfer + donation) is insuficent the transfer would revert. Donation are done in the `transfer` function to simplify the usage and to not add an additional function, but the implementation could be donne differently, and for exemple allow a transfer to go through without the donation amount when donation is activated. The token implementer can also choose to store the donation in the contract or in another one and add a withdrawal or claimable function, so the charity can claim the allocated amount of token themselves, the additional transfer will be triggered by the charity and not the token holder. - - Also, donations amount are calculated here as a percentage of the amount of token transfered to allow different case scenario, but the token implementer can decide to opt for another approach instead like rounding up the transaction value. - -## Backwards Compatibility - -This implementation is an extension of the functionality of [EIP-20](./eip-20.md), it introduces new functionality retaining the core interfaces and functionality of the [EIP-20](./eip-20.md) standard. There is a small backwards compatibility issue, indeed if an account has insufficient balance, it's possible for the transfer to fail. - -## Test Cases - -Tests can be found in [`charity.js`](../assets/eip-6353/test/charity.js). - -## Reference Implementation - -The reference implementation of the standard can be found under [`contracts/`](../assets/eip-6353/contracts/ERC20Charity.sol) folder. - -## Security Considerations - -There are no additional security considerations compared to EIP-20. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6353.md diff --git a/EIPS/eip-6357.md b/EIPS/eip-6357.md index da8d7614aef361..6cc0c65261aefa 100644 --- a/EIPS/eip-6357.md +++ b/EIPS/eip-6357.md @@ -1,90 +1 @@ ---- -eip: 6357 -title: Single-contract Multi-delegatecall -description: Allows an EOA to call multiple functions of a smart contract in a single transaction -author: Gavin John (@Pandapip1) -discussions-to: https://ethereum-magicians.org/t/eip-6357-single-contract-multicall/12621 -status: Review -type: Standards Track -category: ERC -created: 2023-01-18 ---- - -## Abstract - -This EIP standardizes an interface containing a single function, `multicall`, allowing EOAs to call multiple functions of a smart contract in a single transaction, and revert all calls if any call fails. - -## Motivation - -Currently, in order to transfer several [ERC-721](./eip-721.md) NFTs, one needs to submit a number of transactions equal to the number of NFTs being tranferred. This wastes users' funds by requiring them to pay 21000 gas fee for every NFT they transfer. - -## 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. - -Contracts implementing this EIP must implement the following interface: - -```solidity -pragma solidity ^0.8.0; - -interface IMulticall { - /// @notice Takes an array of abi-encoded call data, delegatecalls itself with each calldata, and returns the abi-encoded result - /// @dev Reverts if any delegatecall reverts - /// @param data The abi-encoded data - /// @returns results The abi-encoded return values - function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results); - - /// @notice OPTIONAL. Takes an array of abi-encoded call data, delegatecalls itself with each calldata, and returns the abi-encoded result - /// @dev Reverts if any delegatecall reverts - /// @param data The abi-encoded data - /// @param values The effective msg.values. These must add up to at most msg.value - /// @returns results The abi-encoded return values - function multicallPayable(bytes[] calldata data, uint256[] values) external payable virtual returns (bytes[] memory results); -} -``` - -## Rationale - -`multicallPayable` is optional because it isn't always feasible to implement, due to the `msg.value` splitting. - -## Backwards Compatibility - -This is compatible with most existing multicall functions. - -## Test Cases - -The following JavaScript code, using the Ethers library, should atomically transfer `amt` units of an [ERC-20](./eip-20.md) token to both `addressA` and `addressB`. - -```js -await token.multicall(await Promise.all([ - token.interface.encodeFunctionData('transfer', [ addressA, amt ]), - token.interface.encodeFunctionData('transfer', [ addressB, amt ]), -])); -``` - -## Reference Implementation - -```solidity -pragma solidity ^0.8.0; - -/// Derived from OpenZeppelin's implementation -abstract contract Multicall is IMulticall { - function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) { - results = new bytes[](data.length); - for (uint256 i = 0; i < data.length; i++) { - (bool success, bytes memory returndata) = address(this).delegatecall(data); - require(success); - results[i] = returndata; - } - return results; - } -} -``` - -## Security Considerations - -`multicallPayable` should only be used if the contract is able to support it. A naive attempt at implementing it could allow an attacker to call a payable function multiple times with the same ether. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6357.md diff --git a/EIPS/eip-6358.md b/EIPS/eip-6358.md index ba335062c8b93c..c565d25f51fb0f 100644 --- a/EIPS/eip-6358.md +++ b/EIPS/eip-6358.md @@ -1,366 +1 @@ ---- -eip: 6358 -title: Cross-Chain Token States Synchronization -description: A paradigm to synchronize token states over multiple existing public chains -author: Shawn Zheng (@xiyu1984), Jason Cheng , George Huang (@virgil2019), Kay Lin (@kay404) -discussions-to: https://ethereum-magicians.org/t/add-eip-6358-omniverse-distributed-ledger-technology/12625 -status: Review -type: Standards Track -category: ERC -created: 2023-01-17 ---- - -## Abstract - -This ERC standardizes an interface for contract-layer consensus-agnostic verifiable cross-chain bridging, through which we can define a new global token inherited from [ERC-20](./eip-20.md)/[ERC-721](./eip-721.md) over multi-chains. - -### Figure.1 Architecture - -![img](../assets/eip-6358/img/o-dlt.png) - -With this ERC, we can create a global token protocol, that leverages smart contracts or similar mechanisms on existing blockchains to record the token states synchronously. The synchronization could be made by trustless off-chain synchronizers. - -## Motivation - -- The current paradigm of token bridges makes assets fragment. -- If ETH was transferred to another chain through the current token bridge, if the chain broke down, ETH will be lost for users. - -The core of this ERC is synchronization instead of transferring, even if all the other chains break down, as long as Ethereum is still running, user’s assets will not be lost. - -- The fragment problem will be solved. -- The security of users' multi-chain assets can be greatly enhanced. - -## 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. - -### Omniverse Account - -There SHOULD be a global user identifier of this ERC, which is RECOMMENDED to be referred to as Omniverse Account (`o-account` for short) in this article. -The `o-account` is RECOMMENDED to be expressed as a public key created by the elliptic curve `secp256k1`. A [mapping mechanism](#mapping-mechanism-for-different-environments) is RECOMMENDED for different environments. - -### Data Structure - -An Omniverse Transaction (`o-transaction` for short) MUST be described with the following data structure: - -```solidity -/** - * @notice Omniverse transaction data structure - * @member nonce: The number of the o-transactions. If the current nonce of an omniverse account is `k`, the valid nonce of this o-account in the next o-transaction is `k+1`. - * @member chainId: The chain where the o-transaction is initiated - * @member initiateSC: The contract address from which the o-transaction is first initiated - * @member from: The Omniverse account which signs the o-transaction - * @member payload: The encoded bussiness logic data, which is maintained by the developer - * @member signature: The signature of the above informations. - */ -struct ERC6358TransactionData { - uint128 nonce; - uint32 chainId; - bytes initiateSC; - bytes from; - bytes payload; - bytes signature; -} -``` - -- The data structure `ERC6358TransactionData` MUST be defined as above. -- The member `nonce` MUST be defined as `uint128` due to better compatibility for more tech stacks of blockchains. -- The member `chainId` MUST be defined as `uint32`. -- The member `initiateSC` MUST be defined as `bytes`. -- The member `from` MUST be defined as `bytes`. -- The member `payload` MUST be defined as `bytes`. It is encoded from a user-defined data related to the o-transaction. For example: - - For fungible tokens it is RECOMMENDED as follows: - - ```solidity - /** - * @notice Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded - * - * @member op: The operation type - * NOTE op: 0-31 are reserved values, 32-255 are custom values - * op: 0 - omniverse account `from` transfers `amount` tokens to omniverse account `exData`, `from` have at least `amount` tokens - * op: 1 - omniverse account `from` mints `amount` tokens to omniverse account `exData` - * op: 2 - omniverse account `from` burns `amount` tokens from his own, `from` have at least `amount` tokens - * @member exData: The operation data. This sector could be empty and is determined by `op`. For example: - when `op` is 0 and 1, `exData` stores the omniverse account that receives. - when `op` is 2, `exData` is empty. - * @member amount: The amount of tokens being operated - */ - struct Fungible { - uint8 op; - bytes exData; - uint256 amount; - } - ``` - - - The related raw data for `signature` in `o-transaction` is RECOMMENDED to be the concatenation of the raw bytes of `op`, `exData`, and `amount`. - - - For non-fungible tokens it is RECOMMENDED as follows: - - ```solidity - /** - * @notice Non-Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded - * - * @member op: The operation type - * NOTE op: 0-31 are reserved values, 32-255 are custom values - * op: 0 omniverse account `from` transfers token `tokenId` to omniverse account `exData`, `from` have the token with `tokenId` - * op: 1 omniverse account `from` mints token `tokenId` to omniverse account `exData` - * op: 2 omniverse account `from` burns token `tokenId`, `from` have the token with `tokenId` - * @member exData: The operation data. This sector could be empty and is determined by `op` - * when `op` is 0 and 1, `exData` stores the omniverse account that receives. - when `op` is 2, `exData` is empty. - * @member tokenId: The tokenId of the non-fungible token being operated - */ - struct NonFungible { - uint8 op; - bytes exData; - uint256 tokenId; - } - ``` - - - The related raw data for `signature` in `o-transaction` is RECOMMENDED to be the concatenation of the raw bytes of `op`, `exData`, and `tokenId`. - -- The member `signature` MUST be defined as `bytes`. It is RECOMMENDED to be created as follows. - - Concat the sectors in `ERC6358TransactionData` as below (take Fungible token for example) and calculate the hash with `keccak256`: - - ```solidity - /** - * @notice Decode `_data` from bytes to Fungible - * @return A `Fungible` instance - */ - function decodeData(bytes memory _data) internal pure returns (Fungible memory) { - (uint8 op, bytes memory exData, uint256 amount) = abi.decode(_data, (uint8, bytes, uint256)); - return Fungible(op, exData, amount); - } - - /** - * @notice Get the hash of a transaction - * @return Hash value of the raw data of an `ERC6358TransactionData` instance - */ - function getTransactionHash(ERC6358TransactionData memory _data) public pure returns (bytes32) { - Fungible memory fungible = decodeData(_data.payload); - bytes memory payload = abi.encodePacked(fungible.op, fungible.exData, fungible.amount); - bytes memory rawData = abi.encodePacked(_data.nonce, _data.chainId, _data.initiateSC, _data.from, payload); - return keccak256(rawData); - } - ``` - - - Sign the hash value. - -### Smart Contract Interface - -- Every [ERC-6358](./eip-6358.md) compliant contract MUST implement the `IERC6358` - - ```solidity - /** - * @notice Interface of the ERC-6358 - */ - interface IERC6358 { - /** - * @notice Emitted when a o-transaction which has nonce `nonce` and was signed by user `pk` is sent by calling {sendOmniverseTransaction} - */ - event TransactionSent(bytes pk, uint256 nonce); - - /** - * @notice Sends an `o-transaction` - * @dev - * Note: MUST implement the validation of the `_data.signature` - * Note: A map maintaining the `o-account` and the related transaction nonce is RECOMMENDED - * Note: MUST implement the validation of the `_data.nonce` according to the current account nonce - * Note: MUST implement the validation of the `_data. payload` - * Note: This interface is just for sending an `o-transaction`, and the execution MUST NOT be within this interface - * Note: The actual execution of an `o-transaction` is RECOMMENDED to be in another function and MAY be delayed for a time - * @param _data: the `o-transaction` data with type {ERC6358TransactionData} - * See more information in the defination of {ERC6358TransactionData} - * - * Emit a {TransactionSent} event - */ - function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external; - - /** - * @notice Get the number of omniverse transactions sent by user `_pk`, - * which is also the valid `nonce` of a new omniverse transactions of user `_pk` - * @param _pk: Omniverse account to be queried - * @return The number of omniverse transactions sent by user `_pk` - */ - function getTransactionCount(bytes memory _pk) external view returns (uint256); - - /** - * @notice Get the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce` - * @param _user Omniverse account to be queried - * @param _nonce The nonce to be queried - * @return Returns the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce` - */ - function getTransactionData(bytes calldata _user, uint256 _nonce) external view returns (ERC6358TransactionData memory, uint256); - - /** - * @notice Get the chain ID - * @return Returns the chain ID - */ - function getChainId() external view returns (uint32); - } - ``` - - - The `sendOmniverseTransaction` function MAY be implemented as `public` or `external` - - The `getTransactionCount` function MAY be implemented as `public` or `external` - - The `getTransactionData` function MAY be implemented as `public` or `external` - - The `getChainId` function MAY be implemented as `pure` or `view` - - The `TransactionSent` event MUST be emitted when `sendOmniverseTransaction` function is called -- Optional Extension: Fungible Token - - ```solidity - // import "{IERC6358.sol}"; - - /** - * @notice Interface of the ERC-6358 fungible token, which inherits {IERC6358} - */ - interface IERC6358Fungible is IERC6358 { - /** - * @notice Get the omniverse balance of a user `_pk` - * @param _pk `o-account` to be queried - * @return Returns the omniverse balance of a user `_pk` - */ - function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256); - } - ``` - - - The `omniverseBalanceOf` function MAY be implemented as `public` or `external` - -- Optional Extension: NonFungible Token - - ```solidity - import "{IERC6358.sol}"; - - /** - * @notice Interface of the ERC-6358 non fungible token, which inherits {IERC6358} - */ - interface IERC6358NonFungible is IERC6358 { - /** - * @notice Get the number of omniverse NFTs in account `_pk` - * @param _pk `o-account` to be queried - * @return Returns the number of omniverse NFTs in account `_pk` - */ - function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256); - - /** - * @notice Get the owner of an omniverse NFT with `tokenId` - * @param _tokenId Omniverse NFT id to be queried - * @return Returns the owner of an omniverse NFT with `tokenId` - */ - function omniverseOwnerOf(uint256 _tokenId) external view returns (bytes memory); - } - ``` - - - The `omniverseBalanceOf` function MAY be implemented as `public` or `external` - - The `omniverseOwnerOf` function MAY be implemented as `public` or `external` - -## Rationale - -### Architecture - -As shown in [Figure.1](#architecture), smart contracts deployed on multi-chains execute `o-transactions` of ERC-6358 tokens synchronously through the trustless off-chain synchronizers. - -- The ERC-6358 smart contracts are referred to as **Abstract Nodes**. The states recorded by the Abstract Nodes that are deployed on different blockchains respectively could be considered as copies of the global state, and they are ultimately consistent. -- **Synchronizer** is an off-chain execution program responsible for carrying published `o-transactions` from the ERC-6358 smart contracts on one blockchain to the others. The synchronizers work trustless as they just deliver `o-transactions` with others' signatures, and details could be found in the [workflow](#workflow). - -### Principle - -- The `o-account` has been mentioned [above](#omniverse-account). -- The synchronization of the `o-transactions` guarantees the ultimate consistency of token states across all chains. The related data structure is [here](#data-structure). - - - A `nonce` mechanism is brought in to make the states consistent globally. - - The `nonce` appears in two places, the one is `nonce in o-transaction` data structure, and the other is `account nonce` maintained by on-chain ERC-6358 smart contracts. - - When synchronizing, the `nonce in o-transaction` data will be checked by comparing it to the `account nonce`. - -#### Workflow - -- Suppose a common user `A` and her related operation `account nonce` is $k$. -- `A` initiates an `o-transaction` on Ethereum by calling `IERC6358::sendOmniverseTransaction`. The current `account nonce` of `A` in the ERC-6358 smart contracts deployed on Ethereum is $k$ so the valid value of `nonce in o-transaction` needs to be $k+1$. -- The ERC-6358 smart contracts on Ethereum verify the signature of the `o-transaction` data. If the verification succeeds, the `o-transaction` data will be published by the smart contracts on the Ethereum side. The verification includes: - - whether the balance (FT) or the ownership (NFT) is valid - - and whether the `nonce in o-transaction` is $k+1$ -- The `o-transaction` SHOULD NOT be executed on Ethereum immediately, but wait for a time. -- Now, `A`'s latest submitted `nonce in o-transaction` on Ethereum is $k+1$, but still $k$ on other chains. -- The off-chain synchronizers will find a newly published `o-transaction` on Ethereum but not on other chains. -- Next synchronizers will rush to deliver this message because of a rewarding mechanism. (The strategy of the reward could be determined by the deployers of ERC-6358 tokens. For example, the reward could come from the service fee or a mining mechanism.) -- Finally, the ERC-6358 smart contracts deployed on other chains will all receive the `o-transaction` data, verify the signature and execute it when the **waiting time is up**. -- After execution, the `account nonce` on all chains will add 1. Now all the `account nonce` of account `A` will be $k+1$, and the state of the balances of the related account will be the same too. - -## Reference Implementation - -### Omniverse Account - -- An Omniverse Account example: `3092860212ceb90a13e4a288e444b685ae86c63232bcb50a064cb3d25aa2c88a24cd710ea2d553a20b4f2f18d2706b8cc5a9d4ae4a50d475980c2ba83414a796` - - The Omniverse Account is a public key of the elliptic curve `secp256k1` - - The related private key of the example is: `cdfa0e50d672eb73bc5de00cc0799c70f15c5be6b6fca4a1c82c35c7471125b6` - -#### Mapping Mechanism for Different Environments - -In the simplest implementation, we can just build two mappings to get it. One is like `pk based on sece256k1 => account address in the special environment`, and the other is the reverse mapping. - -The `Account System` on `Flow` is a typical example. - -- `Flow` has a built-in mechanism for `account address => pk`. The public key can be bound to an account (a special built-in data structure) and the public key can be got from the `account address` directly. -- A mapping from `pk` to the `account address` on Flow can be built by creating a mapping `{String: Address}`, in which `String` denotes the data type to express the public key and the `Address` is the data type of the `account address` on Flow. - -### ERC-6358 Token - -The ERC-6358 Token could be implemented with the [interfaces mentioned above](#smart-contract-interface). It can also be used with the combination of [ERC-20](./eip-20.md)/[ERC-721](./eip-721.md). - -- The implementation examples of the interfaces can be found at: - - - [Interface `IERC6358`](../assets/eip-6358/src/contracts/interfaces/IERC6358.sol), the basic ERC-6358 interface mentioned [above](#smart-contract-interface) - - [Interface `IERC6358Fungible`](../assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol), the interface for ERC-6358 fungible token - - [Interface `IERC6358NonFungible`](../assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol), the interface for ERC-6358 non-fungible token - -- The implementation example of some common tools to operate ERC-6358 can be found at: - - - [Common Tools](../assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol). - -- The implementation examples of ERC-6358 Fungible Token and ERC-6358 Non-Fungible Token can be found at: - - - [ERC-6358 Fungible Token Example](../assets/eip-6358/src/contracts/ERC6358FungibleExample.sol) - - [ERC-6358 Non-Fungible Token Example](../assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol) - -## Security Considerations - -### Attack Vector Analysis - -According to the above, there are two roles: - -- **common users** are who initiate an `o-transaction` -- **synchronizers** are who just carry the `o-transaction` data if they find differences between different chains. - -The two roles might be where the attack happens: - -#### **Will the *synchronizers* cheat?** - -- Simply speaking, it's none of the **synchronizer**'s business as **they cannot create other users' signatures** unless some **common users** tell him, but at this point, we think it's a problem with the role **common user**. -- The **synchronizer** has no will and cannot do evil because the `o-transaction` data that they deliver is verified by the related **signature** of other **common users**. -- The **synchronizers** would be rewarded as long as they submit valid `o-transaction` data, and *valid* only means that the signature and the amount are both valid. This will be detailed and explained later when analyzing the role of **common user**. -- The **synchronizers** will do the delivery once they find differences between different chains: - - If the current `account nonce` on one chain is smaller than a published `nonce in o-transaction` on another chain - - If the transaction data related to a specific `nonce in o-transaction` on one chain is different from another published `o-transaction` data with the same `nonce in o-transaction` on another chain - -- **Conclusion: The *synchronizers* won't cheat because there are no benefits and no way for them to do so.** - -#### **Will the *common user* cheat?** - -- Simply speaking, **maybe they will**, but fortunately, **they can't succeed**. -- Suppose the current `account nonce` of a **common user** `A` is $k$ on all chains. `A` has 100 token `X`, which is an instance of the ERC-6358 token. -- Common user `A` initiates an `o-transaction` on a Parachain of Polkadot first, in which `A` transfers `10` `X`s to an `o-account` of a **common user** `B`. The `nonce in o-transaction` needs to be $k+1$. After signature and data verification, the `o-transaction` data(`ot-P-ab` for short) will be published on Polkadot. -- At the same time, `A` initiates an `o-transaction` with the **same nonce** $k+1$ but **different data**(transfer `10` `X`s to another `o-account` `C` for example) on Ethereum. This `o-transaction` (named `ot-E-ac` for short) will pass the verification on Ethereum first, and be published. -- At this point, it seems `A` finished a ***double spend attack*** and the states on Polkadot and Ethereum are different. -- **Response strategy**: - - As we mentioned above, the synchronizers will deliver `ot-P-ab` to Ethereum and deliver `ot-E-ac` to Polkadot because they are different although with the same nonce. The synchronizer who submits the `o-transaction` first will be rewarded as the signature is valid. - - Both the ERC-6358 smart contracts or similar mechanisms on Polkadot and Ethereum will find that `A` did cheating after they received both `ot-E-ac` and `ot-P-ab` respectively as the signature of `A` is non-deniable. - - We have mentioned that the execution of an `o-transaction` will not be done immediately and instead there needs to be a fixed waiting time. So the `double spend attack` caused by `A` won't succeed. - - There will be many synchronizers waiting for delivering o-transactions to get rewards. So although it's almost impossible that a **common user** can submit two `o-transactions` to two chains, but none of the synchronizers deliver the `o-transactions` successfully because of a network problem or something else, we still provide a solution: - - The synchronizers will connect to several native nodes of every public chain to avoid the malicious native nodes. - - If it indeed happened that all synchronizers' network break, the `o-transactions` will be synchronized when the network recovered. If the waiting time is up and the cheating `o-transaction` has been executed, we are still able to revert it from where the cheating happens according to the `nonce in o-transaction` and `account nonce`. -- `A` couldn't escape punishment in the end (For example, lock his account or something else, and this is about the certain tokenomics determined by developers according to their own situation). - -- **Conclusion: The *common user* maybe cheat but won't succeed.** - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6358.md diff --git a/EIPS/eip-6366.md b/EIPS/eip-6366.md index ffc37f7b53433e..6d55571671520c 100644 --- a/EIPS/eip-6366.md +++ b/EIPS/eip-6366.md @@ -1,151 +1 @@ ---- -eip: 6366 -title: Permission Token -description: A token that holds the permission of an address in an ecosystem -author: Chiro (@chiro-hiro), Victor Dusart (@vdusart) -discussions-to: https://ethereum-magicians.org/t/eip-6366-a-standard-for-permission-token/9105 -status: Review -type: Standards Track -category: ERC -created: 2022-01-19 -requires: 6617 ---- - -## Abstract - -This EIP offers an alternative to Access Control Lists (ACLs) for granting authorization and enhancing security. A `uint256` is used to store permission of given address in a ecosystem. Each permission is represented by a single bit in a `uint256` as described in [ERC-6617](./eip-6617.md). Bitwise operators and bitmasks are used to determine the access right which is much more efficient and flexible than `string` or `keccak256` comparison. - -## Motivation - -Special roles like `Owner`, `Operator`, `Manager`, `Validator` are common for many smart contracts because permissioned addresses are used to administer and manage them. It is difficult to audit and maintain these system since these permissions are not managed in a single smart contract. - -Since permissions and roles are reflected by the permission token balance of the relevant account in the given ecosystem, cross-interactivity between many ecosystems will be made simpler. - -## 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. - -_Note_ The following specifications use syntax from Solidity `0.8.7` (or above) - -### Core Interface - -Compliant contracts MUST implement `IEIP6366Core`. - -It is RECOMMENDED to define each permission as a power of `2` so that we can check for the relationship between sets of permissions using [ERC-6617](./eip-6617.md). - -```solidity -interface IEIP6366Core { - /** - * MUST trigger when `_permission` are transferred, including `zero` permission transfers. - * @param _from Permission owner - * @param _to Permission receiver - * @param _permission Transferred subset permission of permission owner - */ - event Transfer(address indexed _from, address indexed _to, uint256 indexed _permission); - - /** - * MUST trigger on any successful call to `approve(address _delegatee, uint256 _permission)`. - * @param _owner Permission owner - * @param _delegatee Delegatee - * @param _permission Approved subset permission of permission owner - */ - event Approval(address indexed _owner, address indexed _delegatee, uint256 indexed _permission); - - /** - * Transfers a subset `_permission` of permission to address `_to`. - * The function SHOULD revert if the message caller’s account permission does not have the subset - * of the transferring permissions. The function SHOULD revert if any of transferring permissions are - * existing on target `_to` address. - * @param _to Permission receiver - * @param _permission Subset permission of permission owner - */ - function transfer(address _to, uint256 _permission) external returns (bool success); - - /** - * Allows `_delegatee` to act for the permission owner's behalf, up to the `_permission`. - * If this function is called again it overwrites the current granted with `_permission`. - * `approve()` method SHOULD `revert` if granting `_permission` permission is not - * a subset of all available permissions of permission owner. - * @param _delegatee Delegatee - * @param _permission Subset permission of permission owner - */ - function approve(address _delegatee, uint256 _permission) external returns (bool success); - - /** - * Returns the permissions of the given `_owner` address. - */ - function permissionOf(address _owner) external view returns (uint256 permission); - - /** - * Returns `true` if `_required` is a subset of `_permission` otherwise return `false`. - * @param _permission Checking permission set - * @param _required Required set of permission - */ - function permissionRequire(uint256 _permission, uint256 _required) external view returns (bool isPermissioned); - - /** - * Returns `true` if `_required` permission is a subset of `_actor`'s permissions or a subset of his delegated - * permission granted by the `_owner`. - * @param _owner Permission owner - * @param _actor Actor who acts on behalf of the owner - * @param _required Required set of permission - */ - function hasPermission(address _owner, address _actor, uint256 _required) external view returns (bool isPermissioned); - - /** - * Returns the subset permission of the `_owner` address were granted to `_delegatee` address. - * @param _owner Permission owner - * @param _delegatee Delegatee - */ - function delegated(address _owner, address _delegatee) external view returns (uint256 permission); -} -``` - -### Metadata Interface - -It is RECOMMENDED for compliant contracts to implement the optional extension `IEIP6617Meta`. - -SHOULD define a description for the base permissions and main combinaison. - -SHOULD NOT define a description for every subcombinaison of permissions possible. - -### Error Interface - -Compatible tokens MAY implement `IEIP6366Error` as defined below: - -```solidity -interface IEIP6366Error { - /** - * The owner or actor does not have the required permission - */ - error AccessDenied(address _owner, address _actor, uint256 _permission); - - /** - * Conflict between permission set - */ - error DuplicatedPermission(uint256 _permission); - - /** - * Data out of range - */ - error OutOfRange(); -} -``` - -## Rationale - -Needs discussion. - -## Reference Implementation - -First implementation could be found here: - -- [ERC-6366 Core implementation](../assets/eip-6366/contracts/EIP6366Core.sol) - -## Security Considerations - -Need more discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6366.md diff --git a/EIPS/eip-6372.md b/EIPS/eip-6372.md index 38e63de5bf9466..f4aa7a92f19611 100644 --- a/EIPS/eip-6372.md +++ b/EIPS/eip-6372.md @@ -1,94 +1 @@ ---- -eip: 6372 -title: Contract clock -description: An interface for exposing a contract's clock value and details -author: Hadrien Croubois (@Amxx), Francisco Giordano (@frangio) -discussions-to: https://ethereum-magicians.org/t/eip-6372-contract-clock/12689 -status: Review -type: Standards Track -category: ERC -created: 2023-01-25 ---- - -## Abstract - -Many contracts rely on some clock for enforcing delays and storing historical data. While some contracts rely on block numbers, others use timestamps. There is currently no easy way to discover which time-tracking function a contract internally uses. This EIP proposes to standardize an interface for contracts to expose their internal clock and thus improve composability and interoperability. - -## Motivation - -Many contracts check or store time-related information. For example, timelock contracts enforce a delay before an operation can be executed. Similarly, DAOs enforce a voting period during which stakeholders can approve or reject a proposal. Last but not least, voting tokens often store the history of voting power using timed snapshots. - -Some contracts do time tracking using timestamps while others use block numbers. In some cases, more exotic functions might be used to track time. - -There is currently no interface for an external observer to detect which clock a contract uses. This seriously limits interoperability and forces devs to make risky assumptions. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Compliant contracts MUST implement the `clock` and `CLOCK_MODE` functions as specified below. - -```solidity -interface IERC6372 { - function clock() external view returns (uint48); - function CLOCK_MODE() external view returns (string); -} -``` - -### Methods - -#### clock - -This function returns the current timepoint according to the mode the contract is operating on. It MUST be a **non-decreasing** function of the chain, such as `block.timestamp` or `block.number`. - -```yaml -- name: clock - type: function - stateMutability: view - inputs: [] - outputs: - - name: timepoint - type: uint48 -``` - -#### CLOCK_MODE - -This function returns a machine-readable string description of the clock the contract is operating on. - -This string MUST be formatted like a URL query string (a.k.a. `application/x-www-form-urlencoded`), decodable in standard JavaScript with `new URLSearchParams(CLOCK_MODE)`. - -- If operating using **block number**: - - If the block number is that of the `NUMBER` opcode (`0x43`), then this function MUST return `mode=blocknumber&from=default`. - - If it is any other block number, then this function MUST return `mode=blocknumber&from=`, where `` is a CAIP-2 Blockchain ID such as `eip155:1`. -- If operating using **timestamp**, then this function MUST return `mode=timestamp`. -- If operating using any other mode, then this function SHOULD return a unique identifier for the encoded `mode` field. - -```yaml -- name: CLOCK_MODE - type: function - stateMutability: view - inputs: [] - outputs: - - name: descriptor - type: string -``` - -### Expected properties - -- The `clock()` function MUST be non-decreasing. - -## Rationale - -`clock` returns `uint48` as it is largely sufficient for storing realistic values. In timestamp mode, `uint48` will be enough until the year 8921556. Even in block number mode, with 10,000 blocks per second, it would be enough until the year 2861. Using a type smaller than `uint256` allows storage packing of timepoints with other associated values, greatly reducing the cost of writing and reading from storage. - -Depending on the evolution of the blockchain (particularly layer twos), using a smaller type, such as `uint32` might cause issues fairly quickly. On the other hand, anything bigger than `uint48` appears wasteful. - -In addition to timestamps, it is sometimes necessary to define durations or delays, which are a difference between timestamps. In the general case, we would expect these values to be represented with the same type than timepoints (`uint48`). However, we believe that in most cases `uint32` is a good alternative, as it represents over 136 years if the clock operates using seconds. In most cases, we recommend using `uint48` for storing timepoints and using `uint32` for storing durations. That recommendation applies to "reasonable" durations (delay for a timelock, voting or vesting duration, ...) when operating with timestamps or block numbers that are more than 1 second apart. - -## Security Considerations - -No known security issues. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6372.md diff --git a/EIPS/eip-6381.md b/EIPS/eip-6381.md index 51cf1733405a1f..2f62d10c5e8e1d 100644 --- a/EIPS/eip-6381.md +++ b/EIPS/eip-6381.md @@ -1,368 +1 @@ ---- -eip: 6381 -title: Public Non-Fungible Token Emote Repository -description: React to any Non-Fungible Tokens using Unicode emojis. -author: Bruno Škvorc (@Swader), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/eip-6381-emotable-extension-for-non-fungible-tokens/12710 -status: Final -type: Standards Track -category: ERC -created: 2023-01-22 -requires: 165 ---- - -## Abstract - -The Public Non-Fungible Token Emote Repository standard provides an enhanced interactive utility for [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) by allowing NFTs to be emoted at. - -This proposal introduces the ability to react to NFTs using Unicode standardized emoji in a public non-gated repository smart contract that is accessible at the same address in all of the networks. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability for anyone to interact with an NFT introduces an interactive aspect to owning an NFT and unlocks feedback-based NFT mechanics. - -This ERC introduces new utilities for [ERC-721](./eip-721.md) based tokens in the following areas: - -- [Interactivity](#interactivity) -- [Feedback based evolution](#feedback-based-evolution) -- [Valuation](#valuation) - -### Interactivity - -The ability to emote on an NFT introduces the aspect of interactivity to owning an NFT. This can either reflect the admiration for the emoter (person emoting to an NFT) or can be a result of a certain action performed by the token's owner. Accumulating emotes on a token can increase its uniqueness and/or value. - -### Feedback based evolution - -Standardized on-chain reactions to NFTs allow for feedback based evolution. - -Current solutions are either proprietary or off-chain and therefore subject to manipulation and distrust. Having the ability to track the interaction on-chain allows for trust and objective evaluation of a given token. Designing the tokens to evolve when certain emote thresholds are met incentivizes interaction with the token collection. - -### Valuation - -Current NFT market heavily relies on previous values the token has been sold for, the lowest price of the listed token and the scarcity data provided by the marketplace. There is no real time indication of admiration or desirability of a specific token. Having the ability for users to emote to the tokens adds the possibility of potential buyers and sellers gauging the value of the token based on the impressions the token has collected. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title ERC-6381 Emotable Extension for Non-Fungible Tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-6381 -/// @dev Note: the ERC-165 identifier for this interface is 0xd9fac55a. - -pragma solidity ^0.8.16; - -interface IERC6381 /*is IERC165*/ { - /** - * @notice Used to notify listeners that the token with the specified ID has been emoted to or that the reaction has been revoked. - * @dev The event MUST only be emitted if the state of the emote is changed. - * @param emoter Address of the account that emoted or revoked the reaction to the token - * @param collection Address of the collection smart contract containing the token being emoted to or having the reaction revoked - * @param tokenId ID of the token - * @param emoji Unicode identifier of the emoji - * @param on Boolean value signifying whether the token was emoted to (`true`) or if the reaction has been revoked (`false`) - */ - event Emoted( - address indexed emoter, - address indexed collection, - uint256 indexed tokenId, - bytes4 emoji, - bool on - ); - - /** - * @notice Used to get the number of emotes for a specific emoji on a token. - * @param collection Address of the collection containing the token being checked for emoji count - * @param tokenId ID of the token to check for emoji count - * @param emoji Unicode identifier of the emoji - * @return Number of emotes with the emoji on the token - */ - function emoteCountOf( - address collection, - uint256 tokenId, - bytes4 emoji - ) external view returns (uint256); - - /** - * @notice Used to get the number of emotes for a specific emoji on a set of tokens. - * @param collections An array of addresses of the collections containing the tokens being checked for emoji count - * @param tokenIds An array of IDs of the tokens to check for emoji count - * @param emojis An array of unicode identifiers of the emojis - * @return An array of numbers of emotes with the emoji on the tokens - */ - function bulkEmoteCountOf( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis - ) external view returns (uint256[] memory); - - /** - * @notice Used to get the information on whether the specified address has used a specific emoji on a specific - * token. - * @param emoter Address of the account we are checking for a reaction to a token - * @param collection Address of the collection smart contract containing the token being checked for emoji reaction - * @param tokenId ID of the token being checked for emoji reaction - * @param emoji The ASCII emoji code being checked for reaction - * @return A boolean value indicating whether the `emoter` has used the `emoji` on the token (`true`) or not - * (`false`) - */ - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji - ) external view returns (bool); - - /** - * @notice Used to get the information on whether the specified addresses have used specific emojis on specific - * tokens. - * @param emoters An array of addresses of the accounts we are checking for reactions to tokens - * @param collections An array of addresses of the collection smart contracts containing the tokens being checked - * for emoji reactions - * @param tokenIds An array of IDs of the tokens being checked for emoji reactions - * @param emojis An array of the ASCII emoji codes being checked for reactions - * @return An array of boolean values indicating whether the `emoter`s has used the `emoji`s on the tokens (`true`) - * or not (`false`) - */ - function haveEmotersUsedEmotes( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis - ) external view returns (bool[] memory); - - /** - * @notice Used to get the message to be signed by the `emoter` in order for the reaction to be submitted by someone - * else. - * @param collection The address of the collection smart contract containing the token being emoted at - * @param tokenId ID of the token being emoted - * @param emoji Unicode identifier of the emoji - * @param state Boolean value signifying whether to emote (`true`) or undo (`false`) emote - * @param deadline UNIX timestamp of the deadline for the signature to be submitted - * @return The message to be signed by the `emoter` in order for the reaction to be submitted by someone else - */ - function prepareMessageToPresignEmote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to get multiple messages to be signed by the `emoter` in order for the reaction to be submitted by someone - * else. - * @param collections An array of addresses of the collection smart contracts containing the tokens being emoted at - * @param tokenIds An array of IDs of the tokens being emoted - * @param emojis An arrau of unicode identifiers of the emojis - * @param states An array of boolean values signifying whether to emote (`true`) or undo (`false`) emote - * @param deadlines An array of UNIX timestamps of the deadlines for the signatures to be submitted - * @return The array of messages to be signed by the `emoter` in order for the reaction to be submitted by someone else - */ - function bulkPrepareMessagesToPresignEmote( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states, - uint256[] memory deadlines - ) external view returns (bytes32[] memory); - - /** - * @notice Used to emote or undo an emote on a token. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @param collection Address of the collection containing the token being emoted at - * @param tokenId ID of the token being emoted - * @param emoji Unicode identifier of the emoji - * @param state Boolean value signifying whether to emote (`true`) or undo (`false`) emote - */ - function emote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state - ) external; - - /** - * @notice Used to emote or undo an emote on multiple tokens. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @dev MUST revert if the lengths of the `collections`, `tokenIds`, `emojis` and `states` arrays are not equal. - * @param collections An array of addresses of the collections containing the tokens being emoted at - * @param tokenIds An array of IDs of the tokens being emoted - * @param emojis An array of unicode identifiers of the emojis - * @param states An array of boolean values signifying whether to emote (`true`) or undo (`false`) emote - */ - function bulkEmote( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states - ) external; - - /** - * @notice Used to emote or undo an emote on someone else's behalf. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @dev MUST revert if the lengths of the `collections`, `tokenIds`, `emojis` and `states` arrays are not equal. - * @dev MUST revert if the `deadline` has passed. - * @dev MUST revert if the recovered address is the zero address. - * @param emoter The address that presigned the emote - * @param collection The address of the collection smart contract containing the token being emoted at - * @param tokenId IDs of the token being emoted - * @param emoji Unicode identifier of the emoji - * @param state Boolean value signifying whether to emote (`true`) or undo (`false`) emote - * @param deadline UNIX timestamp of the deadline for the signature to be submitted - * @param v `v` value of an ECDSA signature of the message obtained via `prepareMessageToPresignEmote` - * @param r `r` value of an ECDSA signature of the message obtained via `prepareMessageToPresignEmote` - * @param s `s` value of an ECDSA signature of the message obtained via `prepareMessageToPresignEmote` - */ - function presignedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji, - bool state, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to bulk emote or undo an emote on someone else's behalf. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @dev MUST revert if the lengths of the `collections`, `tokenIds`, `emojis` and `states` arrays are not equal. - * @dev MUST revert if the `deadline` has passed. - * @dev MUST revert if the recovered address is the zero address. - * @param emoters An array of addresses of the accounts that presigned the emotes - * @param collections An array of addresses of the collections containing the tokens being emoted at - * @param tokenIds An array of IDs of the tokens being emoted - * @param emojis An array of unicode identifiers of the emojis - * @param states An array of boolean values signifying whether to emote (`true`) or undo (`false`) emote - * @param deadlines UNIX timestamp of the deadline for the signature to be submitted - * @param v An array of `v` values of an ECDSA signatures of the messages obtained via `prepareMessageToPresignEmote` - * @param r An array of `r` values of an ECDSA signatures of the messages obtained via `prepareMessageToPresignEmote` - * @param s An array of `s` values of an ECDSA signatures of the messages obtained via `prepareMessageToPresignEmote` - */ - function bulkPresignedEmote( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states, - uint256[] memory deadlines, - uint8[] memory v, - bytes32[] memory r, - bytes32[] memory s - ) external; -} -``` - -### Message format for presigned emotes - -The message to be signed by the `emoter` in order for the reaction to be submitted by someone else is formatted as follows: - -```solidity -keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collection, - tokenId, - emoji, - state, - deadline - ) - ); -``` - -The values passed when generating the message to be signed are: - -- `DOMAIN_SEPARATOR` - The domain separator of the Emotable repository smart contract -- `collection` - Address of the collection containing the token being emoted at -- `tokenId` - ID of the token being emoted -- `emoji` - Unicode identifier of the emoji -- `state` - Boolean value signifying whether to emote (`true`) or undo (`false`) emote -- `deadline` - UNIX timestamp of the deadline for the signature to be submitted - -The `DOMAIN_SEPARATOR` is generated as follows: - -```solidity -keccak256( - abi.encode( - "ERC-6381: Public Non-Fungible Token Emote Repository", - "1", - block.chainid, - address(this) - ) - ); -``` - -Each chain, that the Emotable repository smart contract is deployed on, will have a different `DOMAIN_SEPARATOR` value due to chain IDs being different. - -### Pre-determined address of the Emotable repository - -The address of the Emotable repository smart contract is designed to resemble the function it serves. It starts with `0x311073` which is the abstract representation of `EMOTE`. The address is: - -``` -0x31107354b61A0412E722455A771bC462901668eA -``` - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **Does the proposal support custom emotes or only the Unicode specified ones?**\ -The proposal only accepts the Unicode identifier which is a `bytes4` value. This means that while we encourage implementers to add the reactions using standardized emojis, the values not covered by the Unicode standard can be used for custom emotes. The only drawback being that the interface displaying the reactions will have to know what kind of image to render and such additions will probably be limited to the interface or marketplace in which they were made. -2. **Should the proposal use emojis to relay the impressions of NFTs or some other method?**\ -The impressions could have been done using user-supplied strings or numeric values, yet we decided to use emojis since they are a well established mean of relaying impressions and emotions. -3. **Should the proposal establish an emotable extension or a common-good repository?**\ -Initially we set out to create an emotable extension to be used with any ERC-721 compilant tokens. However, we realized that the proposal would be more useful if it was a common-good repository of emotable tokens. This way, the tokens that can be reacted to are not only the new ones but also the old ones that have been around since before the proposal.\ -In line with this decision, we decided to calculate a deterministic address for the repository smart contract. This way, the repository can be used by any NFT collection without the need to search for the address on the given chain. -4. **Should we include only single-action operations, only multi-action operations, or both?**\ -We've considered including only single-action operations, where the user is only able to react with a single emoji to a single token, but we decided to include both single-action and multi-action operations. This way, the users can choose whether they want to emote or undo emote on a single token or on multiple tokens at once.\ -This decision was made for the long-term viability of the proposal. Based on the gas cost of the network and the number of tokens in the collection, the user can choose the most cost-effective way of emoting. -5. **Should we add the ability to emote on someone else's behalf?**\ -While we did not intend to add this as part of the proposal when drafting it, we realized that it would be a useful feature for it. This way, the users can emote on behalf of someone else, for example, if they are not able to do it themselves or if the emote is earned through an off-chain activity. -6. **How do we ensure that emoting on someone else's behalf is legitimate?**\ -We could add delegates to the proposal; when a user delegates their right to emote to someone else, the delegate can emote on their behalf. However, this would add a lot of complexity and additional logic to the proposal.\ -Using ECDSA signatures, we can ensure that the user has given their consent to emote on their behalf. This way, the user can sign a message with the parameters of the emote and the signature can be submitted by someone else. -7. **Should we add chain ID as a parameter when reacting to a token?**\ -During the course of discussion of the proposal, a suggestion arose that we could add chain ID as a parameter when reacting to a token. This would allow the users to emote on the token of one chain on another chain.\ -We decided against this as we feel that additional parameter would rarely be used and would add additional cost to the reaction transactions. If the collection smart contract wants to utilize on-chain emotes to tokens they contain, they require the reactions to be recorded on the same chain. Marketplaces and wallets integrating this proposal will rely on reactions to reside in the same chain as well, because if chain ID parameter was supported this would mean that they would need to query the repository smart contract on all of the chains the repository is deployed in order to get the reactions for a given token.\ -Additionally, if the collection creator wants users to record their reactions on a different chain, they can still direct the users to do just that. The repository does not validate the existence of the token being reacted to, which in theory means that you can react to non-existent token or to a token that does not exist yet. The likelihood of a different collection existing at the same address on another chain is significantly low, so the users can react using the collection's address on another chain and it is very unlikely that they will unintentionally react to another collection's token. - -## Backwards Compatibility - -The Emote repository standard is fully compatible with [ERC-721](./eip-721.md) and with the robust tooling available for implementations of ERC-721 as well as with the existing ERC-721 infrastructure. - -## Test Cases - -Tests are included in [`emotableRepository.ts`](../assets/eip-6381/test/emotableRepository.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-6381 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`EmotableRepository.sol`](../assets/eip-6381/contracts/EmotableRepository.sol). - -## Security Considerations - -The proposal does not envision handling any form of assets from the user, so the assets should not be at risk when interacting with an Emote repository. - -The ability to use ECDSA signatures to emote on someone else's behalf introduces the risk of a replay attack, which the format of the message to be signed guards against. The `DOMAIN_SEPARATOR` used in the message to be signed is unique to the repository smart contract of the chain it is deployed on. This means that the signature is invalid on any other chain and the Emote repositories deployed on them should revert the operation if a replay attack is attempted. - -Another thing to consider is the ability of presigned message reuse. Since the message includes the signature validity deadline, the message can be reused any number of times before the deadline is reached. The proposal only allows for a single reaction with a given emoji to a specific token to be active, so the presigned message can not be abused to increase the reaction count on the token. However, if the service using the repository relies on the ability to revoke the reaction after certain actions, a valid presigned message can be used to re-react to the token. We suggest that the services using the repository in cnjunction with presigned messages use deadlines that invalidate presigned messages after a reasonalby short period of time. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6381.md diff --git a/EIPS/eip-6384.md b/EIPS/eip-6384.md index 619c8edae2c8de..486736bda7c2a7 100644 --- a/EIPS/eip-6384.md +++ b/EIPS/eip-6384.md @@ -1,144 +1 @@ ---- -eip: 6384 -title: Human-readable offline signatures -description: A method for retrieving a human-readable description of EIP-712 typed and structured data. -author: Tal Be'ery , RoiV (@DeVaz1) -discussions-to: https://ethereum-magicians.org/t/eip-6384-readable-eip-712-signatures/12752 -status: Stagnant -type: Standards Track -category: ERC -created: 2023-01-08 -requires: 712 ---- - -## Abstract - -This EIP introduces the `evalEIP712Buffer` function, which takes an [EIP-712](./eip-712.md) buffer and returns a human-readable text description. - -## Motivation - -The use case of Web3 off-chain signatures intended to be used within on-chain transaction is gaining traction and being used in multiple leading protocols (e.g. OpenSea) and standards [EIP-2612](./eip-2612.md), mainly as it offers a fee-less experience. -Attackers are known to actively and successfully abuse such off-chain signatures, leveraging the fact that users are blindly signing off-chain messages, since they are not humanly readable. -While [EIP-712](./eip-712.md) originally declared in its title that being ”humanly readable” is one of its goals, it did not live up to its promise eventually and EIP-712 messages are not understandable by an average user. - -In one example, victims browse a malicious phishing website. It requests the victim to sign a message that will put their NFT token for sale on OpenSea platform, virtually for free. - -The user interface for some popular wallet implementations is not conveying the actual meaning of signing such transactions. - -In this proposal we offer a secure and scalable method to bring true human readability to EIP-712 messages by leveraging their bound smart contracts. -As a result, once implemented this EIP wallets can upgrade their user experience from current state: - -![](../assets/eip-6384/media/MiceyMask-non-compliant.png) - -to a much clearer user experience: - -![](../assets/eip-6384/media/ZenGo-EIP-compliant-warning.png) - -The proposed solution solves the readability issues by allowing the wallet to query the `verifyingContract`. The incentives for keeping the EIP-712 message description as accurate as possible are aligned, as the responsibility for the description is now owned by the contract, that: - -- Knows the message meaning exactly (and probably can reuse the code that handles this message when received on chain) -- Natively incentivized to provide the best explanation to prevent a possible fraud -- Not involving a third party that needs to be trusted -- Maintains the fee-less customer experience as the added function is in “view” mode and does not require an on-chain execution and fees. -- Maintains Web3’s composability property - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -EIP-712 already formally binds an off-chain signature to a contract, with the `verifyingContract` parameter. We suggest adding a “view” function (`"stateMutability":"view"`) to such contracts, that returns a human readable description of the meaning of this specific off-chain buffer. - -```solidity -/** - * @dev Returns the expected result of the offchain message. -*/ - - function evalEIP712Buffer(bytes32 domainHash, string memory primaryType, bytes memory typedDataBuffer) - external - view - returns (string[] memory) { - ... - -} -``` - -**Every compliant contract MUST implement this function.** - -Using this function, wallets can submit the proposed off-chain signature to the contract and present the results to the user, allowing them to enjoy an “on-chain simulation equivalent” experience to their off-chain message. - -This function will have a well known name and signature, such that there is no need for updates in the EIP-712 structure. - -### Function's inputs - -The inputs of the function: - -- `domainHash` is the EIP-712's domainSeparator, a hashed `eip712Domain` struct. -- `primaryType`is the EIP-712's `primaryType`. -- `typedDataBuffer` is an ABI encoded message part of the EIP-712 full message. - -### Function's output(s) - -The output of the the function is an array of strings. The wallet SHOULD display them to its end-users. The wallet MAY choose to augment the returned strings with additional data. (e.g. resolve contract addresses to their name) - -The strings SHOULD NOT be formatted (e.g. should not contain HTML code) and wallets SHOULD treat this string as an untrusted input and handle its rendering as such. - -### Support for EIP-712 messages that are not meant to be used on-chain - -If `verifyingContract` is not included in the EIP-712 domain separator, wallets MUST NOT retrieve a human-readable description using this EIP. In this case, wallets SHOULD fallback to their original EIP-712 display. - -## Rationale - -- We chose to implement the `typeDataBuffer` parameter as abi encoded as it is a generic way to pass the data to the contract. The alternative was to pass the `typedData` struct, which is not generic as it requires the contract to specify the message data. -- We chose to return an array of strings and not a single string as there are potential cases where the message is composed of multiple parts. For example, in the case of a multiple assets transfers in the same `typedDataBuffer`, the contract is advised to describe each transfer in a separate string to allow the wallet to display each transfer separately. - -### Alternative solutions - -#### Third party services: - -Currently, the best choice for users is to rely on some 3rd party solutions that get the proposed message as input and explain its intended meaning to the user. This approach is: - -- Not scalable: 3rd party provider needs to learn all such proprietary messages -- Not necessarily correct: the explanation is based on 3rd party interpretation of the original message author -- Introduces an unnecessary dependency of a third party which may have some operational, security, and privacy implications. - -#### Domain name binding - -Alternatively, wallets can bind domain name to a signature. i.e. only accept EIP-712 message if it comes from a web2 domain that its `name` as defined by EIP-712 is included in `eip712Domain`. However this approach has the following disadvantages: - -- It breaks Web3’s composability, as now other dapps cannot interact with such messages -- Does not protect against bad messages coming from the specified web2 domain, e.g. when web2 domain is hacked -- Some current connector, such as WalletConnect do not allow wallets to verify the web2 domain authenticity - -## Backwards Compatibility - -For non-supporting contracts the wallets will default to showing whatever they are showing today. -Non-supporting wallets will not call this function and will default to showing whatever they are showing today. - -## Reference Implementation - -A reference implementation can be found [here](../assets/eip-6384/implementation/src/MyToken/MyToken.sol). -This toy example shows how an [EIP-20](./eip-20.md) contract supporting this EIP implements an EIP-712 support for "transferWithSig" functionality (a non-standard variation on Permit, as the point of this EIP is to allow readability to non-standard EIP-712 buffers). -To illustrate the usability of this EIP to some real world use case, a helper function for the actual OpenSea's SeaPort EIP-712 is implemented too in [here](../assets/eip-6384/implementation/src/SeaPort/SeaPort712ParserHelper.sol). - -## Security Considerations - -### The threat model: - -The attack is facilitated by a rogue web2 interface (“dapp”) that provides bad parameters for an EIP-712 formatted message that is intended to be consumed by a legitimate contract. Therefore, the message is controlled by attackers and cannot be trusted, however the contract is controlled by a legitimate party and can be trusted. - -The attacker intends to use that signed EIP-712 message on-chain later on, with a transaction crafted by the attackers. If the subsequent on-chain transaction was to be sent by the victim, then a regular transaction simulation would have sufficed. - -The case of a rogue contract is irrelevant, as such a rogue contract can already facilitate the attack regardless of the existence of the EIP-712 formatted message. - -Having said that, a rogue contract may try to abuse this functionality in order to send some maliciously crafted string in order to exploit vulnerabilities in wallet rendering of the string. Therefore wallets should treat this string as an untrusted input and handle its renderring it as such. - -### Analysis of the proposed solution - -The explanation is controlled by the relevant contract which is controlled by a legitimate party. The attacker must specify the relevant contract address, as otherwise it will not be accepted by it. Therefore, the attacker cannot create false explanations using this method. -Please note that if the explanation was part of the message to sign it would have been under the control of the attacker and hence irrelevant for security purposes. - -Since the added functionality to the contract has the “view” modifier, it cannot change the on-chain state and harm the existing functionalities of the contract. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6384.md diff --git a/EIPS/eip-6454.md b/EIPS/eip-6454.md index bd51bd43638132..821a5655a2b55b 100644 --- a/EIPS/eip-6454.md +++ b/EIPS/eip-6454.md @@ -1,138 +1 @@ ---- -eip: 6454 -title: Minimal Transferable NFT detection interface -description: A minimal extension to identify the transferability of Non-Fungible Tokens. -author: Bruno Škvorc (@Swader), Francesco Sullo (@sullof), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/minimalistic-transferable-interface/12517 -status: Final -type: Standards Track -category: ERC -created: 2023-01-31 -requires: 165, 721 ---- - -## Abstract - -The Minimalistic Transferable interface for Non-Fungible Tokens standard extends [ERC-721](./eip-721.md) by introducing the ability to identify whether an NFT can be transferred or not. - -This proposal introduces the ability to prevent a token from being transferred from their owner, making them bound to the externally owned account, abstracted account, smart contract or token that owns it. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability to prevent the tokens from being transferred introduces new possibilities of NFT utility and evolution. - -This proposal is designed in a way to be as minimal as possible in order to be compatible with any usecases that wish to utilize this proposal. - -This EIP introduces new utilities for [ERC-721](./eip-721.md) based tokens in the following areas: - -- [Verifiable attribution](#verifiable-attribution) -- [Immutable properties](#immutable-properties) - -### Verifiable attribution - -Personal achievements can be represented by non-fungible tokens. These tokens can be used to represent a wide range of accomplishments, including scientific advancements, philanthropic endeavors, athletic achievements, and more. However, if these achievement-indicating NFTs can be easily transferred, their authenticity and trustworthiness can be called into question. By binding the NFT to a specific account, it can be ensured that the account owning the NFT is the one that actually achieved the corresponding accomplishment. This creates a secure and verifiable record of personal achievements that can be easily accessed and recognized by others in the network. The ability to verify attribution helps to establish the credibility and value of the achievement-indicating NFT, making it a valuable asset that can be used as a recognition of the holder's accomplishments. - -### Immutable properties - -NFT properties are a critical aspect of non-fungible tokens, serving to differentiate them from one another and establish their scarcity. Centralized control of NFT properties by the issuer, however, can undermine the uniqueness of these properties. - -By tying NFTs to specific properties, the original owner is ensured that the NFT will always retain these properties and its uniqueness. - -In a blockchain game that employs non-transferable NFTs to represent skills or abilities, each skill would be a unique and permanent asset tied to a specific player or token. This would ensure that players retain ownership of the skills they have earned and prevent them from being traded or sold to other players. This can increase the perceived value of these skills, enhancing the player experience by allowing for greater customization and personalization of characters. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title EIP-6454 Minimalistic Non-Transferable interface for NFTs -/// @dev See https://eips.ethereum.org/EIPS/eip-6454 -/// @dev Note: the ERC-165 identifier for this interface is 0x91a6262f. - -pragma solidity ^0.8.16; - -interface IERC6454 /* is IERC165 */ { - /** - * @notice Used to check whether the given token is transferable or not. - * @dev If this function returns `false`, the transfer of the token MUST revert execution. - * @dev If the tokenId does not exist, this method MUST revert execution, unless the token is being checked for - * minting. - * @dev The `from` parameter MAY be used to also validate the approval of the token for transfer, but anyone - * interacting with this function SHOULD NOT rely on it as it is not mandated by the proposal. - * @param tokenId ID of the token being checked - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @return Boolean value indicating whether the given token is transferable - */ - function isTransferable(uint256 tokenId, address from, address to) external view returns (bool); -} -``` - -In order to determine whether a token is transferable or not in general, the function SHOULD return the appropriate boolean value when passing the `0x0000000000000000000000000000000000000000` address as the `to` and `from` parameter. - -The general transferability of a token should not be affected by the ability to mint the token (value of `from` parameter is `0x0000000000000000000000000000000000000000`) and the ability to burn the token (value of `to` parameter is `0x0000000000000000000000000000000000000000`). - -If the general transferability of token is `false`, any kind of transfer of the token, save minting and burning, MUST revert execution. - -In order to determine whether a token is mintable, the exception SHOULD be made to allow the `tokenId` parameter for a token that does not exist. Additionally the `from` parameter SHOULD be `0x0000000000000000000000000000000000000000` and the `to` parameter SHOULD NOT be `0x0000000000000000000000000000000000000000`. - -In order to determine whether a token is burnable, the `from` parameter SHOULD NOT be `0x0000000000000000000000000000000000000000` and the `to` parameter SHOULD be `0x0000000000000000000000000000000000000000`. - -Implementers MAY choose to validate the approval of the token for transfer by the `from` parameter, but anyone interacting with this function SHOULD NOT rely on it as it is not mandated by the proposal. This means that the `from` parameter in such implementations validates the initiator of the transaction rather than the owner from which the token is being transferred (which can either be the owner of the token or the operator allowed to transfer the token). - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **Should we propose another (Non-)Transferable NFT proposal given the existence of existing ones, some even final, and how does this proposal compare to them?**\ - This proposal aims to provide the minimum necessary specification for the implementation of non-transferable NFTs, we feel none of the existing proposals have presented the minimal required interface. Unlike other proposals that address the same issue, this proposal requires fewer methods in its specification, providing a more streamlined solution. -2. **Why is there no event marking the token as Non-Transferable in this interface?**\ - The token can become non-transferable either at its creation, after being marked as non-transferable, or after a certain condition is met. This means that some cases of tokens becoming non-transferable cannot emit an event, such as if the token becoming non-transferable is determined by a block number. Requiring an event to be emitted upon the token becoming non-transferable is not feasible in such cases. -3. **Should the transferability state management function be included in this proposal?**\ - A function that marks a token as non-transferable or releases the binding is referred to as the transferability management function. To maintain the objective of designing an agnostic minimal transferable proposal, we have decided not to specify the transferability management function. This allows for a variety of custom implementations that require the tokens to be non-transferable. -4. **Why should this be an EIP if it only contains one method?**\ - One could argue that since the core of this proposal is to only prevent ERC-721 tokens to be transferred, this could be done by overriding the transfer function. While this is true, the only way to assure that the token is non-transferable before the smart contract execution, is for it to have the transferable interface.\ - This also allows for smart contract to validate whether the token is not transferable and not attempt transferring it as this would result in failed transactions and wasted gas. -5. **Should we include the most straightforward method possible that only accepts a `tokenId` parameter?**\ - The initial version of the proposal contained a method that only accepted a `tokenId` parameter. This method would return a boolean value indicating whether the token is transferable. However, the fact that the token can be non-transferable for different reasons was brought up throughout the discussion. This is why the method was changed to accept additional parameters, allowing for a more flexible implementation. Additionally, we kept the original method’s functionality by specifying the methodology on how to achieve the same result (by passing the `0x0000000000000000000000000000000000000000` address as the `to` and `from` parameters). -6. **What is the best user experience for frontend?**\ - The best user experience for the front end is having a single method that checks whether the token is transferable. This method should handle both cases of transferability, general and conditional.\ - The front end should also be able to handle the case where the token is not transferable and the transfer is attempted. This can be done by checking the return value of the transfer function, which will be false if the token is not transferable. If the token would just be set as non-transferable, without a standardized interface to check whether the token is transferable, the only way to validate transferability would be to attempt a gas calculation and check whether the transaction would revert. This is a bad user experience and should be avoided. -7. **Should we mandate that the `isTransferable` validates approvals as well?**\ - We considered specifying that the `from` parameter represents the initiator of the token transfer. This would mean that the `from` would validate whether the address is the owner of the token or approved to transfer it. While this might be beneficial, we ultimately decided to make it optional.\ - As this proposal aims to be the minimal possible implementation and the approvals are already standardized, we feel that `isTransferable` can be used in conjunction with the approvals to validate whether the given address can initiate the transfer or not.\ - Additionally, mandating the validation of approvals would incur higher gas consumption as additional checks would be required to validate the transferability. - -## Backwards Compatibility - -The Minimalistic Non-Transferable token standard is fully compatible with [ERC-721](./eip-721.md) and with the robust tooling available for implementations of ERC-721 as well as with the existing ERC-721 infrastructure. - -## Test Cases - -Tests are included in [`transferable.ts`](../assets/eip-6454/test/transferable.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-6454 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`ERC721TransferableMock.sol`](../assets/eip-6454/contracts/mocks/ERC721TransferableMock.sol). - -## Security Considerations - -The same security considerations as with [ERC-721](./eip-721.md) apply: hidden logic may be present in any of the functions, including burn, add asset, accept asset, and more. - -A smart contract can implement the proposal interface but returns fraudulent values, i.e., returning `false` for `isTransferable` when the token is transferable. Such a contract would trick other contracts into thinking that the token is non-transferable when it is transferable. If such a contract exists, we suggest not interacting with it. Much like fraudulent [ERC-20](./eip-20.md) or [ERC-721](./eip-721.md) smart contracts, it is not possible to prevent such contracts from existing. We suggest that you verify all of the external smart contracts you interact with and not interact with contracts you do not trust. - -Since the transferability state can change over time, verifying that the state of the token is transferable before interacting with it is essential. Therefore, a dApp, marketplace, or wallet implementing this interface should verify the state of the token every time the token is displayed. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6454.md diff --git a/EIPS/eip-6464.md b/EIPS/eip-6464.md index 7156420d68bee1..1b6746d2a8b002 100644 --- a/EIPS/eip-6464.md +++ b/EIPS/eip-6464.md @@ -1,198 +1 @@ ---- -eip: 6464 -title: Multi-operator, per-token ERC-721 approvals. -description: Extends ERC-721 to allow token owners to approve multiple operators to control their assets on a per-token basis. -author: Cristian Espinoza (@crisgarner), Simon Fremaux (@dievardump), David Huber (@cxkoda), and Arran Schlosberg (@aschlosberg) -discussions-to: https://ethereum-magicians.org/t/fine-grained-erc721-approval-for-multiple-operators/12796 -status: Stagnant -type: Standards Track -category: ERC -created: 2023-02-02 -requires: 165, 721 ---- - -## Abstract - -[ERC-721](./eip-721.md) did not foresee the approval of multiple operators to manage a specific token on behalf of its owner. This lead to the establishment of `setApprovalForAll()` as the predominant way to authorise operators, which affords the approved address control over all assets and creates an unnecessarily broad security risk that has already been exploited in a multitude of phishing attacks. The presented EIP extends ERC-721 by introducing a fine-grained, on-chain approval mechanism that allows owners to authorise multiple, specific operators on a per-token basis; this removes unnecessary access permissions and shrinks the surface for exploits to a minimum. The provided reference implementation further enables cheap revocation of all approvals on a per-owner or per-token basis. - -## Motivation - -The NFT standard defined in ERC-721 allows token owners to "approve" arbitrary addresses to control their tokens—the approved addresses are known as "operators". Two types of approval were defined: - -1. `approve(address,uint256)` provides a mechanism for only a single operator to be approved for a given `tokenId`; and -2. `setApprovalForAll(address,bool)` toggles whether an operator is approved for *every* token owned by `msg.sender`. - -With the introduction of multiple NFT marketplaces, the ability to approve multiple operators for a particular token is necessary if sellers wish to allow each marketplace to transfer a token upon sale. There is, however, no mechanism for achieving this without using `setApprovalForAll()`. This is in conflict with the principle of least privilege and creates an attack vector that is exploited by phishing for malicious (i.e. zero-cost) sell-side signatures that are executed by legitimate marketplace contracts. - -This EIP therefore defines a fine-grained approach for approving multiple operators but scoped to specific token(s). - -### Goals - -1. Ease of adoption for marketplaces; requires minimal changes to existing workflows. -2. Ease of adoption for off-chain approval-indexing services. -3. Simple revocation of approvals; i.e. not requiring one per grant. - -### Non-goals - -1. Security measures for protecting NFTs other than through limiting the scope of operator approvals. -2. Compatibility with [ERC-1155](./eip-1155.md) semi-fungible tokens. However we note that the mechanisms described herein are also applicable to ERC-1155 token *types* without requiring approval for all other types. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -To comply with this EIP, a contract MUST implement `IERC6464` (defined herein) and the `ERC165` and `ERC721` interfaces; see [ERC-165](./eip-165.md) and ERC-721 respectively. - -```solidity -/** - * @notice Extends ERC-721 to include per-token approval for multiple operators. - * @dev Off-chain indexers of approvals SHOULD assume that an operator is approved if either of `ERC721.Approval(…)` or - * `ERC721.ApprovalForAll(…, true)` events are witnessed without the corresponding revocation(s), even if an - * `ExplicitApprovalFor(…, false)` is emitted. - * @dev TODO: the ERC-165 identifier for this interface is TBD. - */ -interface IERC6464 is ERC721 { - /** - * @notice Emitted when approval is explicitly granted or revoked for a token. - */ - event ExplicitApprovalFor( - address indexed operator, - uint256 indexed tokenId, - bool approved - ); - - /** - * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are - * revoked for all tokens. - * @dev MUST be emitted upon calls to `revokeAllExplicitApprovals()`. - */ - event AllExplicitApprovalsRevoked(address indexed owner); - - /** - * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are - * revoked for the specific token. - * @param owner MUST be `ownerOf(tokenId)` as per ERC721; in the case of revocation due to transfer, this MUST be - * the `from` address expected to be emitted in the respective `ERC721.Transfer()` event. - */ - event AllExplicitApprovalsRevoked( - address indexed owner, - uint256 indexed tokenId - ); - - /** - * @notice Approves the operator to manage the asset on behalf of its owner. - * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner. - * @dev Approvals set via this method MUST be revoked upon transfer of the token to a new owner; equivalent to - * calling `revokeAllExplicitApprovals(tokenId)`, including associated events. - * @dev MUST emit `ApprovalFor(operator, tokenId, approved)`. - * @dev MUST NOT have an effect on any standard ERC721 approval setters / getters. - */ - function setExplicitApproval( - address operator, - uint256 tokenId, - bool approved - ) external; - - /** - * @notice Approves the operator to manage the token(s) on behalf of their owner. - * @dev MUST be equivalent to calling `setExplicitApprovalFor(operator, tokenId, approved)` for each `tokenId` in - * the array. - */ - function setExplicitApproval( - address operator, - uint256[] memory tokenIds, - bool approved - ) external; - - /** - * @notice Revokes all explicit approvals granted by `msg.sender`. - * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender)`. - */ - function revokeAllExplicitApprovals() external; - - /** - * @notice Revokes all excplicit approvals granted for the specified token. - * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner. - * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender, tokenId)`. - */ - function revokeAllExplicitApprovals(uint256 tokenId) external; - - /** - * @notice Query whether an address is an approved operator for a token. - */ - function isExplicitlyApprovedFor(address operator, uint256 tokenId) - external - view - returns (bool); -} - -interface IERC6464AnyApproval is ERC721 { - /** - * @notice Returns true if any of the following criteria are met: - * 1. `isExplicitlyApprovedFor(operator, tokenId) == true`; OR - * 2. `isApprovedForAll(ownerOf(tokenId), operator) == true`; OR - * 3. `getApproved(tokenId) == operator`. - * @dev The criteria MUST be extended if other mechanism(s) for approving operators are introduced. The criteria - * MUST include all approval approaches. - */ - function isApprovedFor(address operator, uint256 tokenId) - external - view - returns (bool); -} -``` - -## Rationale - -### Draft notes to be expanded upon - -1. Approvals granted via the newly introduced methods are called *explicit* as a means of easily distinguishing them from those granted via the standard `ERC721.approve()` and `ERC721.setApprovalForAll()` functions. However they follow the same intent: authorising operators to act on the owner's behalf. -2. Abstracting `isApprovedFor()` into `IERC6464AnyApproval` interface, as against keeping it in `IERC6464` allows for modularity of plain `IERC6464` implementations while also standardising the interface for checking approvals when interfacing with specific implementations and any future approval EIPs. -3. Inclusion of an indexed owner address in `AllExplicitApprovalsRevoked(address,uint256)` assists off-chain indexing of existing approvals. -4. Re `IERC6464AnyApproval`: With an increasing number of approval mechanisms it becomes cumbersome for marketplaces to integrate with them since they have to query multiple interfaces to check if they are approved to manage tokens. This provides a streamlined interface, intended to simplify data ingestion for them. - - - -## Backwards Compatibility - -This extension was written to allow for the smallest change possible to the original ERC-721 spec while still providing a mechanism to grant, revoke and track approvals of multiple operators on a per-token basis. - -Extended contracts remain fully compatible with all existing platforms. - -**Note** the `Security Considerations` sub-section on `Other risks` regarding interplay of approval types. - -## Reference Implementation - -TODO: add internal link to assets directory when the implementation is in place. - -An efficient mechanism for broad revocation of approvals via incrementing nonces is included. - -## Security Considerations - -### Threat model - -### Mitigations - -### Other risks - -TODO: Interplay with `setApprovalForAll()`. - - - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6464.md diff --git a/EIPS/eip-6492.md b/EIPS/eip-6492.md index 6a136b6fec6332..248ba2389409f7 100644 --- a/EIPS/eip-6492.md +++ b/EIPS/eip-6492.md @@ -1,254 +1 @@ ---- -eip: 6492 -title: Signature Validation for Predeploy Contracts -description: A way to verify a signature when the account is a smart contract that has not been deployed yet -author: Ivo Georgiev (@Ivshti), Agustin Aguilar (@Agusx1211) -discussions-to: https://ethereum-magicians.org/t/eip-6492-signature-validation-for-pre-deploy-contracts/12903 -status: Final -type: Standards Track -category: ERC -created: 2023-02-10 -requires: 1271 ---- - -## Abstract - -Contracts can sign verifiable messages via [ERC-1271](./eip-1271.md). - -However, if the contract is not deployed yet, [ERC-1271](./eip-1271.md) verification is impossible, as you can't call the `isValidSignature` function on said contract. - -We propose a standard way for any contract or off-chain actor to verify whether a signature on behalf of a given counterfactual contract (that is not deployed yet) is valid. This standard way extends [ERC-1271](./eip-1271.md). - -## Motivation - -With the rising popularity of account abstraction, we often find that the best user experience for contract wallets is to defer contract deployment until the first user transaction, therefore not burdening the user with an additional deploy step before they can use their account. However, at the same time, many dApps expect signatures, not only for interactions, but also just for logging in. - -As such, contract wallets have been limited in their ability to sign messages before their de-facto deployment, which is often done on the first transaction. - -Furthermore, not being able to sign messages from counterfactual contracts has always been a limitation of [ERC-1271](./eip-1271.md). - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -The words "validation" and "verification" are used interchangeably. - -Quoting [ERC-1271](./eip-1271.md), -> `isValidSignature` can call arbitrary methods to validate a given signature, which could be context dependent (e.g. time based or state based), EOA dependent (e.g. signers authorization level within smart wallet), signature scheme Dependent (e.g. ECDSA, multisig, BLS), etc. -> -> This function should be implemented by contracts which desire to sign messages (e.g. smart contract wallets, DAOs, multisignature wallets, etc.) Applications wanting to support contract signatures should call this method if the signer is a contract. - - -We use the same `isValidSignature` function, but we add a new wrapper signature format, that signing contracts MAY use before they're deployed, in order to allow support for verification. - -The signature verifier MUST perform a contract deployment before attempting to call `isValidSignature` if the wrapper signature format is detected. - -The wrapper format is detected by checking if the signature ends in `magicBytes`, which MUST be defined as `0x6492649264926492649264926492649264926492649264926492649264926492`. - -It is RECOMMENDED to use this ERC with CREATE2 contracts, as their deploy address is always predictable. - -### Signer side - -The signing contract will normally be a contract wallet, but it could be any contract that implements [ERC-1271](./eip-1271.md) and is deployed counterfactually. - -- If the contract is deployed, produce a normal [ERC-1271](./eip-1271.md) signature -- If the contract is not deployed yet, wrap the signature as follows: `concat(abi.encode((create2Factory, factoryCalldata, originalERC1271Signature), (address, bytes, bytes)), magicBytes)` -- If the contract is deployed but not ready to verify using [ERC-1271](./eip-1271.md), wrap the signature as follows: `concat(abi.encode((prepareTo, prepareData, originalERC1271Signature), (address, bytes, bytes)), magicBytes)`; `prepareTo` and `prepareData` must contain the necessary transaction that will make the contract ready to verify using [ERC-1271](./eip-1271.md) (e.g. a call to `migrate` or `update`) - -Note that we're passing `factoryCalldata` instead of `salt` and `bytecode`. We do this in order to make verification compliant with any factory interface. We do not need to calculate the address based on `create2Factory`/`salt`/`bytecode`, because [ERC-1271](./eip-1271.md) verification presumes we already know the account address we're verifying the signature for. - -### Verifier side - -Full signature verification MUST be performed in the following order: - -- check if the signature ends with magic bytes, in which case do an `eth_call` to a multicall contract that will call the factory first with the `factoryCalldata` and deploy the contract if it isn't already deployed; Then, call `contract.isValidSignature` as usual with the unwrapped signature -- check if there's contract code at the address. If so perform [ERC-1271](./eip-1271.md) verification as usual by invoking `isValidSignature` -- if the [ERC-1271](./eip-1271.md) verification fails, and the deploy call to the `factory` was skipped due to the wallet already having code, execute the `factoryCalldata` transaction and try `isValidSignature` again -- if there is no contract code at the address, try `ecrecover` verification - -## Rationale - -We believe that wrapping the signature in a way that allows to pass the deploy data is the only clean way to implement this, as it's completely contract agnostic, but also easy to verify. - -The wrapper format ends in `magicBytes`, which ends with a `0x92`, which makes it is impossible for it to collide with a valid `ecrecover` signature if packed in the `r,s,v` format, as `0x92` is not a valid value for `v`. To avoid collisions with normal [ERC-1271](./eip-1271.md), `magicBytes` itself is also quite long (`bytes32`). - -The order to ensure correct verification is based on the following rules: - -- checking for `magicBytes` MUST happen before the usual [ERC-1271](./eip-1271.md) check in order to allow counterfactual signatures to be valid even after contract deployment -- checking for `magicBytes` MUST happen before `ecrecover` in order to avoid trying to verify a counterfactual contract signature via `ecrecover` if such is clearly identifiable -- checking `ecrecover` MUST NOT happen before [ERC-1271](./eip-1271.md) verification, because a contract may use a signature format that also happens to be a valid `ecrecover` signature for an EOA with a different address. One such example is a contract that's a wallet controlled by said EOA. - -We can't determine the reason why a signature was encoded with a "deploy prefix" when the corresponding wallet already has code. It could be due to the signature being created before the contract was deployed, or it could be because the contract was deployed but not ready to verify signatures yet. As such, we need to try both options. - -## Backwards Compatibility - -This ERC is backward compatible with previous work on signature validation, including [ERC-1271](./eip-1271.md) and allows for easy verification of all signature types, including EOA signatures and typed data ([EIP-712](./eip-712.md)). - -### Using [ERC-6492](./eip-6492.md) for regular contract signatures - -The wrapper format described in this ERC can be used for all contract signatures, instead of plain [ERC-1271](./eip-1271.md). This provides several advantages: - -- allows quick recognition of the signature type: thanks to the magic bytes, you can immediately know whether the signature is a contract signature without checking the blockchain -- allows recovery of address: you can get the address only from the signature using `create2Factory` and `factoryCalldata`, just like `ecrecover` - -## Reference Implementation - -Below you can find an implementation of a universal verification contract that can be used both on-chain and off-chain, intended to be deployed as a singleton. It can validate signatures signed with this ERC, [ERC-1271](./eip-1271.md) and traditional `ecrecover`. [EIP-712](./eip-712.md) is also supported by extension, as we validate the final digest (`_hash`). - -```solidity -// As per ERC-1271 -interface IERC1271Wallet { - function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue); -} - -error ERC1271Revert(bytes error); -error ERC6492DeployFailed(bytes error); - -contract UniversalSigValidator { - bytes32 private constant ERC6492_DETECTION_SUFFIX = 0x6492649264926492649264926492649264926492649264926492649264926492; - bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e; - - function isValidSigImpl( - address _signer, - bytes32 _hash, - bytes calldata _signature, - bool allowSideEffects, - bool tryPrepare - ) public returns (bool) { - uint contractCodeLen = address(_signer).code.length; - bytes memory sigToValidate; - // The order here is strictly defined in https://eips.ethereum.org/EIPS/eip-6492 - // - ERC-6492 suffix check and verification first, while being permissive in case the contract is already deployed; if the contract is deployed we will check the sig against the deployed version, this allows 6492 signatures to still be validated while taking into account potential key rotation - // - ERC-1271 verification if there's contract code - // - finally, ecrecover - bool isCounterfactual = bytes32(_signature[_signature.length-32:_signature.length]) == ERC6492_DETECTION_SUFFIX; - if (isCounterfactual) { - address create2Factory; - bytes memory factoryCalldata; - (create2Factory, factoryCalldata, sigToValidate) = abi.decode(_signature[0:_signature.length-32], (address, bytes, bytes)); - - if (contractCodeLen == 0 || tryPrepare) { - (bool success, bytes memory err) = create2Factory.call(factoryCalldata); - if (!success) revert ERC6492DeployFailed(err); - } - } else { - sigToValidate = _signature; - } - - // Try ERC-1271 verification - if (isCounterfactual || contractCodeLen > 0) { - try IERC1271Wallet(_signer).isValidSignature(_hash, sigToValidate) returns (bytes4 magicValue) { - bool isValid = magicValue == ERC1271_SUCCESS; - - // retry, but this time assume the prefix is a prepare call - if (!isValid && !tryPrepare && contractCodeLen > 0) { - return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true); - } - - if (contractCodeLen == 0 && isCounterfactual && !allowSideEffects) { - // if the call had side effects we need to return the - // result using a `revert` (to undo the state changes) - assembly { - mstore(0, isValid) - revert(31, 1) - } - } - - return isValid; - } catch (bytes memory err) { - // retry, but this time assume the prefix is a prepare call - if (!tryPrepare && contractCodeLen > 0) { - return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true); - } - - revert ERC1271Revert(err); - } - } - - // ecrecover verification - require(_signature.length == 65, 'SignatureValidator#recoverSigner: invalid signature length'); - bytes32 r = bytes32(_signature[0:32]); - bytes32 s = bytes32(_signature[32:64]); - uint8 v = uint8(_signature[64]); - if (v != 27 && v != 28) { - revert('SignatureValidator: invalid signature v value'); - } - return ecrecover(_hash, v, r, s) == _signer; - } - - function isValidSigWithSideEffects(address _signer, bytes32 _hash, bytes calldata _signature) - external returns (bool) - { - return this.isValidSigImpl(_signer, _hash, _signature, true, false); - } - - function isValidSig(address _signer, bytes32 _hash, bytes calldata _signature) - external returns (bool) - { - try this.isValidSigImpl(_signer, _hash, _signature, false, false) returns (bool isValid) { return isValid; } - catch (bytes memory error) { - // in order to avoid side effects from the contract getting deployed, the entire call will revert with a single byte result - uint len = error.length; - if (len == 1) return error[0] == 0x01; - // all other errors are simply forwarded, but in custom formats so that nothing else can revert with a single byte in the call - else assembly { revert(error, len) } - } - } -} - -// this is a helper so we can perform validation in a single eth_call without pre-deploying a singleton -contract ValidateSigOffchain { - constructor (address _signer, bytes32 _hash, bytes memory _signature) { - UniversalSigValidator validator = new UniversalSigValidator(); - bool isValidSig = validator.isValidSigWithSideEffects(_signer, _hash, _signature); - assembly { - mstore(0, isValidSig) - return(31, 1) - } - } -} -``` - -### On-chain validation - -For on-chain validation, you could use two separate methods: - -- `UniversalSigValidator.isValidSig(_signer, _hash, _signature)`: returns a bool of whether the signature is valid or not; this is reentrancy-safe -- `UniversalSigValidator.isValidSigWithSideEffects(_signer, _hash, _signature)`: this is equivalent to the former - it is not reentrancy-safe but it is more gas-efficient in certain cases - -Both methods may revert if the underlying calls revert. - -### Off-chain validation - -The `ValidateSigOffchain` helper allows you to perform the universal validation in one `eth_call`, without any pre-deployed contracts. - -Here's example of how to do this with the `ethers` library: - -```javascript -const isValidSignature = '0x01' === await provider.call({ - data: ethers.utils.concat([ - validateSigOffchainBytecode, - (new ethers.utils.AbiCoder()).encode(['address', 'bytes32', 'bytes'], [signer, hash, signature]) - ]) -}) -``` - -You may also use a library to perform the universal signature validation, such as Ambire's `signature-validator`. - -## Security Considerations - -The same considerations as [ERC-1271](./eip-1271.md) apply. - -However, deploying a contract requires a `CALL` rather than a `STATICCALL`, which introduces reentrancy concerns. This is mitigated in the reference implementation by having the validation method always revert if there are side-effects, and capturing its actual result from the revert data. For use cases where reentrancy is not a concern, we have provided the `isValidSigWithSideEffects` method. - -Furthermore, it is likely that this ERC will be more frequently used for off-chain validation, as in many cases, validating a signature on-chain presumes the wallet has been already deployed. - -One out-of-scope security consideration worth mentioning is whether the contract is going to be set-up with the correct permissions at deploy time, in order to allow for meaningful signature verification. By design, this is up to the implementation, but it's worth noting that thanks to how CREATE2 works, changing the bytecode or contructor callcode in the signature will not allow you to escalate permissions as it will change the deploy address and therefore make verification fail. - -It must be noted that contract accounts can dynamically change their methods of authentication. This issue is mitigated by design in this EIP - even when validating counterfactual signatures, if the contract is already deployed, we will still call it, checking against the current live version of the contract. - -As per usual with signatures, replay protection should be implemented in most use cases. This proposal adds an extra dimension to this, because it may be possible to validate a signature that has been rendered invalid (by changing the authorized keys) on a different network as long as 1) the signature was valid at the time of deployment 2) the wallet can be deployed with the same factory address/bytecode on this different network. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6492.md diff --git a/EIPS/eip-6506.md b/EIPS/eip-6506.md index d92c7e8536c310..7c35ed9160202c 100644 --- a/EIPS/eip-6506.md +++ b/EIPS/eip-6506.md @@ -1,485 +1 @@ ---- -eip: 6506 -title: P2P Escrowed Governance Incentives -description: Interface for building contracts that escrow funds based on an account taking action in a DAO -author: Josh Weintraub (@jhweintraub) -discussions-to: https://ethereum-magicians.org/t/escrowed-and-private-bribes-for-generalized-dao-voting/12694 -status: Stagnant -type: Standards Track -category: ERC -created: 2023-02-15 ---- - - -## Abstract - -The following EIP defines the interface for a contract that facilitates the exchange of a governance-incentive for users to vote in a designated direction on a DAO-proposal while escrowing funds until the vote can be verified. - -## Motivation - -While a ton of effort has gone into building bribe systems for DAOs like Curve, Frax, Convex, etc., not a lot of focus has been put on how bribes on other, more general DAO votes, may affect outcomes. Bribes are a lucrative market on many popular DAO’s, and it stands to reason that people are willing to accept them for voting on other proposals, especially if they have no personal stake in the outcome. There are however, problems with current systems: - -1. Current bribe schemes for votes based on pro-rata distribution are economically innefficient and result in worse outcomes for voters. For systems like Votium or Hidden-Hand, If Alice votes on a proposal with the expectation of receiving $10 in bribes, they can just be backrun by a larger voter, diluting their share of the pool. It may no longer be economical to make the decision they did. Using an OTC mechanisms is more efficient because the amount is “locked in” when the bribe is made and the recipient has much more concrete assurances on which to base their decision. These protocols are also centralized, relying on a central authority to accept and redistribute rewards fairly. Whenever possible, centralization should be avoided. - -2. The lack of an existing standard means that parties are relying entirely on trust in one-another to obey. Bob has to trust Alice to pay out and Alice has to trust Bob to vote. Even if the two of them were to use an escrow contract, it may have flaws like relying on a trusted third-party, or simply that it is outside the technical reach of both parties. - -3. There are no mechanisms for creating transparency into the collusion of actors. Users colluding off-chain to sway the vote of a large token-holder creates opaque outcomes with no accountability since everything happens off-chain. - -4. For actors that wish to solicit incentives for their vote, this may require either active management, or the doxxing of their identify/psuedononymous identifier. A user who wishes to negotiate would need to provide a way for incentivizers to contact them, engage in a negotiation process, write and deploy escrow contracts, vote, and then claim their reward. This is a lengthy and involved process that requires active management and communication. This creates a limit on who is able to solicit these incentives, and leads to the centralization of profit towards the few who can sustain this process at length. - -5. Bribe Revenue as subsidies. As Vitalik wrote in a 2019 article, *On Collusion*, a potential solution would be a token that requires voters for a proposal to purchase the governance-token if the proposal-passes, subsidizing the cost of a bad decision for everyone else. If the revenue generated from these incentives is used (at least partly) to directly buy back those tokens by the treasury, then you get a similar outcome. The impact of a bad proposal being passed via-bribing is subsidized for everyone who didn't vote for it by having some value returned to token-holders. This not only makes malicious bribes more costly, as it has to offset the value accrued via buyback, but also means higher profits for recipients. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -The key words "BRIBE" and "INCENTIVE" are to be interpreted as the transfer of a digital-asset(s) from user A to user B in exchange for an assurance that user B will vote in a specific-direction, on a specific proposal, for a specified-DAO. If user B does not honor the arrangement, the digital-asset(s) will be returned to user A. - -The key words "BRIBER", "INCENTIVIZER", and "SENDER" shall refer to the user A offering monetrary compensation to user B. "RECIPIENT", "VOTER", and "INCENTIVIZEE" herein refer to user B whom shall formally receive their compensation upon the termination of the agreement. - -The key word "VOTE" shall be interpreted as the casting of a ballot in any kind of governance system which uses accounts as a form of permissions. - -Contracts wishing to implement such a standard must implement the following interface - -```solidity -interface IEscrowedGovIncentive { - - struct incentive { - address incentiveToken; - address incentivizer; - address recipient; - uint amount; - uint256 proposalId; - bytes32 direction; //the keccack256 of the vote direction - uint96 deadline; - uint96 timestamp; - bool claimed; - } - - event incentiveSent(address indexed incentivizer, address indexed token, uint256 indexed amount, address recipient, bytes data); - - event incentiveReclaimed(address incentivizer, address indexed recipient, address indexed token, uint256 indexed amount, bytes data); - - event modifiedClaimer(address recipient, address claimer, bool direction); - - event incentiveClaimed(address indexed incentivizer, address voter, bytes32 incentiveId, bytes proofData); - - event disputeInitiated(bytes32 indexed incentiveId, address indexed plaintiff, address indexed defendant); - - event disputeResolved(bytes32 indexed incentive, address indexed plaintiff, address indexed defendant, bool dismissed); - - - //Core mechanism - function incentivize(bytes32 incentiveId, bytes memory incentiveInfo) external payable; - - function claimIncentive(bytes32 incentiveId, bytes memory reveal, address payable recipient) external; - - function reclaimIncentive(bytes32 incentiveId, bytes memory reveal) external; - - function verifyVote(bytes32 incentive, bytes memory voteInfo) external view returns (bool isVerifiable, bytes proofData); - - function modifyClaimer(address claimer, bool designation) external; - - //Dispute Mechanism - function beginDispute(bytes32 incentiveId, bytes memory disputeInfo) external payable; - - function resolveDispute(bytes32 incentiveId, bytes memory disputeResolutionInfo) external returns (bool isDismissed); - -} -``` - -### Optional Implementation Details - -Below are three potential implementation examples of the above system for different aspects. - -#### *Complete Transparency* - -In this version all information about the vote direction, the amount, and the recipient are public at all times. Information is passed as calldata in plaintext and stored/emitted as such. - -#### *Opacity until Completion (OUC)* - -In this model, the recipient, the direction, and the amount are kept secret until the incentive is claimed. In this model, the data is committed to, and an encrypted version is passed as calldata. This data can be encrypted with the recipient's public-key. It should be emitted as such which can then be decrypted off-chain by the recipient and used to make a determination on whether to oblige. In this model to ensure the privacy of transferring funds into escrow, the incentivizer could use methods such as deterministic-address-generation with the create2 opcode. - -Upon the claiming of the bribe the recipient would simply open the committment, which would then be checked on-chain and funds released. - -#### *Compatibility with Off-Chain Voting* - -Many DAO's operate off-chain, typically through voting platforms like snapshot. This sytem does allow for such compatability using known signature data. Consider the following example - -1. User A commits an incentive to user B to vote on snapshot. User B votes. -2. Once the deadline has passed, a challenge window is initiated. The incentivizer has a predetermined window to demonstrate that the bribe was not honored. This can be done by simply passing to the contract a signature signed by User B voting in the opposite direction of the bribe. If the signature can be verified, then the arrangement was not honored and funds can be safely released back to user A. -3. If the challenge window concludes without A being able produce proof of noncompliance, then B is able to claim the reward. If B voted inline with the incentive, A will not be able to produce a valid signature of noncompliance. The challenge window with A demonstrating noncompliance is necesarry, because otherwise B could simply sign a message and not broadcast it, allowing them to claim the reward without voting. -4. In the event that B does NOT vote at all, then a special challenge period may be entered. Since B did not vote at all, A would not be able to produce the requisite proof, but B would still be able to claim the reward without complying. In this event, user A would have the option to enter a special dispute period. The details of this are determined by the contract implementation. This can include resolution by a trusted third-party, or other methods. An example includes using a merkle-root to show that B was not in the list of voters at the conclusion of the proposal. It should be considered making A present a - - -### Methods - -While this EIP defines a struct *incentive*, `bytes memory` should be used whenever possible. Given as each DAO will have its own implementation details, interfaces, and signature data, this should then be decoded using `abi.decode()` and interpreted according to those known specifications. - -#### `incentivize` - -The function where an incentivizer should commit to the details of their incentive. The commitment value can be calculated off-chain or calculated on-chain in a full transparency system. The function should take the input data from `incentiveInfo` and create store a new `incentive` object in the mapping incentives. If OUC is enabled, then only incentivizer and timestamp information need be public, everything else should be left as zero. - -Function should account for fees taken from user at deposit. If fees are present, then `incentivize` should take them up front. This is to ensure that the amount quoted to a recipient is *at least* as much as they would receive. - -MUST emit the `incentiveSent` event - -```yaml -- name: incentivize - type: function - stateMutability: payable - - inputs: - - name: incentiveId - type: bytes32 - - name: incentiveInfo - type: bytes memory -``` - -#### `claimIncentive` - -Should be used by the intended recipient of a previously-committed incentive. - -MUST revert if `msg.sender != original_recipient` and `!allowedClaimer[original_recipient][msg.sender]` - -MUST revert if the data provided in `reveal` does not match the data committed to by `incentiveId`. - -MUST revert if all funds committed to cannot be properly sent to `recipient` at conclusion of the function. If fees are present, then additional funds should be present at deposit to ensure that *at least* the amount committed to is sent to the user. This however, **DOES NOT** apply to any fees which may be taken by an approved claimer. - -Ex: Alice commits to Bob an incentive 100 USDC. Bob has approved Eve to claim on his behalf in exchange for 5% of net value. Function should check that amount paid to Bob and Eve is `>=100 USDC` but **NOT** that Bob himself receives `>=100 USDC` - -MUST revert if the voting direction of the original recipient cannot be verified as being in line with the intended direction of `incentiveId`, and no dispute resolution process is defined. - -MUST revert if the specified incentive has a pending dispute. - -If verification is successful then funds should be sent to `recipient`. - -MUST emit the `incentiveClaimed` event if function does not revert. - -```yaml -- name: claimIncentive - type: function - stateMutability: nonpayable - - inputs: - - name: incentiveId - type: bytes32 - - name: reveal - type: bytes memory - - name: recipient - type: address payable -``` - -#### `reclaimIncentive` - - Function that should be invoked by the initial sender of `incentiveId` in the event that `recipient` did not vote in accordance with the incentive's `direction`. Function should return the funds initially committed to by `incentiveId` to `incentivizer` - - MUST revert if all of the funds committed to cannot be returned to the incentivizer. - - MUST revert if the function cannot successfully verify the validity of `msg.sender` claim of non-compliance. - - MUST emit the event `incentiveReclaimed` if verification is successful. If proof can be retrieved on-chain, then the `proof` parameter may be left empty. - - MUST revert if the specified incentive has a pending dispute. - - If fees are taken, then all funds including any prepaid fees committed to should be returned to the `incentivizer`. - - ```yaml -- name: reclaimIncentive - type: function - stateMutability: nonpayable - - inputs: - - name: incentiveId - type: bytes32 - - name: reveal - type: bytes memory - ``` - -#### `verifyVote` - - `function verifyVote(bytes32 incentive, bytes memory voteInfo) public view returns (bool isVerifiable);` - - Function used to determine if the voter for `incentive` should receive the incentive originally committed to. - - Functions may use whatever scheme they like to determine this information. Necesarry data should be encoded and passed through `voteInfo`. - - MUST return `false` if `voteInfo` indicates that `recipient` did not vote in the direction committed to by `incentive`, and true otherwise. - -```yaml -- name: verifyVote - type: function - stateMutability: view - - inputs: - - name: incentiveId - type: bytes32 - - name: voteInfo - type: bytes memory - - outputs: - - name: isVerified - type: bool - - name: proofData - type: bytes memory -``` - -#### `modifyClaimer` - -Function changing the designation of an address as being approved to claim a bribe on behalf of another user. Only an approved claimer should be able to claim the incentive on behalf of the user which approved them. - -```yaml -- name: modifyClaimer - type: function - stateMutability: nonpayable - - inputs: - - name: claimer - type: address - - name: designation - type: bool - -``` - -#### `beginDispute` - -A function used to initiate the resolution of an incentive through an optional dispute-mechanism. At the discretion of the developers, and based on the specifics of the vote-verification mechanism in which a voting direction cannot be conclusively decided, the developers may opt for an additional mechanism to resolve dispute between parties. This may include third-party intervention, additional cryptographic evidence, etc. needed to determine whether to pay out rewards to `recipient` or return them to the `incentivizer` - -Potential Examples requiring additional dispute mechanisms: - - 1. Requiring a trusted third-party to resolve disputes. - 2. The recipient did not vote in an off-chain proposal, and additional off-chain information is needed to confirm. - 3. An additional unlocking mechanism is required to access previously deposited funds. - - -Dispute mechanisms may optionally choose to require a bond from the filer to prevent frivolous filings, to be returned to them on successful resolution of the dispute in their favor. - -Must emit the event `disputeInitiated` - -Once a dispute for a given incentive has been filed, neither the `incentivizer` nor `recipient` should be able to withdraw funds until completed. - - -```yaml -- name: beginDispute - type: function - stateMutability: payable - - inputs: - - name: incentiveId - type: bytes32 - - name: disputeInfo - type: bytes memory -``` - -#### `resolveDispute` - -A function which is used to resolve pending disputes over `incentiveId`. The exact mechanism shall be specified by the developers. - -MUST return false, and be *"dismissed"*, if the mechanisms resolves the dispute in favor of the defendant `(recipient)`, by showing they did honor the incentive of `incentiveId`. If the dispute is *"confirmed"*, then the function should return true. - -MUST transfer funds committed to by `incentivizer` to `recipient` if dispute is `dismissed` and return `funds + fee + bond` to the `plaintiff`. If dismissed, the distribution of the bond shall be at the discretion of the developers. This may including burning, awarding to the defendant, or donating to a community treasury. - -MUST emit the event `disputeResolved` on successful resolution. - -```yaml -- name: resolveDispute - type: function - stateMutability: nonPayable - - inputs: - - name: incentiveId - type: bytes32 - - name: disputeResolutionInfo - type: bytes memory - - outputs: - - name isDismissed - type: bool -``` - -### Events - -#### `incentiveSent` - -`incentivizer` has bribed `recipient` `amount` of `token` for some information. - -If system is private then recipient, amount, and `token` may be left as zero. - -```yaml -- name: incentiveSent - type: event - - inputs: - - name incentivizer - indexed: true - type: address - - name: token - indexed: true - type: address - - name: amount - indexed: true - type: uint256 - - name: recipient - indexed: true - type: address -``` - - -#### `incentiveClaimed` - - `recipient` claimed an incentive `amount` of `token` and any other data relevant. - -```yaml -- name: incentiveClaimed - - type: event - - inputs: - - name: recipient - indexed: true - type: address - - name: token - indexed: true - type: address - - name: amount - indexed: true - type: uint256 - - name: data - indexed: false - type: bytes -``` - -#### `modifiedClaimer` - - A new `claimer` was either whitelisted by `recipient` or blacklisted. - -```yaml -- name: modifiedClaimer - type: event - - inputs: - - name: recipient - indexed: false - type: address - - name: claimer - indexed: false - type: address - - name: direction - indexed: false - type: bool -``` - -#### `incentiveReclaimed` - - An `incentivizer` is reclaiming `incentiveId`, and outing the noncompliance of `voter` - -```yaml -- name: incentiveReclaimed - type: event - - inputs: - - name: incentivizer - indexed: true - type: address - - name: voter - indexed: true - type: address - - name: incentiveId - indexed: false - type: bytes32 - - name: proofData - indexed: false - type: bytes -``` - -#### `disputeInitiated` - - `incentivizer` has initiated a dispute with `plaintiff` over `incentiveId` - -```yaml -- name: disputeInitiated - type: event - - inputs: - - name: incentiveId - indexed: true - type: bytes32 - - name: plaintiff - indexed: true - type: address - - name: defendant - indexed: true - type: address -``` - -#### `disputeResolved` - - The dispute over `incentiveId` has been resolved, either `dismissed` in favor of `defendant` or resolved in favor of the `plaintiff` - -```yaml -- name: disputeResolved - type: event - - inputs: - - name: incentiveId - indexed: false - type: bytes32 - - name: plaintiff - indexed: true - type: address - - name: defendant - indexed: true - type: address - - name: dismissed - indexed: true - type: bool - -``` - -## Rationale - -This design was motivated by a few factors: - -1. The issue of offering incentives for votes is an inevitability. There is no mechanism that can prevent users from colluding off-chain to vote a certain direction, and with enough obfuscation, can be completely hidden from the community's view. The solution is therefore to realign the incentives of these actors in a way that both creates transparency, while allowing for the decentralization of bribe-revenue. Flashbots is a relevant example. Since MEV could not be prevented, the solution was to make it more fairly distributed by incentivizing miners to use Flashbots-Geth with profits. Using an OTC market structure would have the same effect, allowing anyone to reap the benefits of a potential incentive while also creating a more efficient marketplace. - -2. Injecting transparency about whom is bribing whom for what increases both fairness and profitability. This makes it possible for the community to organize around potential solutions. Ex: Alice pays Bob $10 for his 1k votes in the DAO. This is now known on-chain and next time someone who cares about the outcome can offer Bob $11 for the votes. This maximizes profit to the recipient. - -**Implementations should operate similar to the following example:** - -1. Alice wants to give bob $10 to vote YES on DAO proposal #420. She wants an assurance he will do it and gets the money back if he doesn’t - -2. It should work as an escrow service for both on-chain and snapshot based voting, releasing funds only after the vote has concluded, and it can be verified the recipient voted in line with the vote. It should be done without requiring a trusted third-party to escrow and release the funds themselves. - -3. This EIP makes no discernment about the nature in which this information is relayed to the recipient. Implementation details are at the discretion of the protocol. This includes the optional decisions to enable privacy for both the recipient and the amount. Information on how this can be implemented is below. Once the vote has occured, then the contents of the bribe can be claimed, pending verification. This verification should satisfy both soundness and completeness, that only after the user can show they did vote in line with the incentive do they receive the funds, and that such proof cannot be forged or misleading in any way. - - -**Factors to consider** - -1. To remedy the problem of diluted rewards, the system uses a simple hash-based commitment scheme. When an incentive is sent, its data is committed to, and revealed when withdrawn. - -2. Once a bribe is committed to, it cannot be withdrawn until after the voting period for the proposal has concluded. This is to ensure the legitimacy of the escrow, so that user A cannot withdraw the bribe after B has voted, but before they can claim the reward. - - -### Potential Ethical Issues - -Potential ethical issues have been raised about the prospect of potentially encouraging users to accept monetary payment for their vote. This is the wrong frame of reference. The question is not whether it is ethical to encourage users to send/solicit, but rather the consequences of doing nothing. Returning to the flashbots example, the question is not whether MEV is ethical, but reprecussions of allowing it to flourish without pushback. - -If nothing is done, the following outcomes are possible: - -1. Flywheel Effect - Only dedicated and financially endowed holders will solicit incentives with impunity. This centralization of profit allows them to purchase more voting-rights, increasing power and so on until they have accumulated a critical mass, exerting potentially harmful influence over operations. This can range anywhere from minor operational decisions, to votes over treasury resolution. - -2. Lack of transparency - Decisionmaking will occur behind closed doors as the true intentions of voters is unclear, and votes that should pass may fail, or vice-versa. The will of the community will not be honored. - -## Backwards Compatibility - -No backward compatibility issues found. - -## Security Considerations - -This standard is intended to work with existing governance systems. Any potential issue with existing governance may represent a potential attack on this as well. This includes voting-weight manipulation, vote forgery, verification discrepancies etc. All systems in which this EIP is integrated with should be properly audited for maximum security, as any issues may result in improper distribution of these governance incentives. - -Potential implementations of this system may rely on complex cryptographic operations as well. This may include proper implementation of digitial-signatures to prevent replay attacks, or correctness requirements of SNARK proofs. These features may be **non-trivial** and thus require special care to ensure they are implemented and configured securely, otherwise features like confidentiality may be violated. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6506.md diff --git a/EIPS/eip-6538.md b/EIPS/eip-6538.md index 6f1005bf1c4e42..d0e86535452b27 100644 --- a/EIPS/eip-6538.md +++ b/EIPS/eip-6538.md @@ -1,117 +1 @@ ---- -eip: 6538 -title: Stealth Meta-Address Registry -description: A registry to map addresses to stealth meta-addresses -author: Matt Solomon (@mds1), Toni Wahrstätter (@nerolation), Ben DiFrancesco (@apbendi), Vitalik Buterin (@vbuterin) -discussions-to: https://ethereum-magicians.org/t/stealth-meta-address-registry/12888 -status: Stagnant -type: Standards Track -category: ERC -created: 2023-01-24 ---- - -## Abstract - -This specification defines a standardized way of storing and retrieving an entity's stealth meta-address, by extending [ERC-5564](./eip-5564.md). - -## Motivation - -The standardization of stealth address generation holds the potential to greatly enhance the privacy capabilities of Ethereum by enabling the recipient of a transfer to remain anonymous when receiving an asset. By introducing a central smart contract for users to store their stealth meta-addresses, EOAs and contracts can programmatically engage in stealth interactions using a variety of stealth address scehemes. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -This contract defines an `ERC5564Registry` that stores the stealth meta-address for entities. These entities may be identified by an address, ENS name, or other identifier. This MUST be a singleton contract, with one instance per chain. - -The contract is specified below. A one byte integer is used to identify the stealth address scheme. This integer is used to differentiate between different stealth address schemes. A mapping from the scheme ID to it's specification is maintained at [this](../assets/eip-5564/scheme_ids.md) location. - -```solidity -pragma solidity ^0.8.17; - -/// @notice Registry to map an address or other identifier to its stealth meta-address. -contract ERC5564Registry { - /// @dev Emitted when a registrant updates their stealth meta-address. - event StealthMetaAddressSet( - bytes indexed registrant, uint256 indexed scheme, bytes stealthMetaAddress - ); - - /// @notice Maps a registrant's identifier to the scheme to the stealth meta-address. - /// @dev Registrant may be a 160 bit address or other recipient identifier, such as an ENS name. - /// @dev Scheme is an integer identifier for the stealth address scheme. - /// @dev MUST return zero if a registrant has not registered keys for the given inputs. - mapping(bytes => mapping(uint256 => bytes)) public stealthMetaAddressOf; - - /// @notice Sets the caller's stealth meta-address for the given stealth address scheme. - /// @param scheme An integer identifier for the stealth address scheme. - /// @param stealthMetaAddress The stealth meta-address to register. - function registerKeys(uint256 scheme, bytes memory stealthMetaAddress) external { - stealthMetaAddressOf[abi.encode(msg.sender)][scheme] = stealthMetaAddress; - } - - /// @notice Sets the `registrant`s stealth meta-address for the given scheme. - /// @param registrant Recipient identifier, such as an ENS name. - /// @param scheme An integer identifier for the stealth address scheme. - /// @param signature A signature from the `registrant` authorizing the registration. - /// @param stealthMetaAddress The stealth meta-address to register. - /// @dev MUST support both EOA signatures and EIP-1271 signatures. - /// @dev MUST revert if the signature is invalid. - function registerKeysOnBehalf( - address registrant, - uint256 scheme, - bytes memory signature, - bytes memory stealthMetaAddress - ) external { - // TODO If registrant has no code, spit signature into r, s, and v and call `ecrecover`. - // TODO If registrant has code, call `isValidSignature` on the registrant. - } - - /// @notice Sets the `registrant`s stealth meta-address for the given scheme. - /// @param registrant Recipient identifier, such as an ENS name. - /// @param scheme An integer identifier for the stealth address scheme. - /// @param signature A signature from the `registrant` authorizing the registration. - /// @param stealthMetaAddress The stealth meta-address to register. - /// @dev MUST support both EOA signatures and EIP-1271 signatures. - /// @dev MUST revert if the signature is invalid. - function registerKeysOnBehalf( - bytes memory registrant, - uint256 scheme, - bytes memory signature, - bytes memory stealthMetaAddress - ) external { - // TODO How to best generically support any registrant identifier / name - // system without e.g. hardcoding support just for ENS? - } -} -``` - -Deployment is done using the keyless deployment method commonly known as Nick’s method, TODO continue describing this and include transaction data, can base it off the format/description used in [ERC-1820](./eip-1820.md) and [ERC-2470](./eip-2470.md). - -## Rationale - -Having a central smart contract for registering stealth meta-addresses has several benefits: - -1. It guarantees interoperability with other smart contracts, as they can easily retrieve and utilize the registered stealth meta-addresses. This enables applications such as ENS or Gnosis Safe to use that information and integrate stealth addresses into their services. - -2. It ensures that users are not dependent on off-chain sources to retrieve a user's stealth meta-address. - -3. Registration of a stealth meta-address in this contract provides a standard way for users to communicate that they're ready to participate in stealth interactions. - -4. By deploying the registry as a singleton contract, multiple projects can access the same set of stealth meta-addresses, contributing to improved standardization. - -## Backwards Compatibility - -This EIP is fully backward compatible. - -## Reference Implementation - -You can find an implementation of this standard above. - -## Security Considerations - -TODO - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6538.md diff --git a/EIPS/eip-6551.md b/EIPS/eip-6551.md index 7bf3d13f8fba74..05ac3b6fd9c3db 100644 --- a/EIPS/eip-6551.md +++ b/EIPS/eip-6551.md @@ -1,531 +1 @@ ---- -eip: 6551 -title: Non-fungible Token Bound Accounts -description: An interface and registry for smart contract accounts owned by non-fungible tokens -author: Jayden Windle (@jaydenwindle), Benny Giang , Steve Jang, Druzy Downs (@druzydowns), Raymond Huynh (@huynhr), Alanah Lam , Wilkins Chung (@wwhchung) , Paul Sullivan (@sullivph) , Auryn Macmillan (@auryn-macmillan), Jan-Felix Schwarz (@jfschwarz), Anton Bukov (@k06a), Mikhail Melnik (@ZumZoom), Josh Weintraub (@jhweintraub) , Rob Montgomery (@RobAnon) -discussions-to: https://ethereum-magicians.org/t/non-fungible-token-bound-accounts/13030 -status: Review -type: Standards Track -category: ERC -created: 2023-02-23 -requires: 155, 165, 721, 1167, 1271 ---- - -## Abstract - -This proposal defines a system which assigns Ethereum accounts to all non-fungible tokens. These token bound accounts allow NFTs to own assets and interact with applications, without requiring changes to existing smart contracts or infrastructure. - -## Motivation - -The [ERC-721](./eip-721.md) standard enabled an explosion of non-fungible token applications. Some notable use cases have included breedable cats, generative artwork, and exchange liquidity positions. - -However, NFTs cannot act as agents or associate with other on-chain assets. This limitation makes it difficult to represent many real-world non-fungible assets as NFTs. For example: - -- A character in a role-playing game that accumulates assets and abilities over time based on actions they have taken -- An automobile composed of many fungible and non-fungible components -- An investment portfolio composed of multiple fungible assets -- A punch pass membership card granting access to an establishment and recording a history of past interactions - -This proposal aims to give every NFT the same rights as an Ethereum user. This includes the ability to self-custody assets, execute arbitrary operations, control multiple independent accounts, and use accounts across multiple chains. By doing so, this proposal allows complex real-world assets to be represented as NFTs using a common pattern that mirrors Etherem's existing ownership model. - -This is accomplished by defining a singleton registry which assigns unique, deterministic smart contract account addresses to all existing and future NFTs. Each account is permanently bound to a single NFT, with control of the account granted to the holder of that NFT. - -The pattern defined in this proposal does not require any changes to existing NFT smart contracts. It is also compatible out of the box with nearly all existing infrastructure that supports Ethereum accounts, from on-chain protocols to off-chain indexers. Token bound accounts are compatible with every existing on-chain asset standard, and can be extended to support new asset standards created in the future. - -By giving every NFT the full capabilities of an Ethereum account, this proposal enables many novel use cases for existing and future NFTs. - -## 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 - -The system outlined in this proposal has two main components: - -- A singleton registry for token bound accounts -- A common interface for token bound account implementations - -The following diagram illustrates the relationship between NFTs, NFT holders, token bound accounts, and the Registry: -![](../assets/eip-6551/diagram.png) - -### Registry - -The registry serves as a single entry point for all token bound account address queries. It has two functions: - -- `createAccount` - creates the token bound account for an NFT given an `implementation` address -- `account` - computes the token bound account address for an NFT given an `implementation` address - -The registry SHALL deploy each token bound account as an [ERC-1167](./eip-1167.md) minimal proxy with immutable constant data appended to the bytecode. - -The deployed bytecode of each token bound account SHALL have the following structure: - -``` -ERC-1167 Header (10 bytes) - (20 bytes) -ERC-1167 Footer (15 bytes) - (32 bytes) - (32 bytes) - (32 bytes) - (32 bytes) -``` - -For example, the token bound account with implementation address `0xbebebebebebebebebebebebebebebebebebebebe`, salt `0`, chain ID `1`, token contract `0xcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcf` and token ID `123` would have the following deployed bytecode: - -``` -363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcfcf000000000000000000000000000000000000000000000000000000000000007b -``` - -Each token bound account proxy SHALL delegate execution to a contract that implements the `IERC6551Account` interface. - -The registry contract is permissionless, immutable, and has no owner. The complete source code for the registry can be found in the [Registry Implementation](#registry-implementation) section below. The registry SHALL be deployed at address `TBD` using Nick's Factory (`0x4e59b44847b379578588920cA78FbF26c0B4956C`) with salt `0x6551655165516551655165516551655165516551655165516551655165516551`. - -The registry SHALL deploy all token bound accounts using the `create2` opcode so that each account address is deterministic. Each token bound account address SHALL be derived from the unique combination of its implementation address, token contract address, token ID, [EIP-155](./eip-155.md) chain ID, and salt. - -The registry SHALL implement the following interface: - -```solidity -interface IERC6551Registry { - /** - * @dev The registry SHALL emit the AccountCreated event upon successful account creation - */ - event AccountCreated( - address account, - address indexed implementation, - uint256 chainId, - address indexed tokenContract, - uint256 indexed tokenId, - uint256 salt - ); - - /** - * @dev Creates a token bound account for a non-fungible token. - * - * If account has already been created, returns the account address without calling create2. - * - * If initData is not empty and account has not yet been created, calls account with - * provided initData after creation. - * - * Emits AccountCreated event. - * - * @return the address of the account - */ - function createAccount( - address implementation, - uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt, - bytes calldata initData - ) external returns (address); - - /** - * @dev Returns the computed token bound account address for a non-fungible token - * - * @return The computed address of the token bound account - */ - function account( - address implementation, - uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt - ) external view returns (address); -} -``` - -### Account Interface - -All token bound accounts SHOULD be created via the registry. - -All token bound account implementations MUST implement [ERC-165](./eip-165.md) interface detection. - -All token bound account implementations MUST implement [ERC-1271](./eip-1271.md) signature validation. - -All token bound account implementations MUST implement the following interface: - -```solidity -/// @dev the ERC-165 identifier for this interface is `0x6faff5f1` -interface IERC6551Account { - /** - * @dev Allows the account to receive Ether - * - * Accounts MUST implement a `receive` function. - * - * Accounts MAY perform arbitrary logic to restrict conditions - * under which Ether can be received. - */ - receive() external payable; - - /** - * @dev Returns the identifier of the non-fungible token which owns the account - * - * The return value of this function MUST be constant - it MUST NOT change - * over time - * - * @return chainId The EIP-155 ID of the chain the token exists on - * @return tokenContract The contract address of the token - * @return tokenId The ID of the token - */ - function token() - external - view - returns ( - uint256 chainId, - address tokenContract, - uint256 tokenId - ); - - /** - * @dev Returns a value that SHOULD be modified each time the account changes state - * - * @return The current account state - */ - function state() external view returns (uint256); - - /** - * @dev Returns a magic value indicating whether a given signer is authorized to act on behalf of the account - * - * MUST return the bytes4 magic value 0x523e3260 if the given signer is valid - * - * By default, the holder of the non-fungible token the account is bound to MUST be considered a valid - * signer - * - * Accounts MAY implement additional authorization logic which invalidates the holder as a - * signer or grants signing permissions to other non-holder accounts - * - * @param signer The address to check signing authorization for - * @param context Additional data used to determine whether the signer is valid - * @return magicValue Magic value indicating whether the signer is valid - */ - function isValidSigner(address signer, bytes calldata context) - external - view - returns (bytes4 magicValue); -} -``` - -### Execution Interface - -All token bound accounts MUST implement an execution interface which allows valid signers to execute arbitrary operations on behalf of the account. Support for an execution interface MUST be signaled by the account using ERC-165 interface detection. - -Token bound accounts MAY support the following execution interface: - -```solidity -/// @dev the ERC-165 identifier for this interface is `0x74420f4c` -interface IERC6551Executable { - /** - * @dev Executes a low-level operation if the caller is a valid signer on the account - * - * Reverts and bubbles up error if operation fails - * - * @param to The target address of the operation - * @param value The Ether value to be sent to the target - * @param data The encoded operation calldata - * @param operation A value indicating the type of operation to perform - * - * Accounts implementing this interface MUST accept the following operation parameter values: - * - 0 = CALL - * - 1 = DELEGATECALL - * - 2 = CREATE - * - 3 = CREATE2 - * - * Accounts implementing this interface MAY support additional operations or restrict a signer's - * ability to execute certain operations - * - * @return The result of the operation - */ - function execute( - address to, - uint256 value, - bytes calldata data, - uint256 operation - ) external payable returns (bytes memory); -} -``` - -## Rationale - -### Singleton Registry - -This proposal specifies a single, canonical registry that can be permissionlessly deployed to any chain at a known address. It purposefully does not specify a common interface that can be implemented by multiple registry contracts. This approach enables several critical properties. - -#### Counterfactual Accounts - -All token bound accounts are created using the create2 opcode, enabling accounts to exist in a counterfactual state prior to their creation. This allows token bound accounts to receive assets prior to contract creation. A singleton account registry ensures a common addressing scheme is used for all token bound account addresses. - -#### Trustless Deployments - -A single ownerless registry ensures that the only trusted contract for any token bound account is the implementation. This guarantees the holder of a token access to all assets stored within a counterfactual account using a trusted implementation. - -Without a canonical registry, some token bound accounts may be deployed using an owned or upgradable registry. This may lead to loss of assets stored in counterfactual accounts, and increases the scope of the security model that applications supporting this proposal must consider. - -#### Cross-chain Compatibility - -A singleton registry with a known address enables each token bound account to exist on multiple chains. The inclusion of `chainId` as a parameter to `createAccount` allows the contract for a token bound account to be deployed at the same address on any supported chain. Account implementations are therefore able to support cross-chain account execution, where an NFT on one chain can control its token bound account on another chain. - -#### Single Entry Point - -A single entry point for querying account addresses and `AccountCreated` events simplifies the complex task of indexing token bound accounts in applications which support this proposal. - -#### Implementation Diversity - -A singleton registry allows diverse account implementations to share a common addressing scheme. This gives developers significant freedom to implement both account-specific features (e.g. delegation) as well as alternative account models (e.g. ephemeral accounts) in a way that can be easily supported by client applications. - -### Registry vs Factory - -The term "registry" was chosen instead of "factory" to highlight the canonical nature of the contract and emphasize the act of querying account addresses (which occurs regularly) over the creation of accounts (which occurs only once per account). - -### Variable Execution Interface - -This proposal does not require accounts to implement a specific execution interface in order to be compatible, so long as they signal support for at least one execution interface via ERC-165 interface detection. Allowing account developers to choose their own execution interface allows this proposal to support the wide variety of existing execution interfaces and maintain forward compatibility with likely future standardized interfaces. - -### Account Ambiguity - -The specification proposed above allows NFTs to have multiple token bound accounts. During the development of this proposal, alternative architectures were considered which would have assigned a single token bound account to each NFT, making each token bound account address an unambiguous identifier. - -However, these alternatives present several trade offs. - -First, due to the permissionless nature of smart contracts, it is impossible to enforce a limit of one token bound account per NFT. Anyone wishing to utilize multiple token bound accounts per NFT could do so by deploying an additional registry contract. - -Second, limiting each NFT to a single token bound account would require a static, trusted account implementation to be included in this proposal. This implementation would inevitably impose specific constraints on the capabilities of token bound accounts. Given the number of unexplored use cases this proposal enables and the benefit that diverse account implementations could bring to the non-fungible token ecosystem, it is the authors' opinion that defining a canonical and constrained implementation in this proposal is premature. - -Finally, this proposal seeks to grant NFTs the ability to act as agents on-chain. In current practice, on-chain agents often utilize multiple accounts. A common example is individuals who use a "hot" account for daily use and a "cold" account for storing valuables. If on-chain agents commonly use multiple accounts, it stands to reason that NFTs ought to inherit the same ability. - -### Proxy Implementation - -ERC-1167 minimal proxies are well supported by existing infrastructure and are a common smart contract pattern. This proposal deploys each token bound account using a custom ERC-1167 proxy implementation that stores the salt, chain id, token contract address, and token ID as ABI-encoded constant data appended to the contract bytecode. This allows token bound account implementations to easily query this data while ensuring it remains constant. This approach was taken to maximize compatibility with existing infrastructure while also giving smart contract developers full flexibility when creating custom token bound account implementations. - -### EIP-155 Support - -This proposal uses an EIP-155 chain ID to identify each NFT along with its contract address and token ID. Token identifiers are globally unique on a single Ethereum chain, but may not be unique across multiple Ethereum chains. - -## Backwards Compatibility - -This proposal seeks to be maximally backwards compatible with existing non-fungible token contracts. As such, it does not extend the ERC-721 standard. - -Additionally, this proposal does not require the registry to perform an ERC-165 interface check for ERC-721 compatibility prior to account creation. This maximizes compatibility with non-fungible token contracts that pre-date the ERC-721 standard (such as CryptoKitties) or only implement a subset of the ERC-721 interface (such as ENS NameWrapper names). It also allows the system described in this proposal to be used with semi-fungible or fungible tokens, although these use cases are outside the scope of the proposal. - -Smart contract authors may optionally choose to enforce interface detection for ERC-721 in their account implementations. - -## Reference Implementation - -### Example Account Implementation - -```solidity -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/interfaces/IERC1271.sol"; -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; - -contract ExampleERC6551Account is IERC165, IERC1271, IERC6551Account, IERC6551Executable { - uint256 public state; - - receive() external payable {} - - function execute( - address to, - uint256 value, - bytes calldata data, - uint256 operation - ) external payable returns (bytes memory result) { - require(_isValidSigner(msg.sender), "Invalid signer"); - require(operation == 0, "Only call operations are supported"); - - ++state; - - bool success; - (success, result) = to.call{value: value}(data); - - if (!success) { - assembly { - revert(add(result, 32), mload(result)) - } - } - } - - function isValidSigner(address signer, bytes calldata) external view returns (bytes4) { - if (_isValidSigner(signer)) { - return IERC6551Account.isValidSigner.selector; - } - - return bytes4(0); - } - - function isValidSignature(bytes32 hash, bytes memory signature) - external - view - returns (bytes4 magicValue) - { - bool isValid = SignatureChecker.isValidSignatureNow(owner(), hash, signature); - - if (isValid) { - return IERC1271.isValidSignature.selector; - } - - return ""; - } - - function supportsInterface(bytes4 interfaceId) external pure returns (bool) { - return (interfaceId == type(IERC165).interfaceId || - interfaceId == type(IERC6551Account).interfaceId || - interfaceId == type(IERC6551Executable).interfaceId); - } - - function token() - public - view - returns ( - uint256, - address, - uint256 - ) - { - bytes memory footer = new bytes(0x60); - - assembly { - extcodecopy(address(), add(footer, 0x20), 0x4d, 0x60) - } - - return abi.decode(footer, (uint256, address, uint256)); - } - - function owner() public view returns (address) { - (uint256 chainId, address tokenContract, uint256 tokenId) = token(); - if (chainId != block.chainid) return address(0); - - return IERC721(tokenContract).ownerOf(tokenId); - } - - function _isValidSigner(address signer) internal view returns (bool) { - return signer == owner(); - } -} -``` - -### Registry Implementation - -```solidity -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/Create2.sol"; - -library ERC6551BytecodeLib { - function getCreationCode( - address implementation_, - uint256 chainId_, - address tokenContract_, - uint256 tokenId_, - uint256 salt_ - ) internal pure returns (bytes memory) { - return - abi.encodePacked( - hex"3d60ad80600a3d3981f3363d3d373d3d3d363d73", - implementation_, - hex"5af43d82803e903d91602b57fd5bf3", - abi.encode(salt_, chainId_, tokenContract_, tokenId_) - ); - } -} - -contract ERC6551Registry is IERC6551Registry { - error AccountCreationFailed(); - - function createAccount( - address implementation, - uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt, - bytes calldata initData - ) external returns (address) { - bytes memory code = ERC6551BytecodeLib.getCreationCode( - implementation, - chainId, - tokenContract, - tokenId, - salt - ); - - address _account = Create2.computeAddress(bytes32(salt), keccak256(code)); - - if (_account.code.length != 0) return _account; - - emit AccountCreated(_account, implementation, chainId, tokenContract, tokenId, salt); - - assembly { - _account := create2(0, add(code, 0x20), mload(code), salt) - } - - if (_account == address(0)) revert AccountCreationFailed(); - - if (initData.length != 0) { - (bool success, bytes memory result) = _account.call(initData); - - if (!success) { - assembly { - revert(add(result, 32), mload(result)) - } - } - } - - return _account; - } - - function account( - address implementation, - uint256 chainId, - address tokenContract, - uint256 tokenId, - uint256 salt - ) external view returns (address) { - bytes32 bytecodeHash = keccak256( - ERC6551BytecodeLib.getCreationCode( - implementation, - chainId, - tokenContract, - tokenId, - salt - ) - ); - - return Create2.computeAddress(bytes32(salt), bytecodeHash); - } -} -``` - -## Security Considerations - -### Fraud Prevention - -In order to enable trustless sales of token bound accounts, decentralized marketplaces will need to implement safeguards against fraudulent behavior by malicious account owners. - -Consider the following potential scam: - -- Alice owns an ERC-721 token X, which owns token bound account Y. -- Alice deposits 10ETH into account Y -- Bob offers to purchase token X for 11ETH via a decentralized marketplace, assuming he will receive the 10ETH stored in account Y along with the token -- Alice withdraws 10ETH from the token bound account, and immediately accepts Bob's offer -- Bob receives token X, but account Y is empty - -To mitigate fraudulent behavior by malicious account owners, decentralized marketplaces SHOULD implement protection against these sorts of scams at the marketplace level. Contracts which implement this EIP MAY also implement certain protections against fraudulent behavior. - -Here are a few mitigations strategies to be considered: - -- Attach the current token bound account state to the marketplace order. If the state of the account has changed since the order was placed, consider the offer void. This functionality would need to be supported at the marketplace level. -- Attach a list of asset commitments to the marketplace order that are expected to remain in the token bound account when the order is fulfilled. If any of the committed assets have been removed from the account since the order was placed, consider the offer void. This would also need to be implemented by the marketplace. -- Submit the order to the decentralized market via an external smart contract which performs the above logic before validating the order signature. This allows for safe transfers to be implemented without marketplace support. -- Implement a locking mechanism on the token bound account implementation that prevents malicious owners from extracting assets from the account while locked - -Preventing fraud is outside the scope of this proposal. - -### Ownership Cycles - -All assets held in a token bound account may be rendered inaccessible if an ownership cycle is created. The simplest example is the case of an ERC-721 token being transferred to its own token bound account. If this occurs, both the ERC-721 token and all of the assets stored in the token bound account would be permanently inaccessible, since the token bound account is incapable of executing a transaction which transfers the ERC-721 token. - -Ownership cycles can be introduced in any graph of n>0 token bound accounts. On-chain prevention of cycles with depth>1 is difficult to enforce given the infinite search space required, and as such is outside the scope of this proposal. Application clients and account implementations wishing to adopt this proposal are encouraged to implement measures that limit the possibility of ownership cycles. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6551.md diff --git a/EIPS/eip-6596.md b/EIPS/eip-6596.md index 3ef296f880aaf9..7b7a302087ec2e 100644 --- a/EIPS/eip-6596.md +++ b/EIPS/eip-6596.md @@ -1,497 +1 @@ ---- -eip: 6596 -title: Historical Asset Metadata JSON Schema -description: Metadata JSON Schema extension to enhance the discoverability, connectivity, and collectability of historically significant NFTs. -author: Phillip Pon , Gary Liu , Henry Chan , Joey Liu , Lauren Ho , Jeff Leung , Yvan Fatal , Brian Liang , Seungyong Moon , Joyce Li , Phoebe Kwok , Avir Mahtani , Zeon Chan , Antoine Cote , David Leung (@dhl) -discussions-to: https://ethereum-magicians.org/t/eip-6596-historical-asset-metadata-json-schema/13090 -status: Stagnant -type: Standards Track -category: ERC -created: 2023-02-28 -requires: 721, 1155 ---- - -## Abstract - -This EIP proposes the establishment of a new metadata standard for Historical Asset Tokens (HATs) on the Ethereum -platform. HATs are tokens that represent a specific historical asset, such as a collectible or a rare item, and provide -comprehensive context and provenance needed to establish historical significance and value. - -HAT is the metadata standard and smart contract for historical NFTs. While existing NFT standards offer the mechanism to -ensure immutability and decentralised ownership of assets on the blockchain, we believe a rich metadata standard (that -includes data for the underlying asset from its moment of creation to its moment of conversion) will imbue historical -NFTs with the comprehensive context and provenance needed to establish historical significance and value. Additionally, -the standard will enhance the discoverability, connectivity, and collectability of all HATs. - -## Motivation - -In recent years, the market for collectible and rare items has experienced significant growth, with many people looking -to buy, sell, and trade these assets in an efficient and secure manner. However, the current market for historical -assets is often plagued by issues such as fraud, counterfeiting, and lack of transparency. - -The creation of a standard for HATs on the Ethereum platform has the potential to address these issues and provide a -secure and transparent marketplace for historical assets. By representing historical assets as tokens on the blockchain, -it becomes possible to create a permanent, tamper-proof record of ownership and transfer of these assets, while also -providing a level of transparency and security that is not currently available in traditional markets. - -The standard is proposed as the number and variety of NFT projects increases. The current [ERC-721](./eip-721.md) has a -modest metadata -extension, optionally allowing for the inclusion of “name” and “symbol” functions to identify NFT -collections, and for "name", "descriptions", and “image” attributes to represent assets. Metadata plays a big role in -facilitating the search and discovery of NFTs on marketplaces. As the number and variety of NFT projects increases, -disconnected and limited metadata structures makes it difficult for collectors to navigate through the ocean of NFT -assets on major marketplaces and make sense of their value. - -The provenance and history of artworks, artifacts and historical IP can take different forms and be created by -different issuers. While there is no consolidated archive, a standardised metadata structure provides connectivity -across NFTs associated with historical and cultural assets, regardless of the medium and issuer. For example, a video -clip issued by a local filmmaker about the 1997 handover of Hong Kong can be connected to the news coverage by South -China Morning Post (SCMP) of the Sino-British Joint Declaration via the HAT metadata. While well-informed collectors may -not need this programmed connectivity, a standardised metadata for HATs will make these connections easily accessible -for all collectors, scholars, and interested parties. - -Provenance and context are extremely important for historical assets. The archival and research of significant artworks, -objects, and collections are ongoing; it is a collaborative effort between creators, artist estates, galleries, auction -houses, scholars and institutions. The consolidation of information and knowledge is not only crucial to the -understanding of our cultural heritage; the richer the context, the more valuable the asset is to collectors. A shared -and standardised metadata structure enriches the context of an NFT. With the establishment of HATs, it is our hope that -this standardised structure will contribute to the evolution of cultural heritage, and fully capture the context of -culturally and historically significant assets, thereby ensuring relevant and fair value for all historically -significant NFTs. - -## 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. - -This EIP extends [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) with 48 additional properties to capture the -historical significance of the underlying asset. - -Compatible contracts, besides implementing the relevant metadata schemas ("Metadata JSON Schema" for -[ERC-721](./eip-721.md) contracts or "Metadata URI JSON Schema" for [ERC-1155](./eip-1155.md) contracts), must implement -the following metadata interface. - -### Historical Asset Metadata Extension TypeScript Interface - -The following TypeScript interface defines the mandatory and optional properties compatible tokens must conform to: - -```typescript -interface HistoricalAssetMetadata { - name: string; // Name of the HAT - description: string, // Full description of the HAT to provide the historical context - image: string; // A URI pointing to a resource with mime type image/* to serve as the cover image of the HAT - properties: { - id: string; // An unambiguous identifier of the HAT - summary: string; // Short description of the HAT, no more than 200 characters - assetType: // The type of the underlying asset - "newspaper_cover" - | "magazine_a1_cover" - | "newspaper_article" - | "magazine_article" - | "photo" - | "graphic" - | "video" - | "audio" - | "3d_object" - | "others"; - issuers: string[]; // Organizations or individuals who created the HAT - issueTimestamp: string; // The date and time the HAT was issued in ISO 8601 format - edition: number; // Unique serial number of the HAT - editionCount: number; // Total number of editions available for this HAT - fileURI: string; // Link to the digital file representing the HAT - fileSize: number; // Size of the digital file of the HAT in bytes - fileFormat: string; // MIME type of the digital file of the HAT - seriesName?: string; // The name of the series this HAT is a part of - assetFullText?: string; // The full text in the underlying asset of the HAT - assetCreators?: string[]; // Organizations or individuals who created the underlying asset - earliestPossibleCreationDate?: string; // Earliest possible creation date of the underlying asset in ISO 8601 date format - latestPossibleCreationDate: string; // Latest possible creation date of the underlying asset in ISO 8601 date format - assetCreationGeos?: string; // Country, subdivision, and city where the underlying asset was created. Reference to ISO 3166-2 standard for the short name of the country and subdivision. Utilize the official name for the city if it is not covered in the ISO subdivision - assetCreationLocations?: string[]; // Specific cities and named locations where the underlying asset was created - assetCreationCoordinates?: string[]; // Coordinates of the location where the underlying asset was created - relevantDates?: string[]; // Dates, in ISO 8601 date format, that are referenced and important to the significance of the HAT - relevantGeos?: string[]; // Country, subdivision, and city that are referenced and important to the significance of the HAT. Reference to ISO 3166-2 standard for the short name of the country and subdivision. Utilize the official name for the city if it is not covered in the ISO subdivision - relevantLocations?: string[]; // Specific cities and named locations that are referenced and important to the significance of the HAT - relevantPeople?: string[]; // Individuals that are referenced and important to the significance of the HAT - relevantEntities?: string[]; // Entities that are referenced and important to the significance of the HAT - assetLanguages?: string[]; // Languages used in the underlying asset. Reference to ISO 639 for code or macrolanguage names - assetHeight?: string; // Height of the underlying asset - assetWidth?: string; // Width of the underlying asset - assetDepth?: string; // Depth of the underlying asset - assetFileURI?: string; // Link to a high quality file of the underlying asset - assetFileSize?: number; // Size of the digital file of the underlying asset in bytes - assetCopyrightHolder?: string; // Copyright holder of the underlying asset - assetCopyrightDocumentURI?: string; // Link to the legal contract that outlines the copyright of the underlying asset - assetProvenanceRecordURI?: string; // Link to the existing provenance record documents of the underlying asset - isPhysicalAsset?: boolean; // Flags whether the asset is tied to a physical asset - assetMedium?: string; // The material used to create the physical underlying asset - assetFormFormat?: string; // The physical form or the digital format of the underlying asset. For digital format, a MIME type should be specified. - issuerNotes?: string; // Issuer's notes regarding the HAT and its underlying asset - tokenReplaced?: string; // Token identifier of the token this HAT replaced - tokenReferenced?: string; // Token identifier of the token this HAT referenced, or is a derivative of - isOwnerTokenCopyrightRightHolder?: boolean; // Flags whether the HAT token holder is the copyright owner of the HAT. Does not include the copyright to the underlying asset - tokenCopyrightRightDocumentURI?: string; // Link to legal document outlining the rights of the token owner. Specific dimensions include the right to display a work via digital and physical mediums, present the work publicly, create or sell copies of the work, and create or sell derivations from the HAT - isOwnerAssetCopyrightRightHolder?: boolean; // Flags whether the HAT token holder is the copyright owner of the underlying asset. Does not include the copyright to the underlying asset - assetCopyrightRightDocumentURI?: string; // Link to legal document outlining the rights of the token owner. Specific dimensions include the right to display a work via digital and physical mediums, present the work publicly, create or sell copies of the work, and create or sell derivations from the underlying asset - isOwnerTokenReprintRightHolder?: boolean; // Flags whether the token owner has non-exclusive reprint/second serial rights to the HAT. Does not include the rights to the underlying asset - tokenReprintRightDocumentURI?: string; // Link to legal document outlining the token owner’s reprint/second serial rights of the HAT - isOwnerAssetReprintRightHolder?: boolean; // Flags whether the token owner has non-exclusive reprint/second serial rights to the underlying asset. Does not include the rights to the underlying asset - assetReprintRightDocumentURI?: string; // Link to legal document outlining the token owner’s reprint/second serial rights of the underlying asset - isEligibleForRelatedTokenAirdrops?: boolean; // Flags whether the token holder is eligible for related HAT airdrops - isEligibleForMetaverseAccess?: boolean; // Flags whether the token holder can gain access to the HAT metaverse - } -} -``` - -### Historical Asset Metadata JSON Schema - -The following JSON Schema enforces the constraints set out in the above interface definition. Tokens adopting the -Historical Asset Metadata JSON Schema extension should be validated against the JSON schema prior to minting: - -```json -{ - "title": "Historical Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the HAT" - }, - "description": { - "type": "string", - "description": "Full description of the HAT to provide the historical context" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* to serve as the cover image of the HAT", - "format": "uri" - }, - "properties": { - "type": "object", - "description": "Historical asset attributes", - "properties": { - "id": { - "type": "string", - "description": "An unambiguous identifier of the HAT" - }, - "summary": { - "type": "string", - "description": "Short description of the HAT, no more than 200 characters" - }, - "assetType": { - "type": "string", - "enum": [ - "newspaper_cover", - "magazine_a1_cover", - "newspaper_article", - "magazine_article", - "photo", - "graphic", - "video", - "audio", - "3d_object", - "others" - ], - "description": "The type of the underlying asset" - }, - "issuers": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Organizations or individuals who created the HAT" - }, - "issueTimestamp": { - "type": "string", - "description": "The date and time the HAT was issued in ISO 8601 format", - "format": "date-time" - }, - "edition": { - "type": "integer", - "description": "Unique serial number of the HAT" - }, - "editionCount": { - "type": "integer", - "description": "Total number of editions available for this HAT" - }, - "fileURI": { - "type": "string", - "description": "Link to the digital file representing the HAT", - "format": "uri" - }, - "fileSize": { - "type": "integer", - "description": "Size of the digital file of the HAT in bytes" - }, - "fileFormat": { - "type": "string", - "description": "MIME type of the digital file of the HAT" - }, - "seriesName": { - "type": "string", - "description": "The name of the series this HAT is a part of" - }, - "assetFullText": { - "type": "string", - "description": "The full text in the underlying asset of the HAT" - }, - "assetCreators": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Organizations or individuals who created the underlying asset" - }, - "earliestPossibleCreationDate": { - "type": "string", - "description": "Earliest possible creation date of the underlying asset in ISO 8601 date format", - "format": "date" - }, - "latestPossibleCreationDate": { - "type": "string", - "format": "date", - "description": "Latest possible creation date of the underlying asset in ISO 8601 date format" - }, - "assetCreationGeos": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Country, subdivision, and city where the underlying asset was created. Reference to ISO 3166-2 standard for the short name of the country and subdivision. Utilize the official name for the city if it is not covered in the ISO subdivision" - }, - "assetCreationLocations": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Specific cities and named locations where the underlying asset was created" - }, - "assetCreationCoordinates": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Coordinates of the location where the underlying asset was created" - }, - "relevantDates": { - "type": "array", - "items": { - "type": "string", - "description": "Dates, in ISO 8601 date format, that are referenced and important to the significance of the HAT", - "format": "date" - } - }, - "relevantGeos": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Country, subdivision, and city that are referenced and important to the significance of the HAT. Reference to ISO 3166-2 standard for the short name of the country and subdivision. Utilize the official name for the city if it is not covered in the ISO subdivision" - }, - "relevantLocations": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Specific cities and named locations that are referenced and important to the significance of the HAT" - }, - "relevantPeople": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Individuals that are referenced and important to the significance of the HAT" - }, - "relevantEntities": { - "type": "string", - "description": "Entities that are referenced and important to the significance of the HAT" - }, - "assetLanguages": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Languages used in the underlying asset. Reference to ISO 639 for code or macrolanguage names" - }, - "assetHeight": { - "type": "string", - "description": "Height of the underlying asset" - }, - "assetWidth": { - "type": "string", - "description": "Width of the underlying asset" - }, - "assetDepth": { - "type": "string", - "description": "Depth of the underlying asset" - }, - "assetFileURI": { - "type": "string", - "description": "Link to a high quality file of the underlying asset", - "format": "uri" - }, - "assetFileSize": { - "type": "integer", - "description": "Size of the digital file of the underlying asset in bytes" - }, - "assetCopyrightHolder": { - "type": "string", - "description": "Copyright holder of the underlying asset" - }, - "assetCopyrightDocumentURI": { - "type": "string", - "description": "Link to the legal contract that outlines the copyright of the underlying asset", - "format": "uri" - }, - "assetProvenanceRecordURI": { - "type": "string", - "description": "Link to the existing provenance record documents of the underlying asset", - "format": "uri" - }, - "isPhysicalAsset": { - "type": "boolean", - "description": "Flags whether the asset is tied to a physical asset" - }, - "assetMedium": { - "type": "string", - "description": "The material used to create the physical underlying asset" - }, - "assetFormFormat": { - "type": "string", - "description": "The physical form or the digital format of the underlying asset" - }, - "issuerNotes": { - "type": "string", - "description": "Issuer's notes regarding the HAT and its underlying asset" - }, - "tokenReplaced": { - "type": "string", - "description": "Token identifier of the token this HAT replaced" - }, - "tokenReferenced": { - "type": "string", - "description": "Token identifier of the token this HAT referenced, or is a derivative of" - }, - "isOwnerTokenCopyrightRightHolder": { - "type": "boolean", - "description": "Flags whether or not the HAT token holder is the copyright owner of the underlying asset. Does not include the copyright to the underlying asset" - }, - "tokenCopyrightRightDocumentURI": { - "type": "string", - "description": "Link to legal document outlining the rights of the token owner. Specific dimensions include the right to display a work via digital and physical mediums, present the work publicly, create or sell copies of the work, and create or sell derivations from the HAT", - "format": "uri" - }, - "isOwnerAssetCopyrightRightHolder": { - "type": "boolean", - "description": "Flags whether or not the HAT token holder is the copyright owner of the underlying asset. Does not include the copyright to the underlying asset" - }, - "assetCopyrightRightDocumentURI": { - "type": "string", - "description": "Link to legal document outlining the rights of the token owner. Specific dimensions include the right to display a work via digital and physical mediums, present the work publicly, create or sell copies of the work, and create or sell derivations from the underlying asset", - "format": "uri" - }, - "isOwnerTokenReprintRightHolder": { - "type": "boolean", - "description": "Flags whether or not the HAT token owner has non-exclusive reprint/second serial rights to the HAT. Does not include the rights to the underlying asset" - }, - "tokenReprintRightDocumentURI": { - "type": "string", - "description": "Link to legal document outlining the token owner’s reprint/second serial rights of the HAT", - "format": "uri" - }, - "isOwnerAssetReprintRightHolder": { - "type": "boolean", - "description": "Flags whether or not the HAT token owner has non-exclusive reprint/second serial rights to the underlying asset. Does not include the rights to the underlying asset" - }, - "assetReprintRightDocumentURI": { - "type": "string", - "description": "Link to legal document outlining the token owner’s reprint/second serial rights of the underlying asset", - "format": "uri" - }, - "isEligibleForRelatedTokenAirdrops": { - "type": "boolean", - "description": "Flags whether or not the HAT token holder is eligible for related HAT airdrops" - }, - "isEligibleForMetaverseAccess": { - "type": "boolean", - "description": "Flags whether or not the HAT token holder can gain access to the HAT metaverse" - } - }, - "required": [ - "assetType", - "id", - "issuers", - "summary", - "issueTimestamp", - "edition", - "editionCount", - "fileURI", - "fileSize", - "fileFormat", - "latestPossibleCreationDate" - ] - } - }, - "required": [ - "name", - "description", - "image", - "properties" - ] -} -``` - -## Rationale - -### Choosing to Extend Off-Chain Metadata JSON Schema over On-Chain Interface - -Both the [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) provides natural extension point in the metadata JSON -file associated with NFTs to provide enriched dataset about the underlying assets. - -Providing enriched dataset through off-chain metadata JSON files allow already-deployed NFT contracts to adopt the -metadata structure in this EIP without upgrade or migration. The off-chain design allows for flexible and progressive -enhancement of any NFT collections to adopt standard such as this EIP, and to progressively enhance and enrich the -collection over time. - -By avoiding the need to newly create or adapt smart contracts, NFT collections can be deployed using audited and battle -tested smart contract code, thus reducing the risk of adopting and implementing a new standard. - -### Capturing Attributes Extensions in `properties` property - -The [ERC-1155](./eip-1155.md) standard has a `properties` property in its JSON Metadata Schema to define an arbitrary -set of additional metadata or properties to describe an underlying asset. Defining the metadata fields in this EIP over -this properties preserve the overall shape of the metadata schema of the [ERC-1155](./eip-1155.md), minimizing the -change needed to list and process the proposed properties. - -For tokens minted against the [ERC-721](./eip-721.md) standard, only one additional property needs to be added to the -metadata JSON object to take advantage of the standardized fields, simplifying the adoption of this standard. - -## Backwards Compatibility - -This EIP is fully backward compatible with [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md). - -## Security Considerations - -NFT platforms and systems working with Historical Asset Metadata JSON files are recommended to treat the files as client -supplied data and follow the appropriate best practices for processing such data. - -When processing the URI fields, backend systems should take care to prevent a malicious issuer exploiting these fields -to perform Server-Side Request Forgery (SSRF). - -Frontend or client-side systems are recommended to escape all control characters that may be exploited to perform -Cross-Site Scripting (XSS). - -Processing systems are also recommended to take care with resource allocation to mitigate against buffer overflow due to -improper processing of variable data such as strings, arrays, and JSON objects. This is to prevent the systems from -becoming susceptible to Denial of Service (DOS) attack or circumventing security protection through arbitrary code -exception. - -The metadata JSON files and the digital resources representing both the token and underlying assets should be stored in -a decentralized storage network to preserve integrity and to ensure available of data for long term preservation. - -Establishing the authenticity of the claims made in the Metadata JSON file is beyond the scope of this EIP, and is left -to future EIPs to propose an appropriate protocol. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6596.md diff --git a/EIPS/eip-6604.md b/EIPS/eip-6604.md index 120e137ca45f13..298eac71050f16 100644 --- a/EIPS/eip-6604.md +++ b/EIPS/eip-6604.md @@ -1,286 +1 @@ ---- -eip: 6604 -title: Abstract Token -description: move tokens on- and off-chain as desired, enabling zero-cost minting while preserving on-chain composability -author: Chris Walker (@cr-walker) -discussions-to: https://ethereum-magicians.org/t/draft-eip-abstract-token-standard/13152 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-03 -requires: 20, 165, 721, 1155 ---- - -## Abstract - -Abstract tokens provide a standard interface to: - -* Mint tokens off-chain as messages -* Reify tokens on-chain via smart contract -* Dereify tokens back into messages - -Abstract tokens can comply with existing standards like [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), and [ERC-1155](./eip-1155.md). The standard allows wallets and other applications to better handle *potential* tokens before any consensus-dependent events occur on-chain. - -## Motivation - -Abstract tokens enable zero-cost token minting, facilitating high-volume applications by allowing token holders to reify tokens (place the tokens on-chain) as desired. Example use cases: - -* airdrops -* POAPs / receipts -* identity / access credentials - -Merkle trees are often used for large token distributions to spread mint/claim costs to participants, but they require participants to provide a markle proof when claiming tokens. This standard aims to improve the claims proces for similar distributions: - -* Generic: compatible with merkle trees, digital signatures, or other eligibility proofs -* Legible: users can query an abstract token contract to understand their potential tokens (e.g. token id, quantity, or uri) -* Contained: users do not need to understand the proof mechanism used by the particular token implementation contract - -## 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. - -### Data Types - -#### Token Messages - -A token message defines one or more tokens along with the context needed to reify the token(s) using a smart contract. - -`chainId` & `implementation`: set the domain of the token message to a specific chain and contract: this is where the token can be reified -`owner`: the address that owns the tokens defined in the messages when reified -`meta`: implementation-specific context necessary to reify the defined token(s), such as id, amount, or uri. -`proof`: implementation-specific authorization to reify the defined token(s). -`nonce`: counter that may be incremented when multiple otherwise-identical abstract token messages are needed - -```solidity -struct AbstractTokenMessage { - uint256 chainId; - address implementation; - address owner; - bytes meta; - uint256 nonce; - bytes proof; -} -``` - -#### Message Status - -A message status may be defined for every (abstract token contract, abstract token message) pair. -`invalid`: the contract cannot interact with the message -`valid`: the contract can interact with the message -`used`: the contract has already interacted with the message - -```solidity -enum AbstractTokenMessageStatus { - invalid, - valid, - used -} -``` - -### Methods - -#### reify - -Moves token(s) from a message to a contract -`function reify(AbstractTokenMessage calldata message) external;` - -The token contract MUST reify a valid token message. - -Reification MUST be idempotent: a particular token message may be used to reify tokens at most once. Calling `reify` with an already used token message MAY succeed or revert. - -#### status - -Returns the status of a particular message -`function status(AbstractTokenMessage calldata message) external view returns (AbstractTokenMessageStatus status);` - -#### dereify - -Moves token(s) from a contract to a message intended for another contract and/or chain. -`function dereify(AbstractTokenMessage calldata message) external;` - -OPTIONAL - allows tokens to be moved between contracts and/or chains by dereifying them from one context and reifying them in another. -Dereification MUST be idempotent: a particular token message must be used to dereify tokens at most once. - -If implemented, dereification: - -* MUST burn the exact tokens from the holder as defined in the token message -* MUST NOT dereify token messages scoped to the same contract and chain. -* MAY succeed or revert if the token message is already used. -* MUST emit the `Reify` event on only the first `reify` call with a specific token message - -#### id - -Return the id of token(s) defined in a token message. -`function id(AbstractTokenMessage calldata message) external view returns (uint256);` - -OPTIONAL - abstract token contracts without a well-defined token ID (e.g. ERC-20) MAY return `0` or not implement this method. - -#### amount - -Return the amount of token(s) defined in a token message. -`function amount(AbstractTokenMessage calldata message) external view returns (uint256);` - -OPTIONAL - abstract token contracts without a well-defined token amount (e.g. ERC-721) MAY return `0` or not implement this method. - -#### uri - -Return the amount of token(s) defined in a token message. -`function uri(AbstractTokenMessage calldata message) external view returns (string memory);` - -OPTIONAL - abstract token contracts without a well-defined uri (e.g. ERC-20) MAY return `""` or not implement this method. - -#### supportsInterface - -All abstract token contracts must support [ERC-165](./eip-165.md) and include the Abstract Token interface ID in their supported interfaces. - -### Events - -#### Reify - -The Reify event MUST be emitted when a token message is reified into tokens -`event Reify(AbstractTokenMessage);` - -#### Dereify - -The Dereify event MUST be emitted when tokens are dereified into a message -`event Dereify(AbstractTokenMessage);` - -### Application to existing token standards - -Abstract tokens compatible with existing token standards MUST overload existing token transfer functions to allow transfers from abstract token messages. - -### Abstract ERC-20 - -```solidity -interface IAbstractERC20 is IAbstractToken, IERC20, IERC165 { - // reify the message and then transfer tokens - function transfer( - address to, - uint256 amount, - AbstractTokenMessage calldata message - ) external returns (bool); - - // reify the message and then transferFrom tokens - function transferFrom( - address from, - address to, - uint256 amount, - AbstractTokenMessage calldata message - ) external returns (bool); -} -``` - -### Abstract ERC-721 - -```solidity -interface IAbstractERC721 is IAbstractToken, IERC721 { - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata _data, - AbstractTokenMessage calldata message - ) external; - - function transferFrom( - address from, - address to, - uint256 tokenId, - AbstractTokenMessage calldata message - ) external; -} -``` - -### Abstract ERC-1155 - -``` -interface IAbstractERC1155 is IAbstractToken, IERC1155 { - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes calldata data, - AbstractTokenMessage calldata message - ) external; - - function safeBatchTransferFrom( - address from, - address to, - uint256[] calldata ids, - uint256[] calldata amounts, - bytes calldata data, - AbstractTokenMessage[] calldata messages - ) external; -} -``` - -## Rationale - -### Meta format - -The abstract token message `meta` field is simply a byte array to preserve the widest possible accesibility. - -* Applications handling abstract tokens can interact with the implementation contract for token metadata rather than parsing this field, so legibility is of secondary importance -* A byte array can be decoded as a struct and checked for errors within the implementation contract -* Future token standards will include unpredictable metadata - -### Proof format - -Similar considerations went into defining the `proof` field as a plain byte array: - -* The contents of this field may vary, e.g. an array of `bytes32` merkle tree nodes or a 65 byte signature. -* a byte array handles all potential use cases at the expense of increased message size. - -## Backwards Compatibility - -No backward compatibility issues found. - -## Reference Implementation - -See [here](../assets/eip-6604/README.md). - -## Security Considerations - -Several concerns are highlighted. - -### Message Loss - -Because token messages are not held on-chain, loss of the message may result in loss of the token. Applications that issue abstract tokens to their users can store the messages themselves, but ideally users would be able to store and interact with abstract token messages within their crypto wallets. - -### Authorizing Reification - -Token messages may only be reified if they include a validity proof. While the proof mechanism itself is out of scope for this standard, those designing proof mechanisms should consider: - -* Does total supply need to audited on-chain and/or off-chain? -* Does the mechanism require ongoing access to a secret (e.g. digital signature) or is it immutable (e.g. merkle proof)? -* Is there any way for an attacker to prevent the reification of an otherwise valid token message? - -### Non-owner (De)Reification - -Can non-owners (de)reify a token message on behalf of the owner? - -Pro: supporting apps should be able to handle this because once a valid message exists, the owner could (de)reify the message at any time -Con: if the token contract reverts upon (de)reification of a used message, an attacker could grief the owner by front-running the transaction - -### Abstract Token Bridge Double Spend - -Abstract tokens could be used for a token-specific bridge: - -* Dereify the token from chain A to with message M -* Reify the token on chain B with message M - -Because the abstract token standard does not specify any cross-chain message passing, the abstract token contracts on chains A and B cannot know whether a (de)reification of message M has occurred on the other chain. - -A naive bridge would be subject to double spend attacks: - -* An attacker requests bridging tokens they hold on chain A to chain B -* A bridging mechanism creates an abstract token message M -* The attacker reifies message M on chain B but *does not* dereify message M on chain A -* The attacker continues to use tokens - -Some oracle mechanism is necessary to prevent the reification of message M on chain B until the corresponding tokens on chain A are dereified. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6604.md diff --git a/EIPS/eip-6617.md b/EIPS/eip-6617.md index d511ff85b3112f..1988f7d0379819 100644 --- a/EIPS/eip-6617.md +++ b/EIPS/eip-6617.md @@ -1,176 +1 @@ ---- -eip: 6617 -title: Bit Based Permission -description: A permission and role system based on bits -author: Chiro (@chiro-hiro), Victor Dusart (@vdusart) -discussions-to: https://ethereum-magicians.org/t/bit-based-permission/13065 -status: Review -type: Standards Track -category: ERC -created: 2023-02-27 ---- - -## Abstract - -This EIP offers a standard for building a bit-based permission and role system. Each permission is represented by a single bit. By using an `uint256`, up to $256$ permissions and $2^{256}$ roles can be defined. We are able to specify the importance of each permission based on the order of the bits. - -## 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. - -_Note_ The following specifications use syntax from Solidity `0.8.7` (or above) - -Interface of reference is described as followed: - -```solidity -pragma solidity ^0.8.7; - -/** - @title EIP-6617 Bit Based Permission - @dev See https://eips.ethereum.org/EIPS/eip-6617 -*/ -interface IEIP6617 { - - /** - MUST trigger when a permission is granted. - @param _grantor Grantor of the permission - @param _permission Permission that is granted - @param _user User who received the permission - */ - event PermissionGranted(address indexed _grantor, uint256 indexed _permission, address indexed _user); - - /** - MUST trigger when a permission is revoked. - @param _revoker Revoker of the permission - @param _permission Permission that is revoked - @param _user User who lost the permission - */ - event PermissionRevoked(address indexed _revoker, uint256 indexed _permission, address indexed _user); - - /** - @notice Check if user has permission - @param _user Address of the user whose permission we need to check - @param _requiredPermission The required permission - @return True if the _permission is a superset of the _requiredPermission else False - */ - function hasPermission(address _user, uint256 _requiredPermission) - external - view - returns (bool); - - /** - @notice Add permission to user - @param _user Address of the user to whom we are going to add a permission - @param _permissionToAdd The permission that will be added - @return The new permission with the _permissionToAdd - */ - function grantPermission(address _user, uint256 _permissionToAdd) - external - returns (bool); - - /** - @notice Revoke permission from user - @param _user Address of the user to whom we are going to revoke a permission - @param _permissionToRevoke The permission that will be revoked - @return The new permission without the _permissionToRevoke - */ - function revokePermission(address _user, uint256 _permissionToRevoke) - external - returns (bool); -} -``` - -- Compliant contracts MUST implement `IEIP6617` -- A permission in a compliant contract is represented as an `uint256`. A permission MUST take only one bit of an `uint256` and therefore MUST be a power of 2. Each permission MUST be unique and the `0` MUST be used for none permission. - -### Metadata Interface - -It is RECOMMENDED for compliant contracts to implement the optional extension `IEIP6617Meta`. - -- They SHOULD define a name and description for the base permissions and main combinaison. - -- They SHOULD NOT define a description for every subcombinaison of permissions possible. - -```solidity -/** - * @dev Defined the interface of the metadata of EIP6617, MAY NOT be implemented - */ -interface IEIP6617Meta { - - /** - Structure of permission description - @param _permission Permission - @param _name Name of the permission - @param _description Description of the permission - */ - struct PermissionDescription { - uint256 permission; - string name; - string description; - } - - /** - MUST trigger when the description is updated. - @param _permission Permission - @param _name Name of the permission - @param _description Description of the permission - */ - event UpdatePermissionDescription(uint256 indexed _permission, string indexed _name, string indexed _description); - - /** - Returns the description of a given `_permission`. - @param _permission Permission - */ - function getPermissionDescription(uint256 _permission) external view returns (PermissionDescription memory description); - - /** - Return `true` if the description was set otherwise return `false`. It MUST emit `UpdatePermissionDescription` event. - @param _permission Permission - @param _name Name of the permission - @param _description Description of the permission - */ - function setPermissionDescription(uint256 _permission, string memory _name, string memory _description) - external - returns (bool success); -} -``` - -## Rationale - -Currently permission and access control is performed using a single owner ([ERC-173](./eip-173.md)) or with `bytes32` roles ([ERC-5982](./eip-5982.md)). -However, using bitwise and bitmask operations allows for greater gas-efficiency and flexibility. - -### Gas cost efficiency - -Bitwise operations are very cheap and fast. For example, doing an `AND` bitwise operation on a permission bitmask is significantly cheaper than calling any number of `LOAD` opcodes. - -### Flexibility - -With the 256 bits of the `uint256`, we can create up to 256 different permissions which leads to $2^{256}$ unique combinations (a.k.a. roles). -_(A role is a combination of multiple permissions)._ Not all roles have to be predefined. - -Since permissions are defined as unsigned integers, we can use the binary OR operator to create new role based on multiple permissions. - -### Ordering permissions by importance - -We can use the most significant bit to represent the most important permission, the comparison between permissions can then be done easily since they all are `uint256`s. - -### Associate a meaning - -Compared with access control managed via ERC-5982, this EIP does not provide a direct and simple understanding of the meaning of a permission or role. - -To deal with this problem, you can set up the metadata interface, which associates a name and description to each permission or role. - -## Reference Implementation - -First implementation could be found here: - -- [Basic ERC-6617 implementation](../assets/eip-6617/contracts/EIP6617.sol) - -## Security Considerations - -No security considerations. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6617.md diff --git a/EIPS/eip-6662.md b/EIPS/eip-6662.md index 3b0f11307dd2cd..81ecc64ae3a7b3 100644 --- a/EIPS/eip-6662.md +++ b/EIPS/eip-6662.md @@ -1,112 +1 @@ ---- -eip: 6662 -title: AA Account Metadata For Authentication -description: An ERC-4337 extension to define a new authentication model -author: Shu Dong (@dongshu2013), Zihao Chen (@zihaoccc), Peter Chen (@pette1999) -discussions-to: https://ethereum-magicians.org/t/eip-6662-account-metadata-for-aa-account-authentication/13232 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-09 -requires: 4337, 4804 ---- - -## Abstract - -This ERC proposes a new **IAccountMetadata** interface as an extension for [ERC-4337](./eip-4337.md) to store authentication data on-chain to support a more user-friendly authentication model. - -## Motivation - -In this proposal, we propose a new **IAccountMetadata** interface as an extension for ERC-4337 **IAccount** interface. With this new interface, users can store authentication data on-chain through one-time publishing, allowing dApps to proactively fetch it from the chain to support a more flexible and user-friendly authentication model. This will serve as an alternative to the current authentication model where users need to log in with a wallet every time and push account-related information to dApps by connecting the wallet in advance. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Authentication Flow - -![Authentication Flow](../assets/eip-6662/auth-flow.png) - -In the new authentication workflow, users use AA compatible smart contract accounts as their wallet addresses. **Authenticator** could be anything but holding the private key to sign users' operations. For example, it can be an offline authenticator mobile app or an online cloud service. **Relay** is an online service responsible for forwarding requests from dApps to the Authenticator. If the authenticator is online, it can play the role of Relay service and listen to dApps directly. - -### Interface - -To support the new authentication workflow, this ERC proposes a new **IAccountMetadata** interface as an extension of **IAccount** interface defined by ERC-4337. - -``` -interface IAccountMetadata { - struct AuthenticatorInfo { - // a list of service URIs to relay message from dApps to authenticators - string[] relayURI; - // a JSON string or URI pointing to a JSON file describing the - // schema of AuthenticationRequest. The URI should follow ERC-4804 - // if the schema file is stored on-chain - string schema; - } - - function getAuthenticationInfo() external view returns(AuthenticatorInfo[] memory); -} -``` - -The relay endpoint should accept an AuthenticationRequest object as input. The format of the AuthenticationRequest object is defined by the schema field at AuthenticationInfo. - -Following is a schema example which supports end to end encryption, where we pack all encrypted fields into an encryptedData field. Here we only list basic fields but there may be more fields per schema definition. A special symbol, such as "$e2ee", could be used to indicate the field is encrypted. - -```json -{ - "title": "AuthenticationRequest", - "type": "object", - "properties": { - "entrypoint": { - "type": "string", - "description": "the entrypoint contract address", - }, - "chainId": { - "type": "string", - "description": "the chain id represented as hex string, e.g. 0x5 for goerli testnet", - }, - "userOp": { - "type": "object", - "description": "UserOp struct defined by ERC-4337 without signature", - }, - "encryptedData": { - "type": "string", - "description": "contains all encrypted fields" - }, - } -} -``` - -## Rationale - -To enable the new authentication workflow we described above, dApp needs to know two things: - -1. **Where is the authenticator?** This is solved by the **relayURI** field in struct **AuthenticationInfo**. Users can publish the uri as the account metadata which will be pulled by dApp to do service discovery. - -2. **What’s the format of AuthenticationRequest?** This is solved by the **schema** field in struct **AuthenticationInfo**. The schema defines the structure of the AuthenticationRequest object which is consumed by the authenticator. It can also be used to define extra fields for the relay service to enable flexible access control. - -### Relay Service Selection - -Each authenticator can provide a list of relay services. dApp should pull through the list of relay services in order to find the first workable one. All relay services under each authenticator must follow the same schema. - -### Signature Aggregation - -Multisig authentication could be enabled if multiple AuthenticatorInfos are provided under each smart contract account. Each authenticator can sign and submit signed user operations to bundler independently. These signatures will be aggregated by the Aggregator defined in ERC-4337. - -### Future Extension - -The **IAccountMetadata** interface could be extended per different requirements. For example, a new alias or avatar field could be defined for profile displaying. - -## Backwards Compatibility - -The new interface is fully backward compatible with ERC-4337. - -## Security Considerations - -### End to End Encryption - -To protect the user’s privacy and prevent front-running attacks, it's better to keep the data from dApps to authenticators encrypted during transmission. This could be done by adopting the JWE (JSON Web Encryption, RFC-7516) method. Before sending out AuthenticationRequest, a symmetric CEK(Content Encryption Key) is generated to encrypt fields with end to end encryption enabled, then the CEK is encrypted with the signer's public key. dApp will pack the request into a JWE object and send it to the authenticator through the relay service. Relay service has no access to the end to end encrypted data since only the authenticator has the key to decrypt the CEK. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6662.md diff --git a/EIPS/eip-6672.md b/EIPS/eip-6672.md index 10d4d6093cd1b9..685334a699b660 100644 --- a/EIPS/eip-6672.md +++ b/EIPS/eip-6672.md @@ -1,189 +1 @@ ---- -eip: 6672 -title: Multi-redeemable NFTs -description: An extension of ERC-721 which enables an NFT to be redeemed in multiple scenarios for either a physical or digital object -author: RE:DREAMER Lab , Archie Chang (@ArchieR7) , Kai Yu (@chihkaiyu) , Yonathan Randyanto (@Randyanto) , Boyu Chu (@chuboyu) , Boxi Li (@boxi79) , Jason Cheng (@Jason0729) -discussions-to: https://ethereum-magicians.org/t/eip-6672-multi-redeemable-nfts/13276 -status: Final -type: Standards Track -category: ERC -created: 2023-02-21 -requires: 165, 721 ---- - -## Abstract - -This EIP proposes an extension to the [ERC-721](./eip-721.md) standard for Non-Fungible Tokens (NFTs) to enable multi-redeemable NFTs. Redemption provides a means for NFT holders to demonstrate ownership and eligibility of their NFT, which in turn enables them to receive a physical or digital item. This extension would allow an NFT to be redeemed in multiple scenarios and maintain a record of its redemption status on the blockchain. - -## Motivation - -The motivation behind our proposed NFT standard is to provide a more versatile and flexible solution compared to existing standards, allowing for multi-redeemable NFTs. Our proposed NFT standard enables multi-redeemable NFTs, allowing them to be redeemed in multiple scenarios for different campaigns or events, thus unlocking new possibilities for commerce use cases and breaking the limitation of one-time redemption per NFT. - -One use case for an NFT that can be redeemed multiple times in various scenarios is a digital concert ticket. The NFT could be redeemed for access to the online concert and then again for exclusive merchandise, a meet and greet with the artist, or any exclusive commerce status that is bound to the NFT. Each redemption could represent a unique experience or benefit for the NFT holder. - -## 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. - -### Redeem and Cancel Functions - -An operator SHALL only make an update to the redemption created by itself. Therefore, the `redeem()` and `cancel()` functions do not have an `_operator` parameter, and the `msg.sender` address MUST be used as the `_operator`. - -### Redemption Flag Key-Value Pairs - -The combination of `_operator`, `_tokenId`, and `_redemptionId` MUST be used as the key in the redemption flag key-value pairs, whose value can be accessed from the `isRedeemed()` function. - -**Every contract compliant with this EIP MUST implement `ERC6672` and `ERC721` interfaces.** - -```solidity -pragma solidity ^0.8.16; - -/// @title ERC-6672 Multi-Redeemable NFT Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-6672 -/// Note: the ERC-165 identifier for this interface is 0x4dddf83f. -interface IERC6672 /* is IERC721 */ { - /// @dev This event emits when an NFT is redeemed. - event Redeem( - address indexed _operator, - uint256 indexed _tokenId, - address redeemer, - bytes32 _redemptionId, - string _memo - ); - - /// @dev This event emits when a redemption is canceled. - event Cancel( - address indexed _operator, - uint256 indexed _tokenId, - bytes32 _redemptionId, - string _memo - ); - - /// @notice Check whether an NFT is already used for redemption or not. - /// @dev - /// @param _operator The address of the operator of the redemption platform. - /// @param _redemptionId The identifier for a redemption. - /// @param _tokenId The identifier for an NFT. - /// @return Whether an NFT is already redeemed or not. - function isRedeemed(address _operator, bytes32 _redemptionId, uint256 _tokenId) external view returns (bool); - - /// @notice List the redemptions created by the given operator for the given NFT. - /// @dev - /// @param _operator The address of the operator of the redemption platform. - /// @param _tokenId The identifier for an NFT. - /// @return List of redemptions of speficic `_operator` and `_tokenId`. - function getRedemptionIds(address _operator, uint256 _tokenId) external view returns (bytes32[]); - - /// @notice Redeem an NFT - /// @dev - /// @param _redemptionId The identifier created by the operator for a redemption. - /// @param _tokenId The NFT to redeem. - /// @param _memo - function redeem(bytes32 _redemptionId, uint256 _tokenId, string _memo) external; - - /// @notice Cancel a redemption - /// @dev - /// @param _redemptionId The redemption to cancel. - /// @param _tokenId The NFT to cancel the redemption. - /// @param _memo - function cancel(bytes32 _redemptionId, uint256 _tokenId, string _memo) external; -} -``` - -### Metadata Extension - -The key format for the `redemptions` key-value pairs MUST be standardized as `operator-tokenId-redemptionId`, where `operator` is the operator wallet address, `tokenId` is the identifier of the token that has been redeemed, and `redemptionId` is the redemption identifier. The value of the key `operator-tokenId-redemptionId` is an object that contains the `status` and `description` of the redemption. - -- Redemption status, i.e. `status` - - The redemption status can have a more granular level, rather than just being a flag with a `true` or `false` value. For instance, in cases of physical goods redemption, we may require the redemption status to be either `redeemed`, `paid`, or `shipping`. It is RECOMMENDED to use a string enum that is comprehensible by both the operator and the marketplace or any other parties that want to exhibit the status of the redemption. - -- Description of the redemption, i.e. `description` - - The `description` SHOULD be used to provide more details about the redemption, such as information about the concert ticket, a detailed description of the action figures, and more. - -The **metadata extension** is OPTIONAL for [ERC-6672](./eip-6672.md) smart contracts (see "caveats", below). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent. - -```solidity -/// @title ERC-6672 Multi-Redeemable Token Standard, optional metadata extension -/// @dev See https://eips.ethereum.org/EIPS/eip-6672 -interface IERC6672Metadata /* is IERC721Metadata */ { - /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. - /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC - /// 3986. The URI may point to a JSON file that conforms to the "ERC-6672 - /// Metadata JSON Schema". - function tokenURI(uint256 _tokenId) external view returns (string); -} -``` - -This is the "[ERC-6672](./eip-6672.md) Metadata JSON Schema" referenced above. - -```json -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - } - }, - "redemptions": { - "operator-tokenId-redemptionId": { - "status": { - "type": "string", - "description": "The status of a redemption. Enum type can be used to represent the redemption status, such as redeemed, shipping, paid." - }, - "description": { - "type": "string", - "description": "Describes the object that has been redeemed for an NFT, such as the name of an action figure series name or the color of the product." - } - } - } -} -``` - -## Rationale - -### Key Choices for Redemption Flag and Status - -The combination of `_operator`, `_tokenId`, and `_redemptionId` is chosen as the key because it provides a clear and unique identifier for each redemption transaction. - -- Operator wallet address, i.e. `_operator` - - It's possible that there are more than one party who would like to use the same NFT for redemption. For example, MisterPunks NFTs are eligible to be redeemed for both Event-X and Event-Y tickets, and each event's ticket redemption is handled by a different operator. - -- Token identifier, i.e. `_tokenId` - - Each NFT holder will have different redemption records created by the same operator. Therefore, it's important to use token identifier as one of the keys. - -- Redemption identifier, i.e. `_redemptionId` - - Using `_redemptionId` as one of the keys enables NFT holders to redeem the same NFT to the same operator in multiple campaigns. For example, Operator-X has 2 campaigns, i.e. campaign A and campaign B, and both campaigns allow for MisterPunks NFTs to be redeemed for physical action figures. Holder of MisterPunk #7 is eligible for redemption in both campaigns and each redemption is recorded with the same `_operator` and `_tokenId`, but with different `_redemptionId`. - -## Backwards Compatibility - -This standard is compatible with [ERC-721](./eip-721.md). - -## Reference Implementation - -The reference implementation of Multi-Redeemable NFT can be found [here](../assets/eip-6672/contracts/ERC6672.sol). - - -## Security Considerations - -An incorrect implementation of [ERC-6672](./eip-6672.md) could potentially allow an unauthorized operator to access redemption flags owned by other operators, creating a security risk. As a result, an unauthorized operator could cancel the redemption process managed by other operators. Therefore, it is crucial for [ERC-6672](./eip-6672.md) implementations to ensure that only the operator who created the redemption, identified using `msg.sender`, can update the redemption flag using the `redeem()` and `cancel()` functions. It is also recommended to isolate the `redeem()` and `cancel()` functions from [ERC-721](./eip-721.md) approval models. - -This [ERC-6672](./eip-6672.md) token is compatible with [ERC-721](./eip-721.md), so wallets and smart contracts capable of storing and handling standard [ERC-721](./eip-721.md) tokens will not face the risk of asset loss caused by incompatible standard implementations. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6672.md diff --git a/EIPS/eip-6682.md b/EIPS/eip-6682.md index b83b3c01638de3..c8bd6e90a3de5a 100644 --- a/EIPS/eip-6682.md +++ b/EIPS/eip-6682.md @@ -1,159 +1 @@ ---- -eip: 6682 -title: NFT Flashloans -description: Minimal interface for ERC-721 NFT flashloans -author: out.eth (@outdoteth) -discussions-to: https://ethereum-magicians.org/t/eip-6682-nft-flashloans/13294 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-12 -requires: 20, 721, 3156 ---- - -## Abstract - -This standard is an extension of the existing flashloan standard ([ERC-3156](./eip-3156.md)) to support [ERC-721](./eip-721.md) NFT flashloans. It proposes a way for flashloan providers to lend NFTs to contracts, with the condition that the loan is repaid in the same transaction along with some fee. - -## Motivation - -The current flashloan standard, [ERC-3156](./eip-3156.md), only supports [ERC-20](./eip-20.md) tokens. ERC-721 tokens are sufficiently different from ERC-20 tokens that they require an extension of this existing standard to support them. - -An NFT flash loan could be useful in any action where NFT ownership is checked. For example, claiming airdrops, claiming staking rewards, or taking an in-game action such as claiming farmed resources. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Contract Interface - -```solidity -pragma solidity ^0.8.19; - -interface IERC6682 { - /// @dev The address of the token used to pay flash loan fees. - function flashFeeToken() external view returns (address); - - /// @dev Whether or not the NFT is available for a flash loan. - /// @param token The address of the NFT contract. - /// @param tokenId The ID of the NFT. - function availableForFlashLoan(address token, uint256 tokenId) external view returns (bool); -} -``` - -The `flashFeeToken` function MUST return the address of the token used to pay flash loan fees. - -If the token used to pay the flash loan fees is ETH then `flashFeeToken` MUST return `address(0)`. - -The `availableForFlashLoan` function MUST return whether or not the `tokenId` of `token` is available for a flashloan. If the `tokenId` is not currently available for a flashloan `availableForFlashLoan` MUST return `false` instead of reverting. - -Implementers `MUST` also implement `IERC3156FlashLender`. - -## Rationale - -The above modifications are the simplest possible additions to the existing flashloan standard to support NFTs. - -We choose to extend as much of the existing flashloan standard ([ERC-3156](./eip-3156.md)) as possible instead of creating a wholly new standard because the flashloan standard is already widely adopted and few changes are required to support NFTs. - -In most cases, the handling of fee payments will be desired to be paid in a separate currency to the loaned NFTs because NFTs themselves cannot always be fractionalized. Consider the following example where the flashloan provider charges a 0.1 ETH fee on each NFT that is flashloaned; The interface must provide methods that allow the borrower to determine the fee rate on each NFT and also the currency that the fee should be paid in. - -## Backwards Compatibility - -This EIP is fully backwards compatible with [ERC-3156](./eip-3156.md) with the exception of the `maxFlashLoan` method. This method does not make sense within the context of NFTs because NFTs are not fungible. However it is part of the existing flashloan standard and so it is not possible to remove it without breaking backwards compatibility. It is RECOMMENDED that any contract implementing this EIP without the intention of supporting ERC-20 flashloans should always return `1` from `maxFlashLoan`. The `1` reflects the fact that only one NFT can be flashloaned per `flashLoan` call. For example: - -```solidity -function maxFlashLoan(address token) public pure override returns (uint256) { - // if a contract also supports flash loans for ERC20 tokens then it can - // return some value here instead of 1 - return 1; -} -``` - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.19; - -import "../interfaces/IERC20.sol"; -import "../interfaces/IERC721.sol"; -import "../interfaces/IERC3156FlashBorrower.sol"; -import "../interfaces/IERC3156FlashLender.sol"; -import "../interfaces/IERC6682.sol"; - -contract ExampleFlashLender is IERC6682, IERC3156FlashLender { - uint256 internal _feePerNFT; - address internal _flashFeeToken; - - constructor(uint256 feePerNFT_, address flashFeeToken_) { - _feePerNFT = feePerNFT_; - _flashFeeToken = flashFeeToken_; - } - - function flashFeeToken() public view returns (address) { - return _flashFeeToken; - } - - function availableForFlashLoan(address token, uint256 tokenId) public view returns (bool) { - // return if the NFT is owned by this contract - try IERC721(token).ownerOf(tokenId) returns (address result) { - return result == address(this); - } catch { - return false; - } - } - - function flashFee(address token, uint256 tokenId) public view returns (uint256) { - return _feePerNFT; - } - - function flashLoan(IERC3156FlashBorrower receiver, address token, uint256 tokenId, bytes calldata data) - public - returns (bool) - { - // check that the NFT is available for a flash loan - require(availableForFlashLoan(token, tokenId), "IERC6682: NFT not available for flash loan"); - - // transfer the NFT to the borrower - IERC721(token).safeTransferFrom(address(this), address(receiver), tokenId); - - // calculate the fee - uint256 fee = flashFee(token, tokenId); - - // call the borrower - bool success = - receiver.onFlashLoan(msg.sender, token, tokenId, fee, data) == keccak256("ERC3156FlashBorrower.onFlashLoan"); - - // check that flashloan was successful - require(success, "IERC6682: Flash loan failed"); - - // check that the NFT was returned by the borrower - require(IERC721(token).ownerOf(tokenId) == address(this), "IERC6682: NFT not returned by borrower"); - - // transfer the fee from the borrower - IERC20(flashFeeToken()).transferFrom(msg.sender, address(this), fee); - - return success; - } - - function maxFlashLoan(address token) public pure override returns (uint256) { - // if a contract also supports flash loans for ERC20 tokens then it can - // return some value here instead of 1 - return 1; - } - - function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) { - return this.onERC721Received.selector; - } -} -``` - -## Security Considerations - -It's possible that the `flashFeeToken` method could return a malicious contract. Borrowers who intend to call the address that is returned from the `flashFeeToken` method should take care to ensure that the contract is not malicious. One way they could do this is by verifying that the returned address from `flashFeeToken` matches that of a user input. - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6682.md diff --git a/EIPS/eip-67.md b/EIPS/eip-67.md index 3bea904a084028..27fa177273e04a 100644 --- a/EIPS/eip-67.md +++ b/EIPS/eip-67.md @@ -1,80 +1 @@ ---- -eip: 67 -title: URI Scheme with Metadata, Value and Bytecode -description: Format for encoding transactions into a URI -author: Alex Van de Sande (@alexvansande) -discussions-to: https://github.com/ethereum/EIPs/issues/67 -status: Withdrawn -type: Standards Track -category: ERC -created: 2016-02-17 -withdrawal-reason: Superseded by EIP-681 ---- - -## Abstract - -This proposal (inspired by BIP 21) defines a format for encoding a transaction into a URI, including a recipient, number of ethers (possibly zero), and optional bytecode. - -## Motivation - -Imagine these scenarios: - - * An exchange or a instant converter like ShapeShift wants to create a single Ethereum address for payments that will be converted into credit in their internal system or output bitcoin to an address. - * A store wants to show a QR code to a client that will pop up a payment for exactly 12.34 ethers, which contains metadata on the product being bought. - * A betting site wants to provide a link that the user can click on his site and it will open a default Ethereum wallet and execute a specific contract with given parameters. - * A dapp in Mist wants to simply ask the user to sign a transaction with a specific ABI in a single call. - - -In all these scenarios, the provider wants to internally set up a transaction, with a recipient, an associated number of ethers (or none) and optional bytecode, all without requiring any fuss from the end user that is expected simply to choose a sender and authorise the transaction. - -Currently implementations for this are wonky: ShapeShift creates tons of temporary addresses and uses an internal system to check which one correspond to which metadata, there isn't any standard way for stores that want payment in ether to put specific metadata about price on the call and any app implementing contracts will have to use different solutions depending on the client they are targeting. - -The proposal goes beyond address, and also includes optional bytecode and value. Of course this would make the link longer, but it should not be something visible to the user. Instead it should be shown as a visual code (QR or otherwise), a link, or some other way to pass the information. - -If properly implemented in all wallets, this should make execution of contracts directly from wallets much simpler as the wallet client only needs to put the bytecode obtained by reading the QR code. - -## Specification - -If we follow the bitcoin standard, the result would be: - -``` - ethereum:
[?value=][?gas=][?data=] -``` - -Other data could be added, but ideally the client should take them from elsewhere in the blockchain, so instead of having a `label` or a `message` to be displayed to the users, these should be read from an identity system or metadata on the transaction itself. - -### Example 1 - -Clicking this link would open a transaction that would try to send _5 unicorns_ to address _deadbeef_. The user would then simply approve, based on each wallet UI. - -``` - ethereum:0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7?gas=100000&data=0xa9059cbb00000000000000000000000000000000000000000000000000000000deadbeef0000000000000000000000000000000000000000000000000000000000000005 -``` - -#### Without Bytecode - -Alternatively, the bytecode could be generated by the client and the request would be in plain text: - -``` - ethereum:
[?value=][?gas=][?function=nameOfFunction(param)] -``` - -### Example 2 - -This is the same function as above, to send 5 unicorns from he sender to _deadbeef_, but now with a more readable function, which the client converts to bytecode. - -``` - ethereum:0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7?gas=100000&function=transfer(address 0xdeadbeef, uint 5) -``` - -## Rationale - -TODO - -## Security Considerations - -TODO - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-67.md diff --git a/EIPS/eip-6734.md b/EIPS/eip-6734.md index bacfe8f19eff8a..0310c24900f5df 100644 --- a/EIPS/eip-6734.md +++ b/EIPS/eip-6734.md @@ -1,529 +1 @@ ---- -eip: 6734 -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 -type: Standards Track -category: ERC -created: 2023-03-20 -requires: 155, 3220 ---- - -## Abstract - -The document describes a JSON token list that ensures that two or more Layer 1, Layer 2, or Sidechains can identify tokens from a different Layer 1, Layer 2, or Sidechain. - -## Motivation - -This particular work by the L2 WG of the EEA Communities Projects managed by OASIS, an open-source initiative, is motivated by a significant challenge around the definition and listing of tokens on Layer 1 (L1), Layer 2 (L2), and Sidechain systems. Note that for simplicity, this document we will collectively refer to L1, L2 and Sidechain systems as chains below since the challenge described below is valid across all such systems: - -* Consensus on the "canonical" token on chain B that corresponds to some token on chain A. When one wants to bridge token X from chain A to chain B, one must create some new representation of the token on chain B. It is worth noting that this problem is not limited to L2s -- every chain connected via bridges must deal with the same issue. - -Related to the above challenge is the standardization around lists of bridges and their routes across different chains. This will be addressed in a separate document. - -Note that both of these issues are fundamental problems for the current multi-chain world. - -Therefore, the goal of this document is to help token users to operationalize and disambiguate the usage of a token in their systems. - -For lists of canonical tokens, L2s currently maintain their own customized versions of the Uniswap token list. For example, Arbitrum maintains a token list with various custom extensions. Optimism also maintains a custom token list, but with different extensions. It should be noted that both of these custom extensions refer to the bridge that these tokens can be carried through. However, these are not the only bridges that the tokens can be carried through, which means that bridges and token lists should be separated. Also note that currently, both Optimism and Arbitrum base "canonicity" on the token name + symbol pair. - -An example of an Arbitrum token entry is given below: - -``` -{ -logoURI: "https://assets.coingecko.com/coins/images/13469/thumb/1inch-token.png?1608803028", -chainId: 42161, -address: "0x6314C31A7a1652cE482cffe247E9CB7c3f4BB9aF", -name: "1INCH Token", -symbol: "1INCH", -decimals: 18, -extensions: { - bridgeInfo: { - 1: { - tokenAddress: "0x111111111117dc0aa78b770fa6a738034120c302", - originBridgeAddress: "0x09e9222e96e7b4ae2a407b98d48e330053351eee", - destBridgeAddress: "0xa3A7B6F88361F48403514059F1F16C8E78d60EeC" - } - } - } -} -``` - -This standard will build upon the current framework and augment it with concepts from [Decentralized Identifiers (DIDs)](https://www.w3.org/TR/2022/REC-did-core-20220719/) based on the JSON linked data model [JSON-LD](https://www.w3.org/TR/2020/REC-json-ld11-20200716/) such as resolvable unique resource identifiers (URIs) and JSON-LD schemas which enable easier schema verification using existing tools. - -Note that a standard for defining tokens is beyond the scope of this document. - -## Specification - -### Keywords: - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [[RFC2119](https://www.rfc-editor.org/rfc/rfc2119)] when, and only when, they appear in all capitals, as shown here. - -### Typographical Convention: Requirement Ids - -A requirement is uniquely identified by a unique ID composed of its requirement level followed by a requirement number, as per convention **[RequirementLevelRequirementNumber]**. -There are four requirement levels that are coded in requirement ids as per below convention: - -**[R]** - The requirement level for requirements which IDs start with the letter _R_ is to be interpreted as **MUST** as described in [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). \ -**[D]** - The requirement level for requirements which IDs start with the letter _D_ is to be interpreted as **SHOULD** as described in [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). \ -**[O]** - The requirement level for requirements which IDs start with the letter _O_ is to be interpreted as **MAY** as described in [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). - -Note that requirements are uniquely numbered in ascending order within each requirement level. - -Example : It should be read that [R1] is an absolute requirement of the specification whereas [D1] is a recommendation and [O1] is truly optional. - - **[R1]** -The following data elements MUST be present in a canonical token list: - -* type -* tokenListId -* name -* createdAt -* updatedAt -* versions -* tokens - -Note, that the detailed definition of the data elements in [[R1]](#r1) along with descriptions and examples are given in the schema itself below. - -[[R1]](#r1) testability: See suggested test fixtures for the data schema below. - - **[R2]** -The tokens data element is a composite which MUST minimally contain the following data elements: - -* chainId -* chainURI -* tokenId -* tokenType -* address -* name -* symbol -* decimals -* createdAt -* updatedAt - -Note, that the detailed definition of the data elements in [[R2]](#r2) along with descriptions and examples are given in the schema itself below. - -[[R2]](#r2) testability: See suggested test fixtures for this documents' data schema below. - - **[D1]** -All other data elements of the schema SHOULD be included in a representation of a canonical token list. - -[[D1]](#d1) testability: See suggested test fixtures for this documents' data schema below. - - **[CR1]>[D1]** -If the extension data elements is used, the following data elements MUST be present in the schema representation: - -* rootChainId -* rootChainURI -* rootAddress - -Note, that the detailed definition of the data elements in [[D1]](#d1) and [[CR1]>[D1]](#cr1d1) along with descriptions and examples are given in the schema itself below. - -[[CR1]>[D1]](#cr1d1) testability: See suggested test fixtures for this documents' data schema below. - - **[R3]** -All properties in the schema identified in the description to be a Universal Resource Identifier (URI) MUST follow in their semantics [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986). - -[[R3]](#r3) testability: All requirements for [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) are testable. - - **[R4]** -The chainId property utilized MUST allow for the requirements of the [EIP-155](./eip-155.md) standard to be met. - -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. - - **[O1]** -The `humanReadableTokenSymbol` property MAY be used. - -[[O1]](#o1) testability: A data property is always implementable in a schema. - - **[CR2]>[O1]** -The `humanReadableTokenSymbol` property MUST be constructed as the hyphenated concatenation of first the `tokenSymbol` and then the `chainId`. - -An example would be: - -``` -"tokenSymbol" = ETH; -"chainId" = 1; -"humanReadableTokenSymbol" = ETH-1; -``` - -[[CR2]>[O1]](#cr2o1) testability: `humanReadableTokenSymbol` can be parsed and split based on existing open source packages and the result compared to the `tokenSymbol` and `chainId` used in the data schema. - - -The schema for a canonical token list is given below as follows and can be utilized as a JSON-LD schema if a JSON-LD context file is utilized (see [[W3C-DID]](https://www.w3.org/TR/2022/REC-did-core-20220719/) for a concrete example in the context of a standard): - -``` -{ - "$id": "https://github.com/eea-oasis/l2/schemas/CanonicalTokenList.json", - "$schema": "https://json-schema.org/draft-07/schema#", - "$comment": "{\"term\": \"CanonicalTokenList\", \"@id\": \"https://github.com/eea-oasis/l2#CanonicalTokenList\"}", - "title": "CanonicalTokenList", - "description": "Canonical Token List", - "type": "object", - "required": [ - "type", - "tokenListId", - "name", - "createdAt", - "updatedAt", - "versions", - "tokens" - ], - "properties": { - "@context": { - "type": "array" - }, - "type": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array" - } - ], - "examples": ["CanonicalTokenList"] - }, - "tokenListId": { - "$comment": "{\"term\": \"tokenListId\", \"@id\": \"https://schema.org/identifier\"}", - "title": "tokenListId", - "description": "A resolvable URI to the publicly accessible place where this list can be found following the RFC 3986 standard.", - "type": "string", - "examples": ["https://ipfs.io/ipns/k51qzi5uqu5dkkciu33khkzbcmxtyhn376i1e83tya8kuy7z9euedzyr5nhoew"] - }, - "name": { - "$comment": "{\"term\": \"name\", \"@id\": \"https://schema.org/name\"}", - "title": "name", - "description": "Token List name", - "type": "string", - "examples": ["Aggregate Canonical Token List"] - }, - "logoURI": { - "$comment": "{\"term\": \"logoURI\", \"@id\": \"https://schema.org/identifier\"}", - "title": "logoURI", - "description": "URI or URL of the token list logo following the RFC 3986 standard", - "type": "string", - "examples": ["https://ipfs.io/ipns/k51qzi5uqu5dh5kbbff1ucw3ksphpy3vxx4en4dbtfh90pvw4mzd8nfm5r5fnl"] - }, - "keywords": { - "$comment": "{\"term\": \"keywords\", \"@id\": \"https://schema.org/DefinedTerm\"}", - "title": "keywords", - "description": "List of key words for the token list", - "type": "array", - "examples": [Aggregate Token List] - }, - "createdAt": { - "$comment": "{\"term\": \"createdAt\", \"@id\": \"https://schema.org/datePublished\"}", - "title": "createdAt", - "description": "Date and time token list was created", - "type": "string", - "examples": ["2022-05-08"] - }, - "updatedAt": { - "$comment": "{\"term\": \"updatedAt\", \"@id\": \"https://schema.org/dateModified\"}", - "title": "updatedAt", - "description": "Date and time token list was updated", - "type": "string", - "examples": ["2022-05-09"] - }, - "versions": { - "$comment": "{\"term\": \"versions\", \"@id\": \"https://schema.org/version\"}", - "title": "versions", - "description": "Versions of the canonical token list", - "type": "array", - "items": { - "type":"object", - "required":[ - "major", - "minor", - "patch" - ], - "properties": { - "major": { - "$comment": "{\"term\": \"major\", \"@id\": \"https://schema.org/Number\"}", - "title": "major", - "description": "Major Version Number of the Token List", - "type": "integer", - "examples": [1] - }, - "minor": { - "$comment": "{\"term\": \"minor\", \"@id\": \"https://schema.org/Number\"}", - "title": "minor", - "description": "Minor Version Number of the Token List", - "type": "integer", - "examples": [1] - }, - "patch": { - "$comment": "{\"term\": \"patch\", \"@id\": \"https://schema.org/Number\"}", - "title": "patch", - "description": "Patch Number of the Token List", - "type": "integer", - "examples": [1] - }, - } - } - }, - "tokens": { - "title": "Listed Token Entry", - "description": "Listed Token Entry", - "type": "array", - "items": { - "type":"object", - "required": [ - "chainId", - "chainURI", - "tokenId", - "tokenType", - "address", - "name", - "symbol", - "decimals", - "createdAt", - "updatedAt" - ], - "properties": { - "chainId": { - "$comment": "{\"term\": \"chainId\", \"@id\": \"https://schema.org/identifier\"}", - "title": "chainId", - "description": "The typically used number identifier for the chain on which the token was issued.", - "type": "number", - "examples": [137] - }, - "chainURI": { - "$comment": "{\"term\": \"chainURI\", \"@id\": \"https://schema.org/identifier\"}", - "title": "chainURI", - "description": "A resolvable URI to the genesis block of the chain on which the token was issued following the RFC 3986 standard.", - "type": "string" - "examples": ["https://polygonscan.com/block/0"] - }, - "genesisBlockHash": { - "$comment": "{\"term\": \"genesisBlockHash\", \"@id\": \"https://schema.org/sha256\"}", - "title": "genesisBlockHash", - "description": "The hash of the genesis block of the chain on which the token was issued.", - "type": "string", - "examples": ["0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b"] - }, - "tokenIssuerId": { - "$comment": "{\"term\": \"tokenIssuerId\", \"@id\": \"https://schema.org/identifier\"}", - "title": "tokenIssuerId", - "description": "A resolvable URI identifying the token issuer following the RFC 3986 standard.", - "type": "string", - "examples": ["https://polygonscan.com/address/0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b"] - }, - "tokenIssuerName": { - "$comment": "{\"term\": \"tokenIssuerName\", \"@id\": \"https://schema.org/name\"}", - "title": "tokenIssuerName", - "description": "The name oof the token issuer.", - "type": "string" - "examples": ["Matic"] - }, - "tokenId": { - "$comment": "{\"term\": \"tokenId\", \"@id\": \"https://schema.org/identifier\"}", - "title": "tokenId", - "description": "A resolvable URI of the token following the RFC 3986 standard to for example the deployment transaction of the token, or a DID identifying the token and its issuer.", - "type": "string", - "example": ["https://polygonscan.com/address/0x0000000000000000000000000000000000001010"] - }, - "tokenType": { - "$comment": "{\"term\": \"tokenType\", \"@id\": \https://schema.org/StructuredValue\"}", - "title": "tokenType", - "description": "Describes the type of token.", - "type": "array" - "examples"[["fungible","transferable"]] - }, - "tokenDesc": { - "$comment": "{\"term\": \"tokenDesc\", \"@id\": \"https://schema.org/description\"}", - "title": "tokenDesc", - "description": "Brief description of the token and its functionality.", - "type": "string", - "examples": ["Protocol Token for the Matic Network"] - }, - "standard": { - "$comment": "{\"term\": \"standard\", \"@id\": \"https://schema.org/citation\"}", - "title": "standard", - "description": "A resolvable URI to the description of the token standard.", - "type": "string", - "examples": ["https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md"] - }, - "address": { - "$comment": "{\"term\": \"address\", \"@id\": \"https://schema.org/identifier\"}", - "title": "address", - "description": "Address of the token smart contract.", - "type": "string", - "examples": ["0x0000000000000000000000000000000000001010"] - }, - "addressType": { - "$comment": "{\"term\": \"address\", \"@id\": \"https://schema.org/Intangible\"}", - "title": "addressType", - "description": "AddressType of the token smart contract.", - "type": "string", - "examples": ["MaticNameSpace"] - }, - "addressAlg": { - "$comment": "{\"term\": \"addressAlg\", \"@id\": \"https://schema.org/algorithm\"}", - "title": "addressAlg", - "description": "Algorithm used to create the address e.g. CREATE2 or the standard ethereum address construction which is the last 40 characters/20 bytes of the Keccak-256 hash of a secp256k1 public key.", - "type": "string", - "examples": ["CREATE2"] - }, - "name": { - "$comment": "{\"term\": \"name\", \"@id\": \"https://schema.org/name\"}", - "title": "name", - "description": "Token name.", - "type": "string", - "examples": ["Matic"] - }, - "symbol": { - "$comment": "{\"term\": \"symbol\", \"@id\": \"https://schema.org/currency\"}", - "title": "symbol", - "description": "Token symbol e.g. ETH.", - "type": "string", - "examples": ["MATIC"] - }, - "humanReadableTokenSymbol": { - "$comment": "{\"term\": \"humanReadableTokenSymbol\", \"@id\": \"https://schema.org/currency\"}", - "title": "humanReadableTokenSymbol", - "description": "A Token symbol e.g. ETH, concatenated with the `chainId` the token was issued on or bridged to, e.g. ETH-1", - "type": "string", - "examples": ["MATIC-137"] - }, - "decimals": { - "$comment": "{\"term\": \"decimals\", \"@id\": \"https://schema.org/Number\"}", - "title": "decimals", - "description": "Allowed number of decimals for the listed token. This property may be named differently by token standards e.g. granularity for ERC-777", - "type": "integer", - "examples": [18] - }, - "logoURI": { - "$comment": "{\"term\": \"logoURI\", \"@id\": \"https://schema.org/identifier\"}", - "title": "logoURI", - "description": "URI or URL of the token logo following the RFC 3986 standard.", - "type": "string" - "examples": ["https://polygonscan.com/token/images/matic_32.png"] - }, - "createdAt": { - "$comment": "{\"term\": \"createdAt\", \"@id\": \"https://schema.org/datePublished\"}", - "title": "createdAt", - "description": "Date and time token was created", - "type": "string", - "examples": ["2020-05-31"] - }, - "updatedAt": { - "$comment": "{\"term\": \"updatedAt\", \"@id\": \"https://schema.org/dateModified\"}", - "title": "updatedAt", - "description": "Date and time token was updated", - "type": "string" - "examples": ["2020-05-31"] - }, - "extensions": { - "title": "extensions", - "description": "Extension to the token list entry to specify an origin chain if the token entry refers to another chain other than the origin chain of the token", - "type": "array", - "items": { - "type":"object", - "required": [ - "rootChainId", - "rootChainURI", - "rootAddress", - ], - "properties": { - "rootChainId": { - "$comment": "{\"term\": \"rootChainId\", \"@id\": \"https://schema.org/identifier\"}", - "title": "rootChainId", - "description": "The typically used number identifier for the root chain on which the token was originally issued.", - "type": "number", - "examples": [137] - }, - "rootChainURI": { - "$comment": "{\"term\": \"rootChainURI\", \"@id\": \"https://schema.org/identifier\"}", - "title": "rootChainURI", - "description": "A resolvable URI to the genesis block of the root chain on which the token was originally issued following the RFC 3986 standard.", - "type": "string", - "examples": ["https://polygonscan.com/block/0"] - }, - "rootAddress": { - "$comment": "{\"term\": \"rootAddress\", \"@id\": \"https://schema.org/identifier\"}", - "title": "rootAddress", - "description": "Root address of the token smart contract.", - "type": "string", - "examples": ["0x0000000000000000000000000000000000001010"] - } - } - } - } - } - } - } - }, - "additionalProperties": false, -} -``` - -Data Schema Testability: As the above data schema follows a JSON/JSON-LD schema format, and since such formats are known to be testable for schema conformance (see for example the W3C CCG Traceability Work Item), the above data schema is testable. - - -### Conformance - -This section describes the conformance clauses and tests required to achieve an implementation that is provably conformant with the requirements in this document. - -#### Conformance Targets - -This document does not yet define a standardized set of test-fixtures with test inputs for all MUST, SHOULD, and MAY requirements with conditional MUST or SHOULD requirements. - -A standardized set of test-fixtures with test inputs for all MUST, SHOULD, and MAY requirements with conditional MUST or SHOULD requirements is intended to be published with the next version of the standard. - -#### Conformance Levels - -This section specifies the conformance levels of this standard. The conformance levels offer implementers several levels of conformance. These can be used to establish competitive differentiation. - -This document defines the conformance levels of a canonical token list as follows: - -* **Level 1:** All MUST 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 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]** -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). - - **[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. - -[[R5]](#r5) 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, be described, be built and implemented and results can be recorded as required in [[R5]](#r5). - - -## Rationale - -This specification is extending and clarifying current custom lists such as from Arbitrum and Optimism as referenced in the [Motivation](#motivation) or the Uniswap Tokenlist Project to improve clarity, security and encourage adoption by non-Web3 native entities. - -The specification is utilizing the current JSON-LD standard to describe a token list to allow for easy integrations with Self-Sovereign-Identity frameworks such as W3C DID and W3C Verifiable Credential standards that allow for interoperability across L2s, Sidechains and L1s when identifying token list relevant entities such as Token Issuers. In addition, being compatible to W3C utilized frameworks allows implementers to use existing tooling around JSON-LD, W3C DIDs and W3C Verifiable Credentials. The choice of referencing known data property definitions from schema.org further disambiguates the meaning and usage of terms. - -## Security Considerations - -There are no additional security requirements apart from the warnings that URIs utilized in implementations of this standard might be direct to malicious resources such as websites, and that implementers should ensure that data utilized for a canonical token list is secure and correct. Since this standard is focused on a data schema and its data properties there are no additional security considerations from for example homoglyph attacks (see [CVE-2021-42574 (2021-10-25T12:38:28)](https://nvd.nist.gov/vuln/detail/CVE-2021-42574)). - -### Security Considerations: Data Privacy - -The standard does not set any requirements for compliance to jurisdiction legislation/regulations. It is the responsibility of the implementer to comply with applicable data privacy laws. - -### Security Considerations: Production Readiness - -The standard does not set any requirements for the use of specific applications/tools/libraries etc. The implementer should perform due diligence when selecting specific applications/tools/libraries. - -### Security Considerations: Internationalization and Localization - -The standard encourages implementers to follow the [W3C "Strings on the Web: Language and Direction Metadata" best practices guide](https://www.w3.org/TR/2022/DNOTE-string-meta-20220804/) for identifying language and base direction for strings used on the Web wherever appropriate. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6734.md diff --git a/EIPS/eip-6735.md b/EIPS/eip-6735.md index aa28a59a386a76..fb5884672b3ff1 100644 --- a/EIPS/eip-6735.md +++ b/EIPS/eip-6735.md @@ -1,189 +1 @@ ---- -eip: 6735 -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 -type: Standards Track -category: ERC -created: 2022-03-20 -requires: 55 ---- - -## Abstract - -The document describes the minimal set of business and technical prerequisites, functional and non-functional requirements for Aliasing of EVM based Addresses that when implemented ensures that two or more Layer 1, Layer 2, or Sidechains can identify and translate EVM based addresses from different Layer 1, Layer 2, or Sidechains. - -## Motivation - -The members of the L2 WG of the EEA Communities Project managed by OASIS have recognized that the ability to deterministically derive addresses of a digital asset or an externally owned account (EOA) in EVM based execution frameworks for L1s, L2s, Sidechains based on an origin chain of an asset or EOA, known as address aliasing, simplifies interoperability between EVM based L1s, L2s, and Sidechains because: - - * It allows messages from chain A (source chain) to unambiguously address asset A (smart contract) or EOA on chain Y (target chain), if asset A or EOA exists on Chain X and on Chain Y. - * It allows a user to deterministically verify the source chain of a message, and, if required, directly verify the origin chain of asset A or EOA and its state on its origin chain utilizing a canonical token list of the (message) source chain. - -The ability to unambiguously, and deterministically, relate an address for a digital asset (smart contract) or an externally owned account (EOA) between EVM based L1s, L2s, and Sidechains where this digital asset or EOA exists, also known as address aliasing, is critical prerequisite for interoperability between EVM based L1s, L2s, and Sidechains. However, there is currently no way to do so in a standardized way -- imagine every internet service provider were to define its own IP addresses. - -Hence, the L2 WG of the EEA Communities Project managed by OASIS, an open-source initiative, intends for this document to establish an unambiguous and deterministic standard for EVM based address aliasing based on the concept of root → leaf where an address alias is derived based on the address on the origin chain and an offset which is an immutable characteristic of the origin chain. - -See Figure 1 for the conceptual root → leaf design with offset. - -![Fig1](../assets/eip-6735/address-aliasing-root-leaf-design.png) - -Figure 1: Root → Leaf address aliasing concept using an chain immanent characteristics from L1 to L2 and L3 and back. - -Alternative Figure 1 Description: The figure describes conceptually how (interoperability) messages from source to target chain utilize address aliasing. At the bottom an EVM based L1 is uni-directionally connected to three EVM based L2s -- A, B, and C -- each with an alias of L1 address + L1 Offset. In addition, A is uni-directionally connected to B with an alias of L1 address + L1 offset + A offset. B is uni-directionally connected to an EVM-based Layer 3 or L3 with an alias of L1 address + L1 offset + B offset signaling that the address is anchored on L1 via the L2 B. And finally D is uni-directionally connected to C via the alias L1 address + L1 offset + B offset plus D offset indicating the asset chain of custody from L1 to B to D to C. - -To further clarify the connections between the different possible paths an asset can take from an L1 to different L2/L3s and the `relativeAddress` of that asset, we visually highlight in red the path from the EVM based L1 to the B L2, to the D L3, and finally to the C L2. - -![Fig2](../assets/eip-6735/visual-Highlight-Path-Red-evm-based-aliasing..png) - -Figure 2: Visually highlighted path in red from the EVM based L1 to the B L2, to the D L3, and finally to the C L2. - -Alternative Figure 1 Description: The figure is the same as Figure 1. However, the uni-directional connections between the EVM based L1 to the L2 B, to the L3 D, and finally to the L2 C are highlighted in red. - -Note, that address aliasing between non-EVM and EVM-based L1s, L2s, and Sidechains, and between non-EVM-based L1s, L2s, and Sidechains is out of scope of this document. - -## Specification - -### Typographical Convention: Requirement Ids - -A requirement is uniquely identified by a unique ID composed of its requirement level followed by a requirement number, as per convention **[RequirementLevelRequirementNumber]**. -There are four requirement levels that are coded in requirement ids as per below convention: - -**[R]** - The requirement level for requirements which IDs start with the letter _R_ is to be interpreted as **MUST** as described in [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). \ -**[D]** - The requirement level for requirements which IDs start with the letter _D_ is to be interpreted as **SHOULD** as described in [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). \ -**[O]** - The requirement level for requirements which IDs start with the letter _O_ is to be interpreted as **MAY** as described in [RFC2119](https://www.rfc-editor.org/rfc/rfc2119). - -Note that requirements are uniquely numbered in ascending order within each requirement level. - -Example : It should be read that [R1] is an absolute requirement of the specification whereas [D1] is a recommendation and [O1] is truly optional. - -The requirements below are only valid for EVM based L1s, L2, or Sidechains. Address aliasing for non-EVM systems is out of scope of this document. - - **[R1]** -An address alias -- `addressAlias` -- to be used between Chain A and Chain B MUST be constructed as follows: -`addressAlias (Chain A) = offsetAlias (for Chain A) relativeAddress (on Chain A) offsetAlias (for Chain A)` - -[[R1]](#r1) testability: `addressAlias` can be parsed and split using existing open source packages and the result compared to known `addressAlias` and `relativeAddress` used in the construction. - - **[R2]** -The `offsetAlias` of a chain MUST be `0xchainId00000000000000000000000000000000chainId` - -[[R2]](#r2) testability: `offsetAlias` can be parsed and split using existing open source packages and the result compared to known `chainId` used in the construction. - - **[R3]** -The `chainId` used in the `offsetAlias` MUST NOT be zero (0) - -[[R3]](#r3) testability: A `chainId` is a numerical value and can be compared to `0`. - - **[R4]** -The `chainId` used in the `offsetAlias` MUST be 8 bytes. - -[[R4]](#r4) testability: The length of the `chainId` string can be converted to bytes and then compared to `8`. - - **[R5]** -In case the `chainId` has less than 16 digits the `chainId` MUST be padded with zeros to 16 digits. - -For example the `chainId` of Polygon PoS is `137`, with the current list of EVM based `chainId`s to be found at chainlist.org, and its `offsetAlias` is `0x0000000000000137000000000000000000000000000000000000000000000137`. - -[[R5]](#r5) testability: `chainId` can be parsed and split using existing open source packages and the result compared to known `chainId` used in the construction. Subsequently the number of zeros used in the padding can be computed and compared to the expected number of zeros for the padding. - - **[R6]** -The `offsetAlias`for Ethereum Mainnet as the primary anchor of EVM based chains MUST be `0x1111000000000000000000000000000000001111` due to current adoption of this offset by existing L2 solutions. - -An example of address alias for the USDC asset would be `addressAlias = 0x1111A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB481111` - -[[R6]](#r6) testability: This requirement is a special case of [[R1]](#r1). Hence, it is testable. - - **[R7]** -The `relativeAddress` of an Externally Owned Account (EOA) or Smart Contract on a chain MUST either be the smart contract or EOA address of the origin chain or a `relativeAddress` of an EOA or Smart Contract from another chain. - -An example of the former instance would be the relative address of wrapped USDC, `relativeAddress = 0x1111A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB481111`, and an example of the latter would be the relative address of wrapped USDC on Polygon, `relativeAddress = 0x00000000000001371111A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB4811110000000000000137`. - -Finally, an example of an address alias for a message to another L1, L2, or Sidechain for wrapped USDC from Ethereum on Arbitrum would be: - -``` -addressAlias = 0x00000000000421611111A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB4811110000000000042161 -``` - -[[R7]](#r7) testability: Since this document is dealing with EVM-based systems with multiple live implementations, there are multiple known methods of how to verify if an address belongs to an EOA or a smart contract. - - **[R8]** -The order of the `offsetAlias`es in an `addressAlias` MUST be ordered from the `offSetAlias` of the root chain bracketing the `relativeAddress` on the root chain through the ordered sequence of `offsetAlias`es of the chains on which the digital asset exists. - -For example, a valid `addressAlias` of an asset on chain A bridged to chain B and subsequently to chain C and that is to be bridged to yet another chain from chain C would be: - -``` -addressAlias = chainId(C) chainId(B) chainId(A) relativeAddress chainId(A) chainId(B) chainId(C) -``` - -However, the reverse order is invalid: - -``` -addressAlias = chainId(A) chainId(B) chainId(C) relativeAddress chainId(C) chainId(B) chainId(A) -``` - -[[R8]](#r8) testability: Since [[R1]](#r1) is testable and since [[R8]](#r8) is an order rule for the construction in [[R1]](#r1), which can be tested by applying logic operations on the output of [[R1]](#r1) tests, [[R8]](#r8) is testable. - -Note, that a proof that a given order is provably correct is beyond the scope of this document. - -### Conformance - -This section describes the conformance clauses and tests required to achieve an implementation that is provably conformant with the requirements in this document. - -#### Conformance Targets - -This document does not yet define a standardized set of test-fixtures with test inputs for all MUST, SHOULD, and MAY requirements with conditional MUST or SHOULD requirements. - -A standardized set of test-fixtures with test inputs for all MUST, SHOULD, and MAY requirements with conditional MUST or SHOULD requirements is intended to be published with the next version of the standard. - -#### Conformance Levels - -This section specifies the conformance levels of this standard. The conformance levels offer implementers several levels of conformance. These can be used to establish competitive differentiation. - -This document defines the conformance levels of EVM based Address Aliasing as follows: - -* **Level 1:** All MUST 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 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. - - **[D1]** -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. - -[[D1]](#d1) 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 [[D1]](#d1). - - **[R9]** -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. - -[[R9]](#r9) 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, be described, be built and implemented and results can be recorded as required in [[R9]](#r9). - -## Rationale - -The standard follows an already existing approach for address aliasing from Ethereum (L1) to EVM-based L2s such as Arbitrum and Optimism and between L2s, and extends and generalizes it to allow aliasing across any type of EVM-based network irrespective of the network type -- L1, L2 or higher layer networks. - -## Security Considerations - -### Data Privacy - -The standard does not set any requirements for compliance to jurisdiction legislation/regulations. It is the responsibility of the implementer to comply with applicable data privacy laws. - -### Production Readiness - -The standard does not set any requirements for the use of specific applications/tools/libraries etc. The implementer should perform due diligence when selecting specific applications/tools/libraries. - -There are security considerations as to the Ethereum-type addresses used in the construction of the `relativeAddress`. - -If the Ethereum-type address used in the `relativeAddress` is supposed to be an EOA, the target system/recipient should validate that the `codehash` of the source account is `NULL` such that no malicious code can be executed surreptitiously in an asset transfer. - -If the Ethereum-type address used in the `relativeAddress` is supposed to be a smart contract account representing an asset, the target system/recipient should validate that the `codehash` of the source account matches the `codehash` of the published smart contract solidity code to ensure that the source smart contract behaves as expected. - -Lastly, it is recommended that as part of the `relativeAddress` validation the target system performs an address checksum validation as defined in [ERC-55](./eip-55.md). - -### Internationalization and Localization - -Given the non-language specific features of EVM-based address aliasing, there are no internationalization/localization considerations. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6735.md diff --git a/EIPS/eip-6785.md b/EIPS/eip-6785.md index b02f62f48c4ca0..52738e544a1225 100644 --- a/EIPS/eip-6785.md +++ b/EIPS/eip-6785.md @@ -1,210 +1 @@ ---- -eip: 6785 -title: ERC-721 Utilities Information Extension -description: Provide easy access to information about the `utility` of an NFT via functions and the NFT's metadata -author: Otniel Nicola (@OT-kthd), Bogdan Popa (@BogdanKTHD) -discussions-to: https://ethereum-magicians.org/t/eip-6785-erc-721-utilities-extension/13568 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-27 -requires: 165, 721 ---- - -## Abstract - -This specification defines standard functions and an extension of the metadata schema that outlines what a -token's utility entails and how the utility may be used and/or accessed. -This specification is an optional extension of [ERC-721](./eip-721.md). - -## Motivation - -This specification aims to clarify what the utility associated with an NFT is and how to access this utility. -Relying on third-party platforms to obtain information regarding the utility of the NFT that one owns can lead to scams, -phishing or other forms of fraud. - -Currently, utilities that are offered with NFTs are not captured on-chain. We want the utility of an NFT to be part of -the metadata of an NFT. The metadata information would include: a) type of utility, b) description -of utility, c) frequency and duration of utility, and d) expiration of utility. This will provide transparency as to the -utility terms, and greater accountability on the creator to honor these utilities. - -As the instructions on how to access a given utility may change over time, there should be a historical record of these -changes for transparency. - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and -“OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Every contract compliant with [ERC-6785](./eip-6785.md) MUST implement the interface defined as follows: - -### Contract Interface - -```solidity -// @title NFT Utility description -/// Note: the EIP-165 identifier for this interface is ed231d73 - -interface IERC6785 { - - // Logged when the utility description URL of an NFT is changed - /// @notice Emitted when the utilityURL of an NFT is changed - /// The empty string for `utilityUri` indicates that there is no utility associated - event UpdateUtility(uint256 indexed tokenId, string utilityUri); - - /// @notice set the new utilityUri - remember the date it was set on - /// @dev The empty string indicates there is no utility - /// Throws if `tokenId` is not valid NFT - /// @param utilityUri The new utility description of the NFT - /// 4a048176 - function setUtilityUri(uint256 tokenId, string utilityUri) external; - - /// @notice Get the utilityUri of an NFT - /// @dev The empty string for `utilityUri` indicates that there is no utility associated - /// @param tokenId The NFT to get the user address for - /// @return The utility uri for this NFT - /// 5e470cbc - function utilityUriOf(uint256 tokenId) external view returns (string memory); - - /// @notice Get the changes made to utilityUri - /// @param tokenId The NFT to get the user address for - /// @return The history of changes to `utilityUri` for this NFT - /// f96090b9 - function utilityHistoryOf(uint256 tokenId) external view returns (string[] memory); -} -``` - -All functions defined as view MAY be implemented as pure or view - -Function `setUtilityUri` MAY be implemented as public or external. Also, the ability to set the `utilityUri` SHOULD be -restricted to the one who's offering the utility, whether that's the NFT creator or someone else. - -The event `UpdateUtility` MUST be emitted when the `setUtilityUri` function is called or any other time that the utility -of the token is changed, like in batch updates. - -The method `utilityHistoryOf` MUST reflect all changes made to the `utilityUri` of a tokenId, whether that's done -through `setUtilityUri` or by any other means, such as bulk updates - -The `supportsInterface` method MUST return true when called with `ed231d73` - -The original metadata SHOULD conform to the “ERC-6785 Metadata with utilities JSON Schema” which is a compatible -extension of the “ERC-721 Metadata JSON Schema” defined in ERC-721. - -“ERC-6785 Metadata with utilities JSON Schema” : - -```json -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - "utilities": { - "type": "object", - "required": [ - "type", - "description", - "t&c" - ], - "properties": { - "type": { - "type": "string", - "description": "Describes what type of utility this is" - }, - "description": { - "type": "string", - "description": "A brief description of the utility" - }, - "properties": { - "type": "array", - "description": "An array of possible properties describing the utility, defined as key-value pairs", - "items": { - "type": "object" - } - }, - "expiry": { - "type": "number", - "description": "The period of time for the validity of the utility, since the minting of the NFT. Expressed in seconds" - }, - "t&c": { - "type": "string", - "description": "" - } - } - } - } -} -``` - -## Rationale - -Since the `utilityUri` could contain information that has to be restricted to some level and could be dependent on an -off-chain tool for displaying said information, the creator needs the ability to modify it in the event the off-chain -tool or platform becomes unavailable or inaccessible. - -For transparency purposes, having a `utilityHistoryOf` method will make it clear how the `utilityUri` has changed over -time. - -For example, if a creator sells an NFT that gives holders a right to a video call with the creator, the metadata for -this utility NFT would read as follows: - -```json -{ - "name": "...", - "description": "...", - "image": "...", - "utilities": { - "type": "Video call", - "description": "I will enter a private video call with whoever owns the NFT", - "properties": [ - { - "sessions": 2 - }, - { - "duration": 30 - }, - { - "time_unit": "minutes" - } - ], - "expiry": 1.577e+7, - "t&c": "https://...." - } -} -``` - -In order to get access to the details needed to enter the video call, the owner would access the URI returned by -the `getUtilityUri` method for the NFT that they own. Additionally, access to the details could be conditioned by the -authentication with the wallet that owns the NFT. - -The current status of the utility would also be included in the URI (eg: how many sessions are still available, etc.) - -## Backwards Compatibility - -This standard is compatible with current ERC-721 standard. There are no other standards that define similar methods for -NFTs and the method names are not used by other ERC-721 related standards. - -## Test Cases - -Test cases are available [here](../assets/eip-6785/test/ERC6785.test.js) - -## Reference Implementation - -The reference implementation can be found [here](../assets/eip-6785/contracts/ERC6785.sol). - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0-1.0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6785.md diff --git a/EIPS/eip-6786.md b/EIPS/eip-6786.md index 125c145e5cfabf..4d95eae797b4f5 100644 --- a/EIPS/eip-6786.md +++ b/EIPS/eip-6786.md @@ -1,105 +1 @@ ---- -eip: 6786 -title: Registry for royalties payment for NFTs -description: A registry used for paying royalties for any NFT with information about the creator -author: Otniel Nicola (@OT-kthd), Bogdan Popa (@BogdanKTHD) -discussions-to: https://ethereum-magicians.org/t/eip-6786-royalty-debt-registry/13569 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-27 -requires: 165, 2981 ---- - -## Abstract - -This standard allows anyone to pay royalties for a certain NFT and also to keep track of the royalties amount paid. It will cumulate the value each time a payment is executed through it and make the information public. - -## Motivation - -There are many marketplaces which do not enforce any royalty payment to the NFT creator every time the NFT is sold or re-sold and/or providing a way for doing it. There are some marketplaces which use specific system of royalties, however that system is applicable for the NFTs creates on their platform. - -In this context, there is a need of a way for paying royalties, as it is a strong incentive for creators to keep contributing to the NFTs ecosystem. - -Additionally, this standard will provide a way of computing the amount of royalties paid to a creator for a certain NFT. This could be useful in the context of categorising NFTs in terms of royalties. The term “debt“ is used because the standard aims to provide a way of knowing if there are any royalties left unpaid for the NFTs trades that took place in a marketplace that does not support them and, in that case, expose a way of paying them. - -With a lot of places made for trading NFTs dropping down the royalty payment or having a centralised approach, we want to provide a way for anyone to pay royalties to the creators. - -Not only the owner of it, but anyone could pay royalties for a certain NFT. This could be a way of supporting a creator for his work. - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -Every contract compliant with [ERC-6786](./eip-6786.md) MUST implement the interface defined as follows: - -### Contract Interface - -```solidity -// @title Royalty Debt Registry -/// Note: the ERC-165 identifier for this interface is 0x253b27b0 - -interface IERC6786 { - - // Logged when royalties were paid for a NFT - /// @notice Emitted when royalties are paid for the NFT with address tokenAddress and id tokenId - event RoyaltiesPaid(address indexed tokenAddress, uint256 indexed tokenId, uint256 amount); - - /// @notice sends msg.value to the creator of a NFT - /// @dev Reverts if there are no on-chain informations about the creator - /// @param tokenAddress The address of NFT contract - /// @param tokenId The NFT id - function payRoyalties(address tokenAddress, uint256 tokenId) external payable; - - /// @notice Get the amount of royalties which was paid for a NFT - /// @dev - /// @param tokenAddress The address of NFT contract - /// @param tokenId The NFT id - /// @return The amount of royalties paid for the NFT - function getPaidRoyalties(address tokenAddress, uint256 tokenId) external view returns (uint256); -} -``` - -All functions defined as view MAY be implemented as pure or view - -Function `payRoyalties` MAY be implemented as public or external - -The event `RoyaltiesPaid` MUST be emitted when the payRoyalties function is called - -The `supportsInterface` function MUST return true when called with `0x253b27b0` - -## Rationale - -The payment can be made in native coins, so it is easy to aggregate the amount of paid royalties. We want this information to be public, so anyone could tell if a creator received royalties in case of under the table trading or in case of marketplaces which don’t support royalties. - -The function used for payment can be called by anyone (not only the NFTs owner) to support the creator at any time. There is a way of seeing the amount of paid royalties in any token, also available for anyone. - -For fetching creator on-chain data we will use [ERC-2981](./eip-2981.md), but any other on-chain method of getting the creator address is accepted. - -## Backwards Compatibility - -This ERC is not introducing any backward incompatibilities. - -## Test Cases - -Tests are included in [`ERC6786.test.js`](../assets/eip-6786/test/ERC6786.test.js). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-6786 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`ERC6786.sol`](../assets/eip-6786/contracts/ERC6786.sol). - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6786.md diff --git a/EIPS/eip-6787.md b/EIPS/eip-6787.md index 0656bb65ba3d60..9432a06d950d07 100644 --- a/EIPS/eip-6787.md +++ b/EIPS/eip-6787.md @@ -1,93 +1 @@ ---- -eip: 6787 -title: Order Book DEX with Two Phase Withdrawal -description: An order book-based DEX Interface that ensures the asset security of both users and the exchange -author: Jessica (@qizheng09), Roy (@royshang), Jun (@SniperUsopp) -discussions-to: https://ethereum-magicians.org/t/order-book-dex-standard/13573 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-27 ---- - - -## Abstract - -The Order Book DEX Standard is a proposed set of interface specifications that define a decentralized exchange (DEX) protocol for trading assets using order books. This standard provides a set of functions that allow users to deposit, withdraw, and trade assets on a decentralized exchange. Additionally, it proposes a novel two-phase withdrawal scheme to ensure the asset security of both users and the exchange, addressing users' trust issues with the exchange. - -## Motivation - -Decentralized exchanges (DEXs) have become increasingly popular in recent years due to their ability to provide users with greater control over their assets and reduce reliance on centralized intermediaries. However, many existing DEX protocols suffer from issues such as low liquidity and inefficient price discovery. Order book-based DEXs based Layer2 have emerged as a popular alternative, but there is currently no standardized interface for implementing such exchanges. - -The Order Book DEX Standard aims to provide developers with a common interface for building interoperable order book-based DEXs that can benefit from network effects. By establishing a standard set of functions for depositing, withdrawing, and forced withdrawals, the Order Book DEX Standard can fully ensure the security of user assets. At the same time, the two-phase forced withdrawal mechanism can also prevent malicious withdrawals from users targeting the exchange. - -The two phase commit protocol is an important distributed consistency protocol, aiming to ensure data security and consistency in distributed systems. In the Layer2 order book DEX system, to enhance user experience and ensure financial security, we adopt a 1:1 reserve strategy, combined with a decentralized clearing and settlement interface, and a forced withdrawal function to fully guarantee users' funds. - -However, such design also faces potential risks. When users engage in perpetual contract transactions, they may incur losses. In this situation, malicious users might exploit the forced withdrawal function to evade losses. To prevent this kind of attack, we propose a two-phase forced withdrawal mechanism. - -By introducing the two phase forced withdrawal function, we can protect users' financial security while ensuring the security of the exchange's assets. In the first phase, the system will conduct a preliminary review of the user's withdrawal request to confirm the user's account status. In the second phase, after the forced withdrawal inspection period, users can directly submit the forced withdrawal request to complete the forced withdrawal process. In this way, we can not only prevent users from exploiting the forced withdrawal function to evade losses but also ensure the asset security for both the exchange and the users. - -In conclusion, by adopting the two phase commit protocol and the two phase forced withdrawal function, we can effectively guard against malicious behaviors and ensure data consistency and security in distributed systems while ensuring user experience and financial security. - -## Specification - -### Interfaces - -The Order Book DEX Standard defines the following Interfaces: - -#### `deposit` - -`function deposit(address token, uint256 amount) external;` - -The **deposit** function allows a user to deposit a specified amount of a particular token to the exchange. The *token* parameter specifies the address of the token contract, and the *amount* parameter specifies the amount of the token to be deposited. - -#### `withdraw` - -`function withdraw(address token, uint256 amount) external;` - -The **withdraw** function allows a user to withdraw a specified amount of a particular token from the exchange. The *token* parameter specifies the address of the token contract, and the *amount* parameter specifies the amount of the token to be withdrawn. - -#### `prepareForceWithdraw` - -`function prepareForceWithdraw(address token, uint256 amount) external returns (uint256 requestID);` - -The assets deposited by users will be stored in the exchange contract's account, and the exchange can achieve real-time 1:1 reserve proof. The **prepareForceWithdraw** function is used for users to initiate a forced withdrawal of a certain amount of a specified token. This function indicates that the user wants to perform a forced withdrawal and can submit the withdrawal after the default timeout period. Within the timeout period, the exchange needs to confirm that the user's order status meets the expected criteria, and forcibly cancel the user's order and settle the trade to avoid malicious attacks by the user. This function takes the following parameters: - -1. *token*: the address of the token to be withdrawn -2. *amount*: the amount of the token to be withdrawn - -Since an account may initiate multiple two phase forced withdrawals in parallel, each forced withdrawal needs to return a unique *requestID*. The function returns a unique *requestID* that can be used to submit the forced withdrawal using the commitForceWithdraw function. - -#### `commitForceWithdraw` - -`function commitForceWithdraw(uint256 requestID) external;` - -1. *requestID*: the request ID of the two phase Withdraw - -The **commitForceWithdraw** function is used to execute a forced withdrawal operation after the conditions are met. The function takes a *requestID* parameter, which specifies the ID of the forced withdrawal request to be executed. The request must have been previously initiated using the prepareForceWithdraw function. - -### Events - -#### `PrepareForceWithdraw` - -MUST trigger when user successful call to PrepareForceWithdraw. - -`event PrepareForceWithdraw(address indexed user, address indexed tokenAddress, uint256 amount);` - -## Rationale - -The flow charts for two-phase withdrawal are shown below: - -![](../assets/eip-6787/image1.png) - -## Backwards Compatibility - -No backward compatibility issues found. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6787.md diff --git a/EIPS/eip-6806.md b/EIPS/eip-6806.md index b37562b02c29a1..0ba5d4e451ea71 100644 --- a/EIPS/eip-6806.md +++ b/EIPS/eip-6806.md @@ -1,132 +1 @@ ---- -eip: 6806 -title: ERC-721 Holding Time Tracking -description: Add holding time information to ERC-721 tokens -author: Saitama (@saitama2009), Combo , Luigi -discussions-to: https://ethereum-magicians.org/t/draft-eip-erc721-holding-time-tracking/13605 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-30 -requires: 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It adds an interface that tracks and describes the holding time of a Non-Fungible Token (NFT) by an account. - -## Motivation - -In some use cases, it is valuable to know the duration for which a NFT has been held by an account. This information can be useful for rewarding long-term holders, determining access to exclusive content, or even implementing specific business logic based on holding time. However, the current ERC-721 standard does not have a built-in mechanism to track NFT holding time. - -This proposal aims to address these limitations by extending the ERC-721 standard to include holding time tracking functionality. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -**Interface** - -The following interface extends the existing ERC-721 standard: - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0 - -interface IERC6806 { - function getHoldingInfo( - uint256 tokenId - ) external view returns (address holder, uint256 holdingTime); -} -``` - -**Functions** - -### getHoldingInfo - -``` -function getHoldingInfo(uint256 tokenId) external view returns (address holder, uint256 holdingTime); -``` - -This function returns the current holder of the specified NFT and the length of time (in seconds) the NFT has been held by the current account. - -* `tokenId`: The unique identifier of the NFT. -* Returns: A tuple containing the current holder's address and the holding time (in seconds). - -## Rationale - -The addition of the `getHoldingInfo` function to an extension of the ERC-721 standard enables developers to implement NFT-based applications that require holding time information. This extension maintains compatibility with existing ERC-721 implementations while offering additional functionality for new use cases. - -The `getHoldingInfo` function provides a straightforward method for retrieving the holding time and holder address of an NFT. By using seconds as the unit of time for holding duration, it ensures precision and compatibility with other time-based functions in smart contracts. - -`getHoldingInfo` returns both `holder` and `holdingTime` so that some token owners (as decided by the implementation) can be ignored for the purposes of calculating holding time. For example, a contract may take ownership of an NFT as collateral for a loan. Such a loan contract could be ignored, so the real owner's holding time increases properly. - -## Backwards Compatibility - -This proposal is fully backwards compatible with the existing ERC-721 standard, as it extends the standard with new functions that do not affect the core functionality. - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC6806.sol"; - -contract ERC6806 is ERC721, Ownable, IERC6806 { - mapping(uint256 => address) private _holder; - mapping(uint256 => uint256) private _holdStart; - mapping(address => bool) private _holdingTimeWhitelist; - - constructor( - string memory name_, - string memory symbol_ - ) ERC721(name_, symbol_) {} - - function _afterTokenTransfer( - address from, - address to, - uint256 firstotTokenId, - uint256 - ) internal override { - if (_holdingTimeWhitelist[from] || _holdingTimeWhitelist[to]) { - return; - } - - if (_holder[firstotTokenId] != to) { - _holder[firstotTokenId] = to; - _holdStart[firstotTokenId] = block.timestamp; - } - } - - function getHoldingInfo( - uint256 tokenId - ) public view returns (address holder, uint256 holdingTime) { - return (_holder[tokenId], block.timestamp - _holdStart[tokenId]); - } - - function setHoldingTimeWhitelistedAddress( - address account, - bool ignoreReset - ) public onlyOwner { - _holdingTimeWhitelist[account] = ignoreReset; - emit HoldingTimeWhitelistSet(account, ignoreReset); - } -} -``` - -## Security Considerations - -This EIP introduces additional state management for tracking holding times, which may have security implications. Implementers should be cautious of potential vulnerabilities related to holding time manipulation, especially during transfers. - -When implementing this EIP, developers should be mindful of potential attack vectors, such as reentrancy and front-running attacks, as well as general security best practices for smart contracts. Adequate testing and code review should be performed to ensure the safety and correctness of the implementation. - -Furthermore, developers should consider the gas costs associated with maintaining and updating holding time information. Optimizations may be necessary to minimize the impact on contract execution costs. - -It is also important to note that the accuracy of holding time information depends on the accuracy of the underlying blockchain's timestamp. While block timestamps are generally reliable, they can be manipulated by miners to some extent. As a result, holding time data should not be relied upon as a sole source of truth in situations where absolute precision is required. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6806.md diff --git a/EIPS/eip-6808.md b/EIPS/eip-6808.md index 658eeabf049b8c..0bd52ee366160a 100644 --- a/EIPS/eip-6808.md +++ b/EIPS/eip-6808.md @@ -1,517 +1 @@ ---- -eip: 6808 -title: Fungible Key Bound Token -description: An interface for Fungible Key Bound Tokens, also known as a FKBT. -author: Mihai Onila (@MihaiORO), Nick Zeman (@NickZCZ), Narcis Cotaie (@NarcisCRO) -discussions-to: https://ethereum-magicians.org/t/fungible-key-bound-token-kbt/13624 -status: Final -type: Standards Track -category: ERC -created: 2023-03-31 -requires: 20 ---- - -## Abstract - -A standard interface for Fungible Key Bound Tokens (**FKBT/s**), a subset of the more general Key Bound Tokens (**KBT/s**). - -The following standardizes an API for tokens within smart contracts and provides basic functionality to the [addBindings](#addbindings-function) function. This function designates **Key Wallets**[^1], which are responsible for conducting a **Safe Transfer**[^2]. During this process, **FKBT's** are safely approved so they can be spent by the user or an on-chain third-party entity. - -The premise of **FKBT's** is to provide fully optional security features built directly into the fungible asset, via the concept of _allow_ found in the [allowTransfer](#allowtransfer-function) and [allowApproval](#allowapproval-function) functions. These functions are called by one of the **Key Wallets**[^1] and _allow_ the **Holding Wallet**[^3] to either call the already familiar `transfer` and `approve` function found in [ERC-20](./eip-20.md). Responsibility for the **FKBT** is therefore split. The **Holding Wallet** contains the asset and **Key Wallets** have authority over how the assets can be spent or approved. **Default Behaviors**[^4] of a traditional fungible ERC-20 can be achieved by simply never using the [addBindings](#addbindings-function) function. - -We considered **FKBTs** being used by every individual who wishes to add additional security to their fungible assets, as well as consignment to third-party wallets/brokers/banks/insurers. **FKBTs** are resilient to attacks/thefts, by providing additional protection to the asset itself on a self-custodial level. - -## Motivation - -In this fast-paced technologically advancing world, people learn and mature at different speeds. The goal of global adoption must take into consideration the target demographic is of all ages and backgrounds. Unfortunately for self-custodial assets, one of the greatest pros is also one of its greatest cons. The individual is solely responsible for their actions and adequately securing their assets. If a mistake is made leading to a loss of funds, no one is able to guarantee their return. - -From January 2021 through March 2022, the United States Federal Trade Commission received more than 46,000[^5] crypto scam reports. This directly impacted crypto users and resulted in a net consumer loss exceeding $1 Billion[^6]. Theft and malicious scams are an issue in any financial sector and oftentimes lead to stricter regulation. However, government-imposed regulation goes against one of this space’s core values. Efforts have been made to increase security within the space through centralized and decentralized means. Up until now, no one has offered a solution that holds onto the advantages of both whilst eliminating their disadvantages. - -We asked ourselves the same question as many have in the past, “How does one protect the wallet?”. After a while, realizing the question that should be asked is “How does one protect the asset?”. Creating the wallet is free, the asset is what has value and is worth protecting. This question led to the development of **KBT's**. A solution that is fully optional and can be tailored so far as the user is concerned. Individual assets remain protected even if the seed phrase or private key is publicly released, as long as the security feature was activated. - -**FKBTs** saw the need to improve on the widely used fungible ERC-20 token standard. The security of fungible assets is a topic that concerns every entity in the crypto space, as their current and future use cases are continuously explored. **FKBTs** provide a scalable decentralized security solution that takes security one step beyond wallet security, focusing on the token's ability to remain secure. The security is on the blockchain itself, which allows every demographic that has access to the internet to secure their assets without the need for current hardware or centralized solutions. Made to be a promising alternative, **FKBTs** inherit all the characteristics of an ERC-20. This was done so **FKBTs** could be used on every dApp that is configured to use traditional fungible tokens. - -During the development process, the potential advantages **KBT's** explored were the main motivation factors leading to their creation; - -1. **Completely Decentralized:** The security features are fully decentralized meaning no third-party will have access to user funds when activated. This was done to truly stay in line with the premise of self-custodial assets, responsibility and values. - -2. **Limitless Scalability:** Centralized solutions require the creation of an account and their availability may be restricted based on location. **FKBT's** do not face regional restrictions or account creation. Decentralized security solutions such as hardware options face scalability issues requiring transport logistics, secure shipping and vendor. **FKBT's** can be used anywhere around the world by anyone who so wishes, provided they have access to the internet. - -3. **Fully Optional Security:** Security features are optional, customizable and removable. It’s completely up to the user to decide the level of security they would like when using **FKBT's**. - -4. **Default Functionality:** If the user would like to use **FKBT's** as a traditional ERC-20, the security features do not have to be activated. As the token inherits all of the same characteristics, it results in the token acting with traditional fungible **Default Behaviors**[^4]. However, even when the security features are activated, the user will still have the ability to customize the functionality of the various features based on their desired outcome. The user can pass a set of custom and or **Default Values**[^7] manually or through a dApp. - -5. **Unmatched Security:** By calling the [addBindings](#addbindings-function) function a **Key Wallet**[^1] is now required for the [allowTransfer](#allowtransfer-function) or [allowApproval](#allowapproval-function) function. The [allowTransfer](#allowtransfer-function) function requires 4 parameters, `_amount`[^8], `_time`[^9], `_address`[^10], and `_allFunds`[^11], where as the [allowApproval](#allowapproval-function) function has 2 parameters, `_time`[^12] and `_numberOfTransfers`[^13]. In addition to this, **FKBT's** have a [safeFallback](#safefallback-function) and [resetBindings](#resetbindings-function) function. The combination of all these prevent and virtually cover every single point of failure that is present with a traditional ERC-20, when properly used. - -6. **Security Fail-Safes:** With **FKBTs**, users can be confident that their tokens are safe and secure, even if the **Holding Wallet**[^3] or one of the **Key Wallets**[^1] has been compromised. If the owner suspects that the **Holding Wallet** has been compromised or lost access, they can call the [safeFallback](#safefallback-function) function from one of the **Key Wallets**. This moves the assets to the other **Key Wallet** preventing a single point of failure. If the owner suspects that one of the **Key Wallets** has been comprised or lost access, the owner can call the [resetBindings](#resetbindings-function) function from `_keyWallet1`[^15] or `_keyWallet2`[^16]. This resets the **FKBT's** security feature and allows the **Holding Wallet** to call the [addBindings](#addbindings-function) function again. New **Key Wallets** can therefore be added and a single point of failure can be prevented. - -7. **Anonymous Security:** Frequently, centralized solutions ask for personal information that is stored and subject to prying eyes. Purchasing decentralized hardware solutions are susceptible to the same issues e.g. a shipping address, payment information, or a camera recording during a physical cash pick-up. This may be considered by some as infringing on their privacy and asset anonymity. **FKBT's** ensure user confidentially as everything can be done remotely under a pseudonym on the blockchain. - -8. **Low-Cost Security:** The cost of using **FKBT's** security features correlate to on-chain fees, the current _GWEI_ at the given time. As a standalone solution, they are a viable cost-effective security measure feasible to the majority of the population. - -9. **Environmentally Friendly:** Since the security features are coded into the **FKBT**, there is no need for centralized servers, shipping, or the production of physical object/s. Thus leading to a minimal carbon footprint by the use of **FKBT's**, working hand in hand with Ethereum’s change to a _PoS_[^14] network. - -10. **User Experience:** The security feature can be activated by a simple call to the [addBindings](#addbindings-function) function. The user will only need two other wallets, which will act as `_keyWallet1`[^15] and `_keyWallet2`[^16], to gain access to all of the benefits **FKBT's** offer. The optional security features improve the overall user experience and Ethereum ecosystem by ensuring a safety net for those who decide to use it. Those that do not use the security features are not hindered in any way. This safety net can increase global adoption as people can remain confident in the security of their assets, even in the scenario of a compromised wallet. - -## Specification - -### `IKBT20` (Token Contract) - -**NOTES**: - -- The following specifications use syntax from Solidity `0.8.0` (or above) -- Callers MUST handle `false` from `returns (bool success)`. Callers MUST NOT assume that `false` is never returned! - -```solidity -interface IKBT20 { - event AccountSecured(address _account, uint256 _amount); - event AccountResetBinding(address _account); - event SafeFallbackActivated(address _account); - event AccountEnabledTransfer( - address _account, - uint256 _amount, - uint256 _time, - address _to, - bool _allFunds - ); - event AccountEnabledApproval( - address _account, - uint256 _time, - uint256 _numberOfTransfers - ); - event Ingress(address _account, uint256 _amount); - event Egress(address _account, uint256 _amount); - - struct AccountHolderBindings { - address firstWallet; - address secondWallet; - } - - struct FirstAccountBindings { - address accountHolderWallet; - address secondWallet; - } - - struct SecondAccountBindings { - address accountHolderWallet; - address firstWallet; - } - - struct TransferConditions { - uint256 amount; - uint256 time; - address to; - bool allFunds; - } - - struct ApprovalConditions { - uint256 time; - uint256 numberOfTransfers; - } - - function addBindings( - address _keyWallet1, - address _keyWallet2 - ) external returns (bool); - - function getBindings( - address _account - ) external view returns (AccountHolderBindings memory); - - function resetBindings() external returns (bool); - - function safeFallback() external returns (bool); - - function allowTransfer( - uint256 _amount, - uint256 _time, - address _to, - bool _allFunds - ) external returns (bool); - - function getTransferableFunds( - address _account - ) external view returns (TransferConditions memory); - - function allowApproval( - uint256 _time, - uint256 _numberOfTransfers - ) external returns (bool); - - function getApprovalConditions( - address account - ) external view returns (ApprovalConditions memory); - - function getNumberOfTransfersAllowed( - address _account, - address _spender - ) external view returns (uint256); - - function isSecureWallet(address _account) external view returns (bool); -} -``` - - -### Events - -#### `AccountSecured` event - -Emitted when the `_account` is securing his account by calling the `addBindings` function. - -`_amount` is the current balance of the `_account`. - -```solidity -event AccountSecured(address _account, uint256 _amount) -``` - -#### `AccountResetBinding` event - -Emitted when the holder is resetting his `keyWallets` by calling the `resetBindings` function. - -```solidity -event AccountResetBinding(address _account) -``` - -#### `SafeFallbackActivated` event - -Emitted when the holder is choosing to move all the funds to one of the `keyWallets` by calling the `safeFallback` function. - -```solidity -event SafeFallbackActivated(address _account) -``` - -#### `AccountEnabledTransfer` event - -Emitted when the `_account` has allowed for transfer an `_amount` of tokens for the `_time` amount of `block` seconds for `_to` address (or if -the `_account` has allowed for transfer all funds though `_allFunds` set to `true`) by calling the `allowTransfer` function. - -```solidity -event AccountEnabledTransfer(address _account, uint256 _amount, uint256 _time, address _to, bool _allFunds) -``` - -#### `AccountEnabledApproval` event - -Emitted when `_account` has allowed approval, for the `_time` amount of `block` seconds and set a `_numberOfTransfers` allowed, by calling the `allowApproval` function. - -```solidity -event AccountEnabledApproval(address _account, uint256 _time, uint256 _numberOfTransfers) -``` - -#### `Ingress` event - -Emitted when `_account` becomes a holder. `_amount` is the current balance of the `_account`. - -```solidity -event Ingress(address _account, uint256 _amount) -``` - -#### `Egress` event - -Emitted when `_account` transfers all his tokens and is no longer a holder. `_amount` is the previous balance of the `_account`. - -```solidity -event Egress(address _account, uint256 _amount) -``` - - -### **Interface functions** - -The functions detailed below MUST be implemented. - -#### `addBindings` function - -Secures the sender account with other two wallets called `_keyWallet1` and `_keyWallet2` and MUST fire the `AccountSecured` event. - -The function SHOULD `revert` if: - -- the sender account is not a holder -- or the sender is already secured -- or the keyWallets are the same -- or one of the keyWallets is the same as the sender -- or one or both keyWallets are zero address (`0x0`) -- or one or both keyWallets are already keyWallets to another holder account - -```solidity -function addBindings (address _keyWallet1, address _keyWallet2) external returns (bool) -``` - -#### `getBindings` function - -The function returns the `keyWallets` for the `_account` in a `struct` format. - -```solidity -struct AccountHolderBindings { - address firstWallet; - address secondWallet; -} -``` - -```solidity -function getBindings(address _account) external view returns (AccountHolderBindings memory) -``` - -#### `resetBindings` function - -**Note:** This function is helpful when one of the two `keyWallets` is compromised. - -Called from a `keyWallet`, the function resets the `keyWallets` for the `holder` account. MUST fire the `AccountResetBinding` event. - -The function SHOULD `revert` if the sender is not a `keyWallet`. - -```solidity -function resetBindings() external returns (bool) -``` - -#### `safeFallback` function - -**Note:** This function is helpful when the `holder` account is compromised. - -Called from a `keyWallet`, this function transfers all the tokens from the `holder` account to the other `keyWallet` and MUST fire the `SafeFallbackActivated` event. - -The function SHOULD `revert` if the sender is not a `keyWallet`. - -```solidity -function safeFallback() external returns (bool); -``` - -#### `allowTransfer` function - -Called from a `keyWallet`, this function is called before a `transfer` function is called. - -It allows to transfer a maximum amount, for a specific time frame, to a specific address. - -If the amount is 0 then there will be no restriction on the amount. -If the time is 0 then there will be no restriction on the time. -If the to address is zero address then there will be no restriction on the to address. -Or if `_allFunds` is `true`, regardless of the other params, it allows all funds, whenever, to anyone to be transferred. - -The function MUST fire `AccountEnabledTransfer` event. - -The function SHOULD `revert` if the sender is not a `keyWallet` or if the `_amount` is greater than the `holder` account balance. - -```solidity -function allowTransfer(uint256 _amount, uint256 _time, address _to, bool _allFunds) external returns (bool); -``` - -#### `getTransferableFunds` function - -The function returns the transfer conditions for the `_account` in a `struct` format. - -```solidity -struct TransferConditions { - uint256 amount; - uint256 time; - address to; - bool allFunds; -} -``` - -```solidity -function getTransferableFunds(address _account) external view returns (TransferConditions memory); -``` - -#### `allowApproval` function - -Called from a `keyWallet`, this function is called before one of the `approve`, `increaseAllowance` or `decreaseAllowance` function are called. - -It allows the `holder` for a specific amount of `_time` to do an `approve`, `increaseAllowance` or `decreaseAllowance` and limit the number of transfers the spender is allowed to do through `_numberOfTransfers` (0 - unlimited number of transfers in the allowance limit). - -The function MUST fire `AccountEnabledApproval` event. - -The function SHOULD `revert` if the sender is not a `keyWallet`. - -```solidity -function allowApproval(uint256 _time, uint256 _numberOfTransfers) external returns (bool) -``` - -#### `getApprovalConditions` function - -The function returns the approval conditions in a struct format. Where `time` is the `block.timestamp` until the `approve`, `increaseAllowance` or `decreaseAllowance` functions can be called, and `numberOfTransfers` is the number of transfers the spender will be allowed. - -```solidity -struct ApprovalConditions { - uint256 time; - uint256 numberOfTransfers; -} -``` - -```solidity -function getApprovalConditions(address _account) external view returns (ApprovalConditions memory); -``` - -#### `transfer` function - -The function transfers `_amount` of tokens to address `_to`. - -The function MUST fire the `Transfer` event. - -The function SHOULD `revert` if the sender’s account balance does not have enough tokens to spend, or if the sender is a secure account and it has not allowed the transfer of funds through `allowTransfer` function. - -**Note:** Transfers of `0` values MUST be treated as normal transfers and fire the `Transfer` event. - -```solidity -function transfer(address _to, uint256 _amount) external returns (bool) -``` - -#### `approve` function - -The function allows `_spender` to transfer from the `holder` account multiple times, up to the `_value` amount. - -The function also limits the `_spender` to the specific number of transfers set in the `ApprovalConditions` for that `holder` account. If the value is `0` then the `_spender` can transfer multiple times, up to the `_value` amount. - -The function MUST fire an `Approval` event. - -If this function is called again it overrides the current allowance with `_value` and also overrides the number of transfers allowed with `_numberOfTransfers`, set in `allowApproval` function. - -The function SHOULD `revert` if: - -- the sender account is secured and has not called `allowApproval` function -- or if the `_time`, set in the `allowApproval` function, has elapsed. - -```solidity -function approve(address _spender, uint256 _amount) external returns (bool) -``` - -#### `increaseAllowance` function - -The function increases the allowance granted to `_spender` to withdraw from your account. - -The function Emits an `Approval` event indicating the updated allowance. - -The function SHOULD `revert` if: - -- the sender account is secured and has not called `allowApproval` function -- or if the `_spender` is a zero address (`0x0`) -- or if the `_time`, set in the `allowApproval` function, has elapsed. - -```solidity -function increaseAllowance(address _spender, uint256 _addedValue) external returns (bool) -``` - -#### `decreaseAllowance` function - -The function decreases the allowance granted to `_spender` to withdraw from your account. - -The function Emits an `Approval` event indicating the updated allowance. - -The function SHOULD `revert` if: - -- the sender account is secured and has not called `allowApproval` function -- or if the `_spender` is a zero address (`0x0`) -- or if the `_time`, set in the `allowApproval` function, has elapsed. -- or if the `_subtractedValue` is greater than the current allowance - -```solidity -function decreaseAllowance(address _spender, uint256 _subtractedValue) external returns (bool) -``` - -#### `transferFrom` function - -The function transfers `_amount` of tokens from address `_from` to address `_to`. - -The function MUST fire the `Transfer` event. - -The `transferFrom` method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf. -The function SHOULD `revert` unless the `_from` account has deliberately authorized the sender. -Each time the spender calls the function the contract subtracts and checks if the number of allowed transfers has reached 0, -and when that happens the approval is revoked using an approve of 0 amount. - -**Note:** Transfers of 0 values MUST be treated as normal transfers and fire the `Transfer` event. - -```solidity -function transferFrom(address _from, address _to, uint256 _amount) external returns (bool) -``` - -## Rationale - -The intent from individual technical decisions made during the development of **FKBTs** focused on maintaining consistency and backward compatibility with ERC-20s, all the while offering self-custodial security features to the user. It was important that **FKBT's** inherited all of ERC-20s characteristics to comply with requirements found in dApps which use fungible tokens on their platform. In doing so, it allowed for flawless backward compatibility to take place and gave the user the choice to decide if they want their **FKBTs** to act with **Default Behaviors**[^4]. We wanted to ensure that wide-scale implementation and adoption of **FKBTs** could take place immediately, without the greater collective needing to adapt and make changes to the already flourishing decentralized ecosystem. - -For developers and users alike, the [allowTransfer](#allowtransfer-function) and [allowApproval](#allowapproval-function) functions both return bools on success and revert on failures. This decision was done purposefully, to keep consistency with the already familiar ERC-20. Additional technical decisions related to self-custodial security features are broken down and located within the [Security Considerations](#security-considerations) section. - -## Backwards Compatibility - -**KBT's** are designed to be backward-compatible with existing token standards and wallets. Existing tokens and wallets will continue to function as normal, and will not be affected by the implementation of **FKBT's**. - -## Test Cases - -The [assets](../assets/eip-6808/README.md) directory has all the [tests](../assets/eip-6808/test/kbt20.js). - -Average Gas used (_GWEI_): - -- `addBindings` - 154,991 -- `resetBindings` - 30,534 -- `safeFallback` - 51,013 -- `allowTransfer` - 49,887 -- `allowApproval` - 44,971 - -## Reference Implementation - -The implementation is located in the [assets](../assets/eip-6808/README.md) directory. There's also a [diagram](../assets/eip-6808/Contract%20Interactions%20diagram.svg) with the contract interactions. - -## Security Considerations - -**FKBT's** were designed with security in mind every step of the way. Below are some design decisions that were rigorously discussed and thought through during the development process. - -**Key Wallets**[^1]: When calling the [addBindings](#addbindings-function) function for an **FKBT**, the user must input 2 wallets that will then act as `_keyWallet1`[^15] and `_keyWallet2`[^16]. They are added simultaneously to reduce user fees, minimize the chance of human error and prevent a pitfall scenario. If the user had the ability to add multiple wallets it would not only result in additional fees and avoidable confusion but would enable a potentially disastrous [safeFallback](#safefallback-function) situation to occur. For this reason, all **KBT's** work under a 3-wallet system when security features are activated. - -Typically if a wallet is compromised, the fungible assets within are at risk. With **FKBT's** there are two different functions that can be called from a **Key Wallet**[^1] depending on which wallet has been compromised. - -Scenario: **Holding Wallet**[^3] has been compromised, call [safeFallback](#safefallback-function). - -[safeFallback](#safefallback-function): This function was created in the event that the owner believes the **Holding Wallet**[^3] has been compromised. It can also be used if the owner losses access to the **Holding Wallet**. In this scenario, the user has the ability to call [safeFallback](#safefallback-function) from one of the **Key Wallets**[^1]. **FKBT's** are then redirected from the **Holding Wallet** to the other **Key Wallet**. - -By redirecting the **FKBT's** it prevents a single point of failure. If an attacker were to call [safeFallback](#safefallback-function) and the **FKBT's** redirected to the **Key Wallet**[^1] that called the function, they would gain access to all the **FKBT's**. - -Scenario: **Key Wallet**[^1] has been compromised, call [resetBindings](#resetbindings-function). - -[resetBindings](#resetbindings-function): This function was created in the event that the owner believes `_keyWallet1`[^15] or `_keyWallet2`[^16] has been compromised. It can also be used if the owner losses access to one of the **Key Wallets**[^1]. In this instance, the user has the ability to call [resetBindings](#resetbindings-function), removing the bound **Key Wallets** and resetting the security features. The **FKBT's** will now function as a traditional ERC-20 until [addBindings](#addbindings-function) is called again and a new set of **Key Wallets** are added. - -The reason why `_keyWallet1`[^15] or `_keyWallet2`[^16] are required to call the [resetBindings](#resetbindings-function) function is because a **Holding Wallet**[^3] having the ability to call [resetBindings](#resetbindings-function) could result in an immediate loss of **FKBT's**. The attacker would only need to gain access to the **Holding Wallet** and call [resetBindings](#resetbindings-function). - -In the scenario that 2 of the 3 wallets have been compromised, there is nothing the owner of the **FKBT's** can do if the attack is malicious. However, by allowing 1 wallet to be compromised, holders of fungible tokens built using the **FKBT** standard are given a second chance, unlike other current standards. - -The [allowTransfer](#allowtransfer-function) function is in place to guarantee a **Safe Transfer**[^2], but can also have **Default Values**[^7] set by a dApp to emulate **Default Behaviors**[^3] of a traditional ERC-20. It enables the user to highly specify the type of transfer they are about to conduct, whilst simultaneously allowing the user to unlock all the **FKBT's** to anyone for an unlimited amount of time. The desired security is completely up to the user. - -This function requires 4 parameters to be filled and different combinations of these result in different levels of security; - -Parameter 1 `_amount`[^8]: This is the number of **FKBT's** that will be spent on a transfer. - -Parameter 2 `_time`[^9]: The number of blocks the **FKBT's** can be transferred starting from the current block timestamp. - -Parameter 3 `_address`[^10]: The destination the **FKBT's** will be sent to. - -Parameter 4 `_allFunds`[^11]: This is a boolean value. When false, the `transfer` function takes into consideration Parameters 1, 2 and 3. If the value is true, the `transfer` function will revert to a **Default Behavior**[^4], the same as a traditional ERC-20. - -The [allowTransfer](#allowtransfer-function) function requires `_keyWallet1`[^15] or `_keyWallet2`[^16] and enables the **Holding Wallet**[^3] to conduct a `transfer` within the previously specified parameters. These parameters were added in order to provide additional security by limiting the **Holding Wallet** in case it was compromised without the user's knowledge. - -The [allowApproval](#allowapproval-function) function provides extra security when allowing on-chain third parties to use your **FKBT's** on your behalf. This is especially useful when a user is met with common malicious attacks e.g. draining dApp. - -This function requires 2 parameters to be filled and different combinations of these result in different levels of security; - -Parameter 1 `_time`[^12]: The number of blocks that the approval of a third-party service can take place, starting from the current block timestamp. - -Parameter 2 `_numberOfTransfers_`[^13]: The number of transactions a third-party service can conduct on the user's behalf. - -The [allowApproval](#allowapproval-function) function requires `_keyWallet1`[^15] or `_keyWallet2`[^16] and enables the **Holding Wallet**[^3] to allow a third-party service by using the `approve` function. These parameters were added to provide extra security when granting permission to a third-party that uses assets on the user's behalf. Parameter 1, `_time`[^12], is a limitation to when the **Holding Wallet** can `approve` a third-party service. Parameter 2, `_numberOfTransfers`[^13], is a limitation to the number of transactions the approved third-party service can conduct on the user's behalf before revoking approval. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - - -[^1]: The **Key Wallet/s** refers to `_keyWallet1` or `_keyWallet2` which can call the `safeFallback`, `resetBindings`, `allowTransfer` and `allowApproval` functions. -[^2]: A **Safe Transfer** is when 1 of the **Key Wallets** safely approved the use of the **FKBT's**. -[^3]: The **Holding Wallet** refers to the wallet containing the **FKBT's**. -[^4]: A **Default Behavior/s** refers to behavior/s present in the preexisting non-fungible ERC-20 standard. -[^5]: The number of crypto scam reports the United States Federal Trade Commission received, from January 2021 through March 2022. -[^6]: The amount stolen via crypto scams according to the United States Federal Trade Commission, from January 2021 through March 2022. -[^7]: A **Default Value/s** refer to a value/s that emulates the non-fungible ERC-20 **Default Behavior/s**. -[^8]: The `_amount` represents the amount of the **FKBT's** intended to be spent. -[^9]: The `_time` in `allowTransfer` represents the number of blocks a `transfer` can take place in. -[^10]: The `_address` represents the address that the **FKBT's** will be sent to. -[^11]: The `_allFunds` is a bool that can be set to true or false. -[^12]: The `_time` in `allowApproval` represents the number of blocks an `approve` can take place in. -[^13]: The `_numberOfTransfers` is the number of transfers a third-party entity can conduct via `transfer` on the user's behalf. -[^14]: A _PoS_ protocol, Proof-of-Stake protocol, is a cryptocurrency consensus mechanism for processing transactions and creating new blocks in a blockchain. -[^15]: The `_keyWallet1` is 1 of the 2 **Key Wallets** set when calling the `addBindings` function. -[^16]: The `_keyWallet2` is 1 of the 2 **Key Wallets** set when calling the `addBindings` function. +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6808.md diff --git a/EIPS/eip-6809.md b/EIPS/eip-6809.md index 7cab46f5166da1..01ed6eafe9b9d9 100644 --- a/EIPS/eip-6809.md +++ b/EIPS/eip-6809.md @@ -1,517 +1 @@ ---- -eip: 6809 -title: Non-Fungible Key Bound Token -description: An interface for Non-Fungible Key Bound Tokens, also known as a NFKBT. -author: Mihai Onila (@MihaiORO), Nick Zeman (@NickZCZ), Narcis Cotaie (@NarcisCRO) -discussions-to: https://ethereum-magicians.org/t/non-fungible-key-bound-token-kbt/13625 -status: Final -type: Standards Track -category: ERC -created: 2023-03-31 -requires: 721 ---- - -## Abstract - -A standard interface for Non-Fungible Key Bound Tokens (**NFKBT/s**), a subset of the more general Key Bound Tokens (**KBT/s**). - -The following standardizes an API for tokens within smart contracts and provides basic functionality to the [addBindings](#addbindings-function) function. This function designates **Key Wallets**[^1], which are responsible for conducting a **Safe Transfer**[^2]. During this process, **NFKBT's** are safely approved so they can be spent by the user or an on-chain third-party entity. - -The premise of **NFKBT's** is to provide fully optional security features built directly into the non-fungible asset, via the concept of _allow_ found in the [allowTransfer](#allowtransfer-function) and [allowApproval](#allowapproval-function) functions. These functions are called by one of the **Key Wallets**[^1] and _allow_ the **Holding Wallet**[^3] to either call the already familiar `transferFrom` and `approve` function found in [ERC-721](./eip-721.md). Responsibility for the **NFKBT** is therefore split. The **Holding Wallet** contains the asset and **Key Wallets** have authority over how the assets can be spent or approved. **Default Behaviors**[^4] of a traditional non-fungible ERC-721 can be achieved by simply never using the [addBindings](#addbindings-function) function. - -We considered **NFKBTs** being used by every individual who wishes to add additional security to their non-fungible assets, as well as consignment to third-party wallets/brokers/banks/insurers/galleries. **NFKBTs** are resilient to attacks/thefts, by providing additional protection to the asset itself on a self-custodial level. - -## Motivation - -In this fast-paced technologically advancing world, people learn and mature at different speeds. The goal of global adoption must take into consideration the target demographic is of all ages and backgrounds. Unfortunately for self-custodial assets, one of the greatest pros is also one of its greatest cons. The individual is solely responsible for their actions and adequately securing their assets. If a mistake is made leading to a loss of funds, no one is able to guarantee their return. - -From January 2021 through March 2022, the United States Federal Trade Commission received more than 46,000[^5] crypto scam reports. This directly impacted crypto users and resulted in a net consumer loss exceeding $1 Billion[^6]. Theft and malicious scams are an issue in any financial sector and oftentimes lead to stricter regulation. However, government-imposed regulation goes against one of this space’s core values. Efforts have been made to increase security within the space through centralized and decentralized means. Up until now, no one has offered a solution that holds onto the advantages of both whilst eliminating their disadvantages. - -We asked ourselves the same question as many have in the past, “How does one protect the wallet?”. After a while, realizing the question that should be asked is “How does one protect the asset?”. Creating the wallet is free, the asset is what has value and is worth protecting. This question led to the development of **KBT's**. A solution that is fully optional and can be tailored so far as the user is concerned. Individual assets remain protected even if the seed phrase or private key is publicly released, as long as the security feature was activated. - -**NFKBTs** saw the need to improve on the widely used non-fungible ERC-721 token standard. The security of non-fungible assets is a topic that concerns every entity in the crypto space, as their current and future use cases are continuously explored. **NFKBTs** provide a scalable decentralized security solution that takes security one step beyond wallet security, focusing on the token's ability to remain secure. The security is on the blockchain itself, which allows every demographic that has access to the internet to secure their assets without the need for current hardware or centralized solutions. Made to be a promising alternative, **NFKBTs** inherit all the characteristics of an ERC-721. This was done so **NFKBTs** could be used on every dApp that is configured to use traditional non-fungible tokens. - -During the development process, the potential advantages **KBT's** explored were the main motivation factors leading to their creation; - -1. **Completely Decentralized:** The security features are fully decentralized meaning no third-party will have access to user funds when activated. This was done to truly stay in line with the premise of self-custodial assets, responsibility and values. - -2. **Limitless Scalability:** Centralized solutions require the creation of an account and their availability may be restricted based on location. **NFKBT's** do not face regional restrictions or account creation. Decentralized security solutions such as hardware options face scalability issues requiring transport logistics, secure shipping and vendor. **NFKBT's** can be used anywhere around the world by anyone who so wishes, provided they have access to the internet. - -3. **Fully Optional Security:** Security features are optional, customizable and removable. It’s completely up to the user to decide the level of security they would like when using **NFKBT's**. - -4. **Default Functionality:** If the user would like to use **NFKBT's** as a traditional ERC-721, the security features do not have to be activated. As the token inherits all of the same characteristics, it results in the token acting with traditional non-fungible **Default Behaviors**[^4]. However, even when the security features are activated, the user will still have the ability to customize the functionality of the various features based on their desired outcome. The user can pass a set of custom and or **Default Values**[^7] manually or through a dApp. - -5. **Unmatched Security:** By calling the [addBindings](#addbindings-function) function a **Key Wallet**[^1] is now required for the [allowTransfer](#allowtransfer-function) or [allowApproval](#allowapproval-function) function. The [allowTransfer](#allowtransfer-function) function requires 4 parameters, `_tokenId`[^8], `_time`[^9], `_address`[^10], and `_anyToken`[^11], where as the [allowApproval](#allowapproval-function) function has 2 parameters, `_time`[^12] and `_numberOfTransfers`[^13]. In addition to this, **NFKBT's** have a [safeFallback](#safefallback-function) and [resetBindings](#resetbindings-function) function. The combination of all these prevent and virtually cover every single point of failure that is present with a traditional ERC-721, when properly used. - -6. **Security Fail-Safes:** With **NFKBTs**, users can be confident that their tokens are safe and secure, even if the **Holding Wallet**[^3] or one of the **Key Wallets**[^1] has been compromised. If the owner suspects that the **Holding Wallet** has been compromised or lost access, they can call the [safeFallback](#safefallback-function) function from one of the **Key Wallets**. This moves the assets to the other **Key Wallet** preventing a single point of failure. If the owner suspects that one of the **Key Wallets** has been comprised or lost access, the owner can call the [resetBindings](#resetbindings-function) function from `_keyWallet1`[^15] or `_keyWallet2`[^16]. This resets the **NFKBT's** security feature and allows the **Holding Wallet** to call the [addBindings](#addbindings-function) function again. New **Key Wallets** can therefore be added and a single point of failure can be prevented. - -7. **Anonymous Security:** Frequently, centralized solutions ask for personal information that is stored and subject to prying eyes. Purchasing decentralized hardware solutions are susceptible to the same issues e.g. a shipping address, payment information, or a camera recording during a physical cash pick-up. This may be considered by some as infringing on their privacy and asset anonymity. **NFKBT's** ensure user confidentially as everything can be done remotely under a pseudonym on the blockchain. - -8. **Low-Cost Security:** The cost of using **NFKBT's** security features correlate to on-chain fees, the current _GWEI_ at the given time. As a standalone solution, they are a viable cost-effective security measure feasible to the majority of the population. - -9. **Environmentally Friendly:** Since the security features are coded into the **NFKBT**, there is no need for centralized servers, shipping, or the production of physical object/s. Thus leading to a minimal carbon footprint by the use of **NFKBT's**, working hand in hand with Ethereum’s change to a _PoS_[^14] network. - -10. **User Experience:** The security feature can be activated by a simple call to the [addBindings](#addbindings-function) function. The user will only need two other wallets, which will act as `_keyWallet1`[^15] and `_keyWallet2`[^16], to gain access to all of the benefits **NFKBT's** offer. The optional security features improve the overall user experience and Ethereum ecosystem by ensuring a safety net for those who decide to use it. Those that do not use the security features are not hindered in any way. This safety net can increase global adoption as people can remain confident in the security of their assets, even in the scenario of a compromised wallet. - -## Specification - -### `IKBT721` (Token Contract) - -**NOTES**: - -- The following specifications use syntax from Solidity `0.8.17` (or above) -- Callers MUST handle `false` from `returns (bool success)`. Callers MUST NOT assume that `false` is never returned! - -```solidity -interface IKBT721 { - event AccountSecured(address indexed _account, uint256 _noOfTokens); - event AccountResetBinding(address indexed _account); - event SafeFallbackActivated(address indexed _account); - event AccountEnabledTransfer( - address _account, - uint256 _tokenId, - uint256 _time, - address _to, - bool _anyToken - ); - event AccountEnabledApproval( - address _account, - uint256 _time, - uint256 _numberOfTransfers - ); - event Ingress(address _account, uint256 _tokenId); - event Egress(address _account, uint256 _tokenId); - - struct AccountHolderBindings { - address firstWallet; - address secondWallet; - } - - struct FirstAccountBindings { - address accountHolderWallet; - address secondWallet; - } - - struct SecondAccountBindings { - address accountHolderWallet; - address firstWallet; - } - - struct TransferConditions { - uint256 tokenId; - uint256 time; - address to; - bool anyToken; - } - - struct ApprovalConditions { - uint256 time; - uint256 numberOfTransfers; - } - - function addBindings( - address _keyWallet1, - address _keyWallet2 - ) external returns (bool); - - function getBindings( - address _account - ) external view returns (AccountHolderBindings memory); - - function resetBindings() external returns (bool); - - function safeFallback() external returns (bool); - - function allowTransfer( - uint256 _tokenId, - uint256 _time, - address _to, - bool _allTokens - ) external returns (bool); - - function getTransferableFunds( - address _account - ) external view returns (TransferConditions memory); - - function allowApproval( - uint256 _time, - uint256 _numberOfTransfers - ) external returns (bool); - - function getApprovalConditions( - address account - ) external view returns (ApprovalConditions memory); - - function getNumberOfTransfersAllowed( - address _account, - address _spender - ) external view returns (uint256); - - function isSecureWallet(address _account) external returns (bool); - - function isSecureToken(uint256 _tokenId) external returns (bool); -} -``` - - -### Events - -#### `AccountSecured` event - -Emitted when the `_account` is securing his account by calling the `addBindings` function. - -`_amount` is the current balance of the `_account`. - -```solidity -event AccountSecured(address _account, uint256 _amount) -``` - -#### `AccountResetBinding` event - -Emitted when the holder is resetting his `keyWallets` by calling the `resetBindings` function. - -```solidity -event AccountResetBinding(address _account) -``` - -#### `SafeFallbackActivated` event - -Emitted when the holder is choosing to move all the funds to one of the `keyWallets` by calling the `safeFallback` function. - -```solidity -event SafeFallbackActivated(address _account) -``` - -#### `AccountEnabledTransfer` event - -Emitted when the `_account` has allowed for transfer `_amount` of tokens for the `_time` amount of `block` seconds for `_to` address (or if -the `_account` has allowed for transfer all funds though `_anyToken` set to `true`) by calling the `allowTransfer` function. - -```solidity -event AccountEnabledTransfer(address _account, uint256 _amount, uint256 _time, address _to, bool _allFunds) -``` - -#### `AccountEnabledApproval` event - -Emitted when `_account` has allowed approval for the `_time` amount of `block` seconds by calling the `allowApproval` function. - -```solidity -event AccountEnabledApproval(address _account, uint256 _time) -``` - -#### `Ingress` event - -Emitted when `_account` becomes a holder. `_amount` is the current balance of the `_account`. - -```solidity -event Ingress(address _account, uint256 _amount) -``` - -#### `Egress` event - -Emitted when `_account` transfers all his tokens and is no longer a holder. `_amount` is the previous balance of the `_account`. - -```solidity -event Egress(address _account, uint256 _amount) -``` - -### **Interface functions** - -The functions detailed below MUST be implemented. - -#### `addBindings` function - -Secures the sender account with other two wallets called `_keyWallet1` and `_keyWallet2` and MUST fire the `AccountSecured` event. - -The function SHOULD `revert` if: - -- the sender account is not a holder -- or the sender is already secured -- or the keyWallets are the same -- or one of the keyWallets is the same as the sender -- or one or both keyWallets are zero address (`0x0`) -- or one or both keyWallets are already keyWallets to another holder account - -```solidity -function addBindings (address _keyWallet1, address _keyWallet2) external returns (bool) -``` - -#### `getBindings` function - -The function returns the `keyWallets` for the `_account` in a `struct` format. - -```solidity -struct AccountHolderBindings { - address firstWallet; - address secondWallet; -} -``` - -```solidity -function getBindings(address _account) external view returns (AccountHolderBindings memory) -``` - -#### `resetBindings` function - -**Note:** This function is helpful when one of the two `keyWallets` is compromised. - -Called from a `keyWallet`, the function resets the `keyWallets` for the `holder` account. MUST fire the `AccountResetBinding` event. - -The function SHOULD `revert` if the sender is not a `keyWallet`. - -```solidity -function resetBindings() external returns (bool) -``` - -#### `safeFallback` function - -**Note:** This function is helpful when the `holder` account is compromised. - -Called from a `keyWallet`, this function transfers all the tokens from the `holder` account to the other `keyWallet` and MUST fire the `SafeFallbackActivated` event. - -The function SHOULD `revert` if the sender is not a `keyWallet`. - -```solidity -function safeFallback() external returns (bool); -``` - -#### `allowTransfer` function - -Called from a `keyWallet`, this function is called before a `transferFrom` or `safeTransferFrom` functions are called. - -It allows to transfer a tokenId, for a specific time frame, to a specific address. - -If the tokenId is 0 then there will be no restriction on the tokenId. -If the time is 0 then there will be no restriction on the time. -If the to address is zero address then there will be no restriction on the to address. -Or if `_anyToken` is `true`, regardless of the other params, it allows any token, whenever, to anyone to be transferred of the holder. - -The function MUST fire `AccountEnabledTransfer` event. - -The function SHOULD `revert` if the sender is not a `keyWallet` for a holder or if the owner of the `_tokenId` is different than the `holder`. - -```solidity -function allowTransfer(uint256 _tokenId, uint256 _time, address _to, bool _anyToken) external returns (bool); -``` - -#### `getTransferableFunds` function - -The function returns the transfer conditions for the `_account` in a `struct` format. - -```solidity -struct TransferConditions { - uint256 tokenId; - uint256 time; - address to; - bool anyToken; -} -``` - -```solidity -function getTransferableFunds(address _account) external view returns (TransferConditions memory); -``` - -#### `allowApproval` function - -Called from a `keyWallet`, this function is called before `approve` or `setApprovalForAll` functions are called. - -It allows the `holder` for a specific amount of `_time` to do an `approve` or `setApprovalForAll` and limit the number of transfers the spender is allowed to do through `_numberOfTransfers` (0 - unlimited number of transfers in the allowance limit). - -The function MUST fire `AccountEnabledApproval` event. - -The function SHOULD `revert` if the sender is not a `keyWallet`. - -```solidity -function allowApproval(uint256 _time) external returns (bool) -``` - -#### `getApprovalConditions` function - -The function returns the approval conditions in a struct format. Where `time` is the `block.timestamp` until the `approve` or `setApprovalForAll` functions can be called, and `numberOfTransfers` is the number of transfers the spender will be allowed. - -```solidity -struct ApprovalConditions { - uint256 time; - uint256 numberOfTransfers; -} -``` - -```solidity -function getApprovalConditions(address _account) external view returns (ApprovalConditions memory); -``` - -#### `transferFrom` function - -The function transfers from `_from` address to `_to` address the `_tokenId` token. - -Each time a spender calls the function the contract subtracts and checks if the number of allowed transfers of that spender has reached 0, -and when that happens, the approval is revoked using a set approval for all to `false`. - -The function MUST fire the `Transfer` event. - -The function SHOULD `revert` if: - -- the sender is not the owner or is not approved to transfer the `_tokenId` -- or if the `_from` address is not the owner of the `_tokenId` -- or if the sender is a secure account and it has not allowed for transfer this `_tokenId` through `allowTransfer` function. - -```solidity -function transferFrom(address _from, address _to, uint256 _tokenId) external returns (bool) -``` - -#### `safeTransferFrom` function - -The function transfers from `_from` address to `_to` address the `_tokenId` token. - -The function MUST fire the `Transfer` event. - -The function SHOULD `revert` if: - -- the sender is not the owner or is not approved to transfer the `_tokenId` -- or if the `_from` address is not the owner of the `_tokenId` -- or if the sender is a secure account and it has not allowed for transfer this `_tokenId` through `allowTransfer` function. - -```solidity -function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external returns (bool) -``` - -#### `safeTransferFrom` function, with data parameter - -This works identically to the other function with an extra data parameter, except this function just sets data to "". - -```solidity -function safeTransferFrom(address _from, address _to, uint256 _tokenId) external returns (bool) -``` - -#### `approve` function - -The function allows `_to` account to transfer the `_tokenId` from the sender account. - -The function also limits the `_to` account to the specific number of transfers set in the `ApprovalConditions` for that `holder` account. If the value is `0` then the `_spender` can transfer multiple times. - -The function MUST fire an `Approval` event. - -If the function is called again it overrides the number of transfers allowed with `_numberOfTransfers`, set in `allowApproval` function. - -The function SHOULD `revert` if: - -- the sender is not the current NFT owner, or an authorized operator of the current owner -- the NFT owner is secured and has not called `allowApproval` function -- or if the `_time`, set in the `allowApproval` function, has elapsed. - -```solidity -function approve(address _to, uint256 _tokenId) public virtual override(ERC721, IERC721) -``` - -#### `setApprovalForAll` function - -The function enables or disables approval for another account `_operator` to manage all of sender assets. - -The function also limits the `_to` account to the specific number of transfers set in the `ApprovalConditions` for that `holder` account. If the value is `0` then the `_spender` can transfer multiple times. - -The function Emits an `Approval` event indicating the updated allowance. - -If the function is called again it overrides the number of transfers allowed with `_numberOfTransfers`, set in `allowApproval` function. - -The function SHOULD `revert` if: - -- the sender account is secured and has not called `allowApproval` function -- or if the `_spender` is a zero address (`0x0`) -- or if the `_time`, set in the `allowApproval` function, has elapsed. - -```solidity -function setApprovalForAll(address _operator, bool _approved) public virtual override(ERC721, IERC721) -``` - -## Rationale - -The intent from individual technical decisions made during the development of **NFKBTs** focused on maintaining consistency and backward compatibility with ERC-721s, all the while offering self-custodial security features to the user. It was important that **NFKBT's** inherited all of ERC-721s characteristics to comply with requirements found in dApps which use non-fungible tokens on their platform. In doing so, it allowed for flawless backward compatibility to take place and gave the user the choice to decide if they want their **NFKBTs** to act with **Default Behaviors**[^4]. We wanted to ensure that wide-scale implementation and adoption of **NFKBTs** could take place immediately, without the greater collective needing to adapt and make changes to the already flourishing decentralized ecosystem. - -For developers and users alike, the [allowTransfer](#allowtransfer-function) and [allowApproval](#allowapproval-function) functions both return bools on success and revert on failures. This decision was done purposefully, to keep consistency with the already familiar ERC-721. Additional technical decisions related to self-custodial security features are broken down and located within the [Security Considerations](#security-considerations) section. - -## Backwards Compatibility - -**KBT's** are designed to be backward-compatible with existing token standards and wallets. Existing tokens and wallets will continue to function as normal, and will not be affected by the implementation of **NFKBT's**. - -## Test Cases - -The [assets](../assets/eip-6809/README.md) directory has all the [tests](../assets/eip-6809/test/kbt721.js). - -Average Gas used (_GWEI_): - -- `addBindings` - 155,096 -- `resetBindings` - 30,588 -- `safeFallback` - 72,221 (depending on how many NFTs the holder has) -- `allowTransfer` - 50,025 -- `allowApproval` - 44,983 - -## Reference Implementation - -The implementation is located in the [assets](../assets/eip-6809/README.md) directory. There's also a [diagram](../assets/eip-6809/Contract%20Interactions%20diagram.svg) with the contract interactions. - -## Security Considerations - -**NFKBT's** were designed with security in mind every step of the way. Below are some design decisions that were rigorously discussed and thought through during the development process. - -**Key Wallets**[^1]: When calling the [addBindings](#addbindings-function) function for an **NFKBT**, the user must input 2 wallets that will then act as `_keyWallet1`[^15] and `_keyWallet2`[^16]. They are added simultaneously to reduce user fees, minimize the chance of human error and prevent a pitfall scenario. If the user had the ability to add multiple wallets it would not only result in additional fees and avoidable confusion but would enable a potentially disastrous [safeFallback](#safefallback-function) situation to occur. For this reason, all **KBT's** work under a 3-wallet system when security features are activated. - -Typically if a wallet is compromised, the non-fungible assets within are at risk. With **NFKBT's** there are two different functions that can be called from a **Key Wallet**[^1] depending on which wallet has been compromised. - -Scenario: **Holding Wallet**[^3] has been compromised, call [safeFallback](#safefallback-function). - -[safeFallback](#safefallback-function): This function was created in the event that the owner believes the **Holding Wallet**[^3] has been compromised. It can also be used if the owner losses access to the **Holding Wallet**. In this scenario, the user has the ability to call [safeFallback](#safefallback-function) from one of the **Key Wallets**[^1]. **NFKBT's** are then redirected from the **Holding Wallet** to the other **Key Wallet**. - -By redirecting the **NFKBT's** it prevents a single point of failure. If an attacker were to call [safeFallback](#safefallback-function) and the **NFKBT's** redirected to the **Key Wallet**[^1] that called the function, they would gain access to all the **NFKBT's**. - -Scenario: **Key Wallet**[^1] has been compromised, call [resetBindings](#resetbindings-function). - -[resetBindings](#resetbindings-function): This function was created in the event that the owner believes `_keyWallet1`[^15] or `_keyWallet2`[^16] has been compromised. It can also be used if the owner losses access to one of the **Key Wallets**[^1]. In this instance, the user has the ability to call [resetBindings](#resetbindings-function), removing the bound **Key Wallets** and resetting the security features. The **NFKBT's** will now function as a traditional ERC-721 until [addBindings](#addbindings-function) is called again and a new set of **Key Wallets** are added. - -The reason why `_keyWallet1`[^15] or `_keyWallet2`[^16] are required to call the [resetBindings](#resetbindings-function) function is because a **Holding Wallet**[^3] having the ability to call [resetBindings](#resetbindings-function) could result in an immediate loss of **NFKBT's**. The attacker would only need to gain access to the **Holding Wallet** and call [resetBindings](#resetbindings-function). - -In the scenario that 2 of the 3 wallets have been compromised, there is nothing the owner of the **NFKBT's** can do if the attack is malicious. However, by allowing 1 wallet to be compromised, holders of non-fungible tokens built using the **NFKBT** standard are given a second chance, unlike other current standards. - -The [allowTransfer](#allowtransfer-function) function is in place to guarantee a **Safe Transfer**[^2], but can also have **Default Values**[^7] set by a dApp to emulate **Default Behaviors**[^3] of a traditional ERC-721. It enables the user to highly specify the type of transfer they are about to conduct, whilst simultaneously allowing the user to unlock all the **NFKBT's** to anyone for an unlimited amount of time. The desired security is completely up to the user. - -This function requires 4 parameters to be filled and different combinations of these result in different levels of security; - -Parameter 1 `_tokenId`[^8]: This is the ID of the **NFKBT** that will be spent on a transfer. - -Parameter 2 `_time`[^9]: The number of blocks the **NFKBT** can be transferred starting from the current block timestamp. - -Parameter 3 `_address`[^10]: The destination the **NFKBT** will be sent to. - -Parameter 4 `_anyToken`[^11]: This is a boolean value. When false, the `transferFrom` function takes into consideration Parameters 1, 2 and 3. If the value is true, the `transferFrom` function will revert to a **Default Behavior**[^4], the same as a traditional ERC-721. - -The [allowTransfer](#allowtransfer-function) function requires `_keyWallet1`[^15] or `_keyWallet2`[^16] and enables the **Holding Wallet**[^3] to conduct a `transferFrom` within the previously specified parameters. These parameters were added in order to provide additional security by limiting the **Holding Wallet** in case it was compromised without the user's knowledge. - -The [allowApproval](#allowapproval-function) function provides extra security when allowing on-chain third parties to use your **NFKBT's** on your behalf. This is especially useful when a user is met with common malicious attacks e.g. draining dApp. - -This function requires 2 parameters to be filled and different combinations of these result in different levels of security; - -Parameter 1 `_time`[^12]: The number of blocks that the approval of a third-party service can take place, starting from the current block timestamp. - -Parameter 2 `_numberOfTransfers_`[^13]: The number of transactions a third-party service can conduct on the user's behalf. - -The [allowApproval](#allowapproval-function) function requires `_keyWallet1`[^15] or `_keyWallet2`[^16] and enables the **Holding Wallet**[^3] to allow a third-party service by using the `approve` function. These parameters were added to provide extra security when granting permission to a third-party that uses assets on the user's behalf. Parameter 1, `_time`[^12], is a limitation to when the **Holding Wallet** can `approve` a third-party service. Parameter 2, `_numberOfTransfers`[^13], is a limitation to the number of transactions the approved third-party service can conduct on the user's behalf before revoking approval. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - -[^1]: The **Key Wallet/s** refers to `_keyWallet1` or `_keyWallet2` which can call the `safeFallback`, `resetBindings`, `allowTransfer` and `allowApproval` functions. -[^2]: A **Safe Transfer** is when 1 of the **Key Wallets** safely approved the use of the **NFKBT's**. -[^3]: The **Holding Wallet** refers to the wallet containing the **NFKBT's**. -[^4]: A **Default Behavior/s** refers to behavior/s present in the preexisting non-fungible ERC-721 standard. -[^5]: The number of crypto scam reports the United States Federal Trade Commission received, from January 2021 through March 2022. -[^6]: The amount stolen via crypto scams according to the United States Federal Trade Commission, from January 2021 through March 2022. -[^7]: A **Default Value/s** refer to a value/s that emulates the non-fungible ERC-721 **Default Behavior/s**. -[^8]: The `_tokenId` represents the ID of the **NFKBT** intended to be spent. -[^9]: The `_time` in `allowTransfer` represents the number of blocks a `transferFrom` can take place in. -[^10]: The `_address` represents the address that the **NFKBT** will be sent to. -[^11]: The `_anyToken` is a bool that can be set to true or false. -[^12]: The `_time` in `allowApproval` represents the number of blocks an `approve` can take place in. -[^13]: The `_numberOfTransfers` is the number of transfers a third-party entity can conduct via `transferFrom` on the user's behalf. -[^14]: A _PoS_ protocol, Proof-of-Stake protocol, is a cryptocurrency consensus mechanism for processing transactions and creating new blocks in a blockchain. -[^15]: The `_keyWallet1` is 1 of the 2 **Key Wallets** set when calling the `addBindings` function. -[^16]: The `_keyWallet2` is 1 of the 2 **Key Wallets** set when calling the `addBindings` function. +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6809.md diff --git a/EIPS/eip-681.md b/EIPS/eip-681.md index 772e9ca4bdbff9..66fdc03d1bd7d3 100644 --- a/EIPS/eip-681.md +++ b/EIPS/eip-681.md @@ -1,92 +1 @@ ---- -eip: 681 -title: URL Format for Transaction Requests -author: Daniel A. Nagy (@nagydani) -type: Standards Track -category: ERC -status: Final -discussions-to: https://ethereum-magicians.org/t/erc-681-representing-various-transactions-as-urls -created: 2017-08-01 -requires: 20, 137 ---- - -## Simple Summary -A standard way of representing various transactions, especially payment requests in ether and [ERC-20](./eip-20.md) tokens as URLs. - -## Abstract -URLs embedded in QR-codes, hyperlinks in web-pages, emails or chat messages provide for robust cross-application signaling between very loosely coupled applications. A standardized URL format for payment requests allows for instant invocation of the user's preferred wallet application (even if it is a webapp or a swarm đapp), with the correct parameterization of the payment transaction only to be confirmed by the (authenticated) user. - -## Motivation -The convenience of representing payment requests by standard URLs has been a major factor in the wide adoption of Bitcoin. Bringing a similarly convenient mechanism to Ethereum would speed up its acceptance as a payment platform among end-users. In particular, URLs embedded in broadcast Intents are the preferred way of launching applications on the Android operating system and work across practically all applications. Desktop web browsers have a standardized way of defining protocol handlers for URLs with specific protocol specifications. Other desktop applications typically launch the web browser upon encountering a URL. Thus, payment request URLs could be delivered through a very broad, ever growing selection of channels. - -This specification supersedes the defunct ERC-67, which is a URL format for representing arbitrary transactions in a low-level fashion. This ERC focuses specifically on the important special case of payment requests, while allowing for other, ABI-specified transactions. - -## Specification - -### Syntax -Payment request URLs contain "ethereum" in their schema (protocol) part and are constructed as follows: - - request = schema_prefix target_address [ "@" chain_id ] [ "/" function_name ] [ "?" parameters ] - schema_prefix = "ethereum" ":" [ "pay-" ] - target_address = ethereum_address - chain_id = 1*DIGIT - function_name = STRING - ethereum_address = ( "0x" 40*HEXDIG ) / ENS_NAME - parameters = parameter *( "&" parameter ) - parameter = key "=" value - key = "value" / "gas" / "gasLimit" / "gasPrice" / TYPE - value = number / ethereum_address / STRING - number = [ "-" / "+" ] *DIGIT [ "." 1*DIGIT ] [ ( "e" / "E" ) [ 1*DIGIT ] ] - - -Where `TYPE` is a standard ABI type name, as defined in [Ethereum Contract ABI specification](https://solidity.readthedocs.io/en/develop/abi-spec.html). `STRING` is a URL-encoded unicode string of arbitrary length, where delimiters and the -percentage symbol (`%`) are mandatorily hex-encoded with a `%` prefix. - -Note that a `number` can be expressed in *scientific notation*, with a multiplier of a power of 10. Only integer numbers are allowed, so the exponent MUST be greater or equal to the number of decimals after the point. - -If *key* in the parameter list is `value`, `gasLimit`, `gasPrice` or `gas` then *value* MUST be a `number`. Otherwise, it must correspond to the `TYPE` string used as *key*. - -For the syntax of ENS_NAME, please consult [ERC-137](./eip-137.md) defining Ethereum Name Service. - -### Semantics - -`target_address` is mandatory and denotes either the beneficiary of native token payment (see below) or the contract address with which the user is asked to interact. - -`chain_id` is optional and contains the decimal chain ID, such that transactions on various test- and private networks can be requested. If no `chain_id` is present, the client's current network setting remains effective. - -If `function_name` is missing, then the URL is requesting payment in the native token of the blockchain, which is ether in our case. The amount is specified in `value` parameter, in the atomic unit (i.e. wei). The use of scientific notation is strongly encouraged. For example, requesting 2.014 ETH to address `0xfb6916095ca1df60bb79Ce92ce3ea74c37c5d359` would look as follows: -[ethereum:0xfb6916095ca1df60bb79Ce92ce3ea74c37c5d359?value=2.014e18](ethereum:0xfb6916095ca1df60bb79Ce92ce3ea74c37c5d359?value=2.014e18) - -Requesting payments in [ERC-20](./eip-20.md) tokens involves a request to call the `transfer` function of the token contract with an `address` and a `uint256` typed parameter, containing the *beneficiary address* and the *amount in atomic units*, respectively. For example, -requesting a Unicorn to address `0x8e23ee67d1332ad560396262c48ffbb01f93d052` looks as follows: -[ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x8e23ee67d1332ad560396262c48ffbb01f93d052&uint256=1](ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7/transfer?address=0x8e23ee67d1332ad560396262c48ffbb01f93d052&uint256=1) - -If using ENS names instead of hexadecimal addresses, the resolution is up to the payer, at any time between receiving the URL and sending the transaction. Hexadecimal addresses always take precedence over ENS names, i. e. even if there exists a matching ENS name consisting of `0x` followed by 40 hexadecimal digits, it should never be resolved. Instead, the hexadecimal address should be used directly. - -Note that the indicated amount is only a suggestion (as are all the supplied arguments) which the user is free to change. With no indicated amount, the user should be prompted to enter the amount to be paid. - -Similarly `gasLimit` and `gasPrice` are suggested user-editable values for *gas limit* and *gas price*, respectively, for the requested transaction. It is acceptable to abbreviate `gasLimit` as `gas`, the two are treated synonymously. - -## Rationale -The proposed format is chosen to resemble `bitcoin:` URLs as closely as possible, as both users and application programmers are already familiar with that format. In particular, this motivated the omission of the unit, which is often used in Ethereum ecosystem. Handling different orders of magnitude is facilitated by the exponent so that amount values can be expressed in their nominal units, just like in the case of `bitcoin:`. The use of scientific notation is strongly encouraged when expressing monetary value in ether or [ERC-20](./eip-20.md) tokens. For better human readability, the exponent should be the decimal value of the nominal unit: 18 for ether or the value returned by `decimals()` of the token contract for [ERC-20](./eip-20.md) tokens. Additional parameters may be added, if popular use cases requiring them emerge in practice. - -The `0x` prefix before ethereum addresses specified as hexadecimal numbers is following established practice and also unambiguously distinguishes hexadecimal addresses from ENS names consisting of 40 alphanumeric characters. - -Future upgrades that are partially or fully incompatible with this proposal must use a prefix other than `pay-` that is separated by a dash (`-`) character from whatever follows it. - -## Backwards Compatibility - -In the fairly common case of only indicating the recipient address in a request for payment in ether, this specification is compatible with the superseded ERC-67. - -## Security Considerations - -Since irreversible transactions can be initiated with parameters from such URLs, the integrity and authenticity of these URLs are of great importance. -In particular, changing either the recipient address or the amount transferred can be a profitable attack. Users should only use URLs received from authenticated sources with adequate integrity protection. - -To prevent malicious redirection of payments using ENS, hexadecimal interpretation of Ethereum addresses must have precedence over ENS lookups. Client software may alert the user if an ENS address is visually similar to a hexadecimal address or even outright reject such addresses as likely phishing attacks. - -In order to make sure that the amount transacted is the same as the amount intended, the amount communicated to the human user should be easily verifiable by inspection, including the order of magnitude. In case of [ERC-20](./eip-20.md) token payments, if the payer client has access to the blockchain or some other trusted source of information about the token contract, the interface should display the amount in the units specified in the token contract. Otherwise, it should be displayed as expressed in the URL, possibly alerting the user to the uncertainty of the nominal unit. To facilitate human inspection of the amount, the use of scientific notation with an exponent corresponding to the nominal unit of the transacted token (e.g. 18 in case of ether) is advisable. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-681.md diff --git a/EIPS/eip-6821.md b/EIPS/eip-6821.md index 8e39dd7bf8929b..3973eb168e421a 100644 --- a/EIPS/eip-6821.md +++ b/EIPS/eip-6821.md @@ -1,42 +1 @@ ---- -eip: 6821 -title: Support ENS Name for Web3 URL -description: A mapping from an ENS name to the contract address in Web3 URL -author: Qi Zhou (@qizhou), Qiang Zhu (@qzhodl) -discussions-to: https://ethereum-magicians.org/t/eip-6821-support-ens-name-for-web3-url/13654 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-02 -requires: 137, 634, 3770, 4804 ---- - -## Abstract - -This standard defines the mapping from an Ethereum name service (ENS) name to an Ethereum address for [ERC-4804](./eip-4804.md). - -## Motivation - -ERC-4804 defines a `web3://`-scheme RFC 2396 URI to call a smart contract either by its address or a **name** from name service. If a **name** is specified, the standard specifies a way to resolve the contract address from the name. - -## Specification - -Given **contractName** and **chainid** from a `web3://` URI defined in ERC-4804, the protocol will find the address of the contract using the following steps: - -1. Find the `contentcontract` text record on ENS resolver on chain **chainid**. Return an error if the chain does not have ENS or the record is an invalid ETH address. -2. If the `contentcontract` text record does not exist, the protocol will use the resolved address of **name** from [ERC-137](./eip-137.md#contract-address-interface). -3. If the resolved address of **name** is the zero address, then return an "address not found" error. - -Note that `contentcontract` text record may return an Ethereum address in hexadecimal with a `0x` prefix or an [ERC-3770](./eip-3770.md) chain-specific address. If the address is an ERC-3770 chain-specific address, then the **chainid** to call the message will be overridden by the **chainid** specified by the ERC-3770 address. - -## Rationale - -The standard uses `contentcontract` text record with ERC-3770 chain-specific address instead of `contenthash` so that the record is human-readable - a design principle of ERC-4804. Further, we can use the text record to add additional fields such as time to live (TTL). - -## Security Considerations - -No security considerations were found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6821.md diff --git a/EIPS/eip-6823.md b/EIPS/eip-6823.md index 2d01a52fec326c..2c45cf8f9f8df1 100644 --- a/EIPS/eip-6823.md +++ b/EIPS/eip-6823.md @@ -1,115 +1 @@ ---- -eip: 6823 -title: Token Mapping Slot Retrieval Extension -description: Approach to enhance precision of off-chain transaction simulations by accessing mapping storage slot in ERC-20/721/1155 contracts. -author: qdqd (@qd-qd) -discussions-to: https://ethereum-magicians.org/t/eip-6823-token-mapping-slot-retrieval-extension/13666 -status: Draft -type: Standards Track -category: ERC -created: 2023-03-29 -requires: 20, 721, 1155 ---- - -## Abstract - -The aim of this proposal is to enhance the precision of off-chain simulations for transactions that involve contracts complying with the [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), or [ERC-1155](./eip-1155.md) standards. To achieve this, a method is proposed for obtaining the reserved storage slot of the mapping responsible to track ownership of compliant tokens. The proposed extension offers a standardized entry point that allows for identifying the reserved storage slot of a mapping in a compatible manner. This not only facilitates capturing state changes more precisely but also enables external tools and services to do so without requiring expertise in the particular implementation details. - -## Motivation - -To understand the rationale behind this proposal, it's important to remember how values and mapping are stored in the storage layout. This procedure is language-agnostic; it can be applied to multiple programming languages beyond Solidity, including Vyper. - -The storage layout is a way to persistently store data in Ethereum smart contracts. In the EVM, storage is organized as a key-value store, where each key is a 32-byte location, and each value is a 32-byte word. When you define a state variable in a contract, it is assigned to a storage location. The location is determined by the variable's position in the contract's storage structure. The first variable in the contract is assigned to location 0, the second to location 1, and so on. Multiple values less than 32 bytes can be grouped to fit in a single slot if possible. - -Due to their indeterminate size, mappings utilize a specialized storage arrangement. Instead of storing mappings "in between" state variables, they are allocated to occupy 32 bytes only, and their elements are stored in a distinct storage slot computed through a keccak-256 hash. The location of the value corresponding to a mapping key `k` is determined by concatenating `h(k)` and `p` and performing a keccak-256 hash. The value of `p` is the position of the mapping in the storage layout, which depends on the order and the nature of the variables initialized before the mapping. It can't be determined in a universal way as you have to know how the implementation of the contract is done. - -Due to the nature of the mapping type, it is challenging to simulate transactions that involve smart contracts because the storage layout for different contracts is unique to their specific implementation, etched by their variable requirements and the order of their declaration. Since the storage location of a value in a mapping variable depends on this implementation-sensitive storage slot, we cannot guarantee similarity on the off-chain simulation version that an on-chain attempted interaction will result in. - -This hurdle prevents external platforms and tools from capturing/validating changes made to the contract's state with certainty. - -That's why transaction simulation relies heavily on events. However, this approach has limitations, and events should only be informative and not relied upon as the single source of truth. The state is and must be the only source of truth. Furthermore, it is impossible to know the shape of the storage deterministically and universally, which prevents us from verifying the source of truth that is storage, forcing us to rely on information emitted from the application layer. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -The proposal suggests an extension to the ERC-20/ERC-721/ERC-1155 standards that allows retrieving the reserved storage slot for the mapping type in any compliant smart-contract implementation in a deterministic manner. This method eliminates the reliance on events and enhances the precision of the data access from storage. The proposed extension therefore enables accurate off-chain simulations. The outcome is greater transparency and predictability at no extra cost for the caller, and a negigleable increase in the deployment cost of the contract. - -The proposed extension is a single function that returns the reserved storage slot for the mapping type in any ERC-20/ERC-721/ERC-1155 compliant smart-contract implementation. The function is named `getTokenLocationRoot` and is declared as follows: - -```solidity -abstract contract ERC20Extension is ERC20 { - function getTokenLocationRoot() external pure virtual returns (bytes32 slot) { - assembly { - slot := .slot - } - } -} - -abstract contract ERC721Extension is ERC721 { - function getTokenLocationRoot() external pure virtual returns (bytes32 slot) { - assembly { - slot := .slot - } - } -} - -abstract contract ERC1155Extension is ERC1155 { - function getTokenLocationRoot() external pure virtual returns (bytes32 slot) { - assembly { - slot := .slot - } - } -} -``` - -For these contracts, off-chain callers can use the `getTokenLocationRoot()` function to find the reserved storage slot for the mapping type. This function returns the reserved storage slot for the mapping type in the contract. This location is used to calculate where all the values of the mapping will be stored. Knowing this value makes it possible to determine precisely where each value of the mapping will be stored, regardless of the contract's implementation. The caller can use this slot to calculate the storage slot for a specific token ID and compare the value to the expected one to verify the action stated by the event. In the case of a ERC-721 mint, the caller can compare the value of the storage slot to the address of the token's owner. In the case of a ERC-20 transfer, the caller can compare the value of the storage slot to the address of the token's new owner. In the case of a ERC-1155 burn, the caller can compare the value of the storage slot to the zero address. The off-chain comparison can be performed with any of the many tools available. In addition, it could perhaps allow storage to be proven atomically by not proving the entire state but only a location -- to track ownership of a specific token, for example. - -The name of the function is intentionally generic to allow the same implementation for all the different token standards. Once implemented universally, the selector derived from the signature of this function will be a single, universal entry point that can be used to directly read the slots in the storage responsible of the ownership, of any token contract. This will make off-chain simulations significantly more accurate, and the events will be used for informational purposes only. - -Contract implementers MUST implement the `getTokenLocationRoot()` function in their contracts. The function MUST return the reserved storage slot for the mapping type in the contract. The function SHOULD be declared as `external pure`. - -## Rationale - -The idea behind the implementation was to find an elegant and concise way that avoided any breaking changes with the current standard. Moreover, since gas consumption is crucial, it was inconceivable to find an implementation that would cost gas to the final user. In this case, the addition of a function increases the deployment cost of the contract in a minimal way, but its use is totally free for the external actors. - -The implementation is minimalist in order to be as flexible as possible while being directly compatible with the main programming languages used today to develop smart-contracts for the EVM. - -## Backwards Compatibility - -No backward compatibility issues have been found. - -## Reference Implementation - -```solidity -abstract contract ERC20Extension is ERC20 { - function getTokenLocationRoot() external pure virtual returns (bytes32 slot) { - assembly { - slot := .slot - } - } -} - -abstract contract ERC721Extension is ERC721 { - function getTokenLocationRoot() external pure virtual returns (bytes32 slot) { - assembly { - slot := .slot - } - } -} - -abstract contract ERC1155Extension is ERC1155 { - function getTokenLocationRoot() external pure virtual returns (bytes32 slot) { - assembly { - slot := .slot - } - } -``` - -## Security Considerations - -No security issues are raised by the implementation of this extension. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6823.md diff --git a/EIPS/eip-6860.md b/EIPS/eip-6860.md index a3d8f00c7e1266..a8e3db82b53858 100644 --- a/EIPS/eip-6860.md +++ b/EIPS/eip-6860.md @@ -1,387 +1 @@ ---- -eip: 6860 -title: Web3 URL to EVM Call Message Translation -description: A translation of an HTTP-style Web3 URL to an EVM call message -author: Qi Zhou (@qizhou), Chao Pi (@pichaoqkc), Sam Wilson (@SamWilsn) -discussions-to: https://ethereum-magicians.org/t/eip-4804-web3-url-to-evm-call-message-translation/8300 -status: Draft -type: Standards Track -category: ERC -created: 2023-09-29 -requires: 137 ---- - -## Abstract - -This standard translates an RFC 2396 URI like `web3://uniswap.eth/` to an EVM message such as: - -``` -EVMMessage { - To: 0xaabbccddee.... // where uniswap.eth's address registered at ENS - Calldata: 0x - ... -} -``` - -⚠️ This proposal updates [ERC-4804](./eip-4804.md) with minor corrections, clarifications and modifications. - -## Motivation - -Currently, reading data from Web3 generally relies on a translation done by a Web2 proxy to Web3 blockchain. The translation is mostly done by the proxies such as dApp websites/node service provider/etherscan, which are out of the control of users. The standard here aims to provide a simple way for Web2 users to directly access the content of Web3, especially on-chain Web contents such as SVG/HTML. Moreover, this standard enables interoperability with other standards already compatible with URIs, like SVG/HTML. - -## Specification - -This specification only defines read-only (i.e. Solidity's `view` functions) semantics. State modifying functions may be defined as a future extension. - -This specification uses the Augmented Backus-Naur Form (ABNF) notation of RFC 2234. The complete URI syntax is listed in Appendix A. - -A Web3 URL is an ASCII string in the following form : - -``` -web3URL = web3Schema [ userinfo "@" ] contractName [ ":" chainid ] pathQuery -web3Schema = "w3://" / "web3://" -userinfo = address -``` - -**userinfo** indicates which user is calling the EVM, i.e., "From" field in EVM call message. If not specified, the protocol will use 0x0 as the sender address. - -``` -contractName = address - / domainName -address = "0x" 20( HEXDIG HEXDIG ) -domainName = domainPart *( "." domainPart ) "." nsSuffix -``` - -**contractName** indicates the contract to be called, i.e., "To" field in the EVM call message. If the **contractName** is an address then "To" will be the address. Otherwise, the name is from a name service. In the second case, **nsSuffix** will be the suffix from name service providers such as "eth", etc. The way to translate the name from a name service to an address will be discussed in later EIPs. - -``` -chainid = 1*DIGIT -``` - -**chainid** indicates which chain to resolve **contractName** and call the message. If not specified, the protocol will use the same chain as the name service provider, e.g., 1 for eth. If no name service provider is available, the default chainid is 1. - -``` -pathQuery = pathQueryManual - \ pathQueryAuto -``` - -**pathQuery**, made of the path and optional query, will have a different structure whether the resolve mode is "manual" or "auto". - - -### Resolve Mode - -Once the "To" address and chainid are determined, the protocol will check the resolver mode of contract by calling the `resolveMode` method of the "To" address. The Solidity signature of `resolveMode` is: - -```solidity -function resolveMode() external returns (bytes32); -``` - -The protocol currently supports two resolve modes: auto and manual. - -- The manual mode will be used if the `resolveMode` return value is `0x6d616e75616c0000000000000000000000000000000000000000000000000000`, i.e., "manual" in bytes32 -- The auto mode will be used if : - - the `resolveMode` return value is `0x6175746f00000000000000000000000000000000000000000000000000000000`, i.e, "auto" in bytes32, or - - the `resolveMode` return value is `0x0000000000000000000000000000000000000000000000000000000000000000`, or - - the call to `resolveMode` throws an error (method not implemented or error thrown from the method) -- Otherwise, the protocol will fail the request with the error "unsupported resolve mode". - -#### Manual Mode - -``` -pathQueryManual = pathManual [ "?" queryManual ] - -pathManual = [ *( "/" segment ) "/" segment [ "." fileExtension ] ] -segment = *pchar ; as in RFC 3986 -fileExtension = 1*( ALPHA / DIGIT ) - -queryManual = *( pchar / "/" / "?" ) ; as in RFC 3986 -``` - -The manual mode will use the raw **pathQueryManual** as calldata of the message directly (no percent-encoding decoding will be done). If **pathQueryManual** is empty, the sent calldata will be ``/`` (0x2f). - -The returned message data will be treated as ABI-encoded bytes and the decoded bytes will be returned to the frontend. - -The MIME type returned to the frontend is ``text/html`` by default, but will be overriden if a **fileExtension** is present. In this case, the MIME type will be deduced from the filename extension. - -#### Auto Mode - -``` -pathQueryAuto = pathAuto [ "?" queryAuto ] -pathAuto = [ "/" [ method *( "/" argument ) ] ] -``` - -In the auto mode, if **pathAuto** is empty or "/", then the protocol will call the target contract with empty calldata. Otherwise, the calldata of the EVM message will use standard Solidity contract ABI. - -``` -method = ( ALPHA / "$" / "_" ) *( ALPHA / DIGIT / "$" / "_" ) -``` - -**method** is a string of the function method to be called - -``` -argument = boolArg - \ uintArg - \ intArg - \ addressArg - \ bytesArg - \ stringArg -boolArg = "bool!" ( "true" / "false" ) -uintArg = [ "uint" [ intSizes ] "!" ] 1*DIGIT -intArg = "int" [ intSizes ] "!" 1*DIGIT -intSizes = "8" / "16" / "24" / "32" / "40" / "48" / "56" / "64" / "72" / "80" / "88" / "96" / "104" / "112" / "120" / "128" / "136" / "144" / "152" / "160" / "168" / "176" / "184" / "192" / "200" / "208" / "216" / "224" / "232" / "240" / "248" / "256" -addressArg = [ "address!" ] ( address / domainName ) -bytesArg = [ "bytes!" ] bytes - \ "bytes1!0x" 1( HEXDIG HEXDIG ) - \ "bytes2!0x" 2( HEXDIG HEXDIG ) - ... - \ "bytes32!0x" 32( HEXDIG HEXDIG ) -stringArg = "string!" *pchar [ "." fileExtension ] -``` - -**argument** is an argument of the method with a type-agnostic syntax of ``[ type "!" ] value``. If **type** is specified, the value will be translated to the corresponding type. The protocol currently supports these basic types: bool, int, uint, int<X>, uint<X> (with X ranging from 8 to 256 in steps of 8), address, bytes<X> (with X ranging from 1 to 32), bytes, and string. If **type** is not specified, then the type will be automatically detected using the following rule in a sequential way: - - 1. **type**="uint256", if **value** is numeric; or - 2. **type**="bytes32", if **value** is in the form of 0x+32-byte-data hex; or - 3. **type**="address", if **value** is in the form of 0x+20-byte-data hex; or - 4. **type**="bytes", if **value** is in the form of 0x followed by any number of bytes besides 20 or 32; or - 5. else **type**="address" and parse the argument as a domain name in the form of `domainPart *( "." domainPart ) "." nsSuffix`. In this case, the actual value of the argument will be obtained from **nsSuffix**, e.g., eth. If **nsSuffix** is not supported, an unsupported NS provider error will be returned. - -``` -queryAuto = attribute *( "&" attribute ) -attribute = attrName "=" attrValue -attrName = "returns" - / "returnTypes" -attrValue = [ "(" [ retTypes ] ")" ] -retTypes = retType *( "," retType ) -retType = *( "[]" ) retRawType -retRawType = "bool" / "uint" [ intSizes ] / "int" [ intSize ] / "address" / "bytes" [ bytesSizes ] / "string" -bytesSizes = DIGIT - / ( "1" / "2" ) DIGIT - / "31" / "32" -``` - -The "returns" attribute in **queryAuto** tells the format of the returned data. - -- If the "returns" attribute value is undefined or empty, the returned message data will be treated as ABI-encoded bytes and the decoded bytes will be returned to the frontend. The MIME type returned to the frontend will be undefined by default, but will be overriden if the last argument is of string type and has a **fileExtension**, in which case the MIME type will be deduced from the filename extension. -- If the "returns" attribute value is equal to "()", the raw bytes of the returned message data will be returned, encoded as a "0x"-prefixed hex string in an array in JSON format: ``["0xXXXXX"]`` -- Otherwise, the returned message data will be ABI-decoded in the data types specified in the **returns** value and encoded in JSON format. The encoding of the data will follow the Ethereum JSON-RPC format: - - Unformatted data (bytes, address) will be encoded as hex, prefixed with "0x", two hex digits per byte - - Quantities (integers) will be encoded as hex, prefix with "0x", the most compact representation (slight exception: zero should be represented as "0x0") - - Boolean and strings will be native JSON boolean and strings - -If multiple "returns" attributes are present, the value of the last "returns" attribute will be applied. Note that "returnTypes" is the alias of "returns", but it is not recommended to use and is mainly for [ERC-4804](./eip-4804.md) backward-compatible purpose. - -### Examples - -#### Example 1a - -``` -web3://w3url.eth/ -``` - -where the contract of **w3url.eth** is in manual mode. - -The protocol will find the address of **w3url.eth** from ENS in chainid 1 (Mainnet). Then the protocol will call the address with "Calldata" = `keccak("resolveMode()")[0:4]` = "0xDD473FAE", which returns "manual" in ABI-type "(bytes32)". After determining the manual mode of the contract, the protocol will call the address with "To" = **contractAddress** and "Calldata" = "0x2F". The returned data will be treated as ABI-type "(bytes)", and the decoded bytes will be returned to the frontend, with the information that the MIME type is ``text/html``. - -#### Example 1b - -``` -web3://w3url.eth/ -``` - -where the contract of **w3url.eth** is in auto mode. - -The protocol will find the address of **w3url.eth** from ENS in chainid 1 (Mainnet). Then the protocol will call the address with "Calldata" = `keccak("resolveMode()")[0:4]` = "0xDD473FAE", which returns "", i.e., the contract is in auto mode. After determining the auto mode of the contract, the protocol will call the address with "To" = **contractAddress** and "Calldata" = "". The returned data will be treated as ABI-type "(bytes)", and the decoded bytes will be returned to the frontend, with the information that the MIME type is undefined. - -#### Example 2 - -``` -web3://cyberbrokers-meta.eth/renderBroker/9999 -``` - -where the contract of **cyberbrokers-meta.eth** is in auto mode. - -The protocol will find the address of **cyberbrokers-meta.eth** from ENS on chainid 1 (Mainnet). Then the protocol will call the address with "Calldata" = `keccak("resolveMode()")[0:4]` = "0xDD473FAE", which returns "", i.e., the contract is in auto mode. After determining the auto mode of the contract, the protocol will call the address with "To" = **contractAddress** and "Calldata" = "0x" + `keccak("renderBroker(uint256)")[0:4] + abi.encode(uint256(9999))`. The returned data will be treated as ABI-type "(bytes)", and the decoded bytes will be returned to the frontend, with the information that the MIME type is undefined. - -#### Example 3 - -``` -web3://vitalikblog.eth:5/ -``` - -where the contract of **vitalikblog.eth:5** is in manual mode. - -The protocol will find the address of **vitalikblog.eth** from ENS on chainid 5 (Goerli). Then after determing the contract is in manual mode, the protocol will call the address with "To" = **contractAddress** and "Calldata" = "0x2F" with chainid = 5. The returned data will be treated as ABI-type "(bytes)", and the decoded bytes will be returned to the frontend, with the information that the MIME type is ``text/html``. - -#### Example 4 - -``` -web3://0xe4ba0e245436b737468c206ab5c8f4950597ab7f:42170/ -``` - -where the contract "0xe4ba0e245436b737468c206ab5c8f4950597ab7f:42170" is in manual mode. - -After determing the contract is in manual mode, the protocol will call the address with "To" = "0xe4ba0e245436b737468c206ab5c8f4950597ab7f" and "Calldata" = "0x2F" with chainid = 42170 (Arbitrum Nova). The returned data will be treated as ABI-type "(bytes)", and the decoded bytes will be returned to the frontend, with the information that the MIME type is ``text/html``. - -#### Example 5 - -``` -web3://0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/balanceOf/vitalik.eth?returns=(uint256) -``` - -where the contract "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" is in auto mode. - -The protocol will find the addresses of **vitalik.eth** from ENS on chainid 1 (Mainnet) and then call the method "balanceOf(address)" of the contract with the **vitalik.eth**'s address. The returned data from the call of the contract will be treated as ABI-type "(uint256)", and the decoded data will be returned to the frontend in JSON format like `[ "0x9184e72a000" ]`, with the information that the MIME type is ``application/json``. - -#### Example 6 - -``` -web3://0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/balanceOf/vitalik.eth?returns=() -``` - -where the contract ”0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48“ is in auto mode. - -The protocol will find the address of **vitalik.eth** from ENS on chainid 1 (Mainnet) and then call the method "balanceOf(address)" of the address. The returned data from the call of the contract will be treated as raw bytes and will be encoded in JSON format like `["0x000000000000000000000000000000000000000000000000000009184e72a000"]` and returned to the frontend, with the information that the MIME type is ``application/json``. - -### Appendix A: Complete ABNF for Web3 URLs - -``` -web3URL = web3Schema [ userinfo "@" ] contractName [ ":" chainid ] pathQuery -web3Schema = "w3://" / "web3://" -userinfo = address -contractName = address - / domainName -chainid = 1*DIGIT - -pathQuery = pathQueryManual - \ pathQueryAuto - -pathQueryManual = pathManual [ "?" queryManual ] -pathManual = [ *( "/" segment ) "/" segment [ "." fileExtension ] ] -segment = *pchar ; as in RFC 3986 - -queryManual = *( pchar / "/" / "?" ) ; as in RFC 3986 - -pathQueryAuto = pathAuto [ "?" queryAuto ] -pathAuto = [ "/" [ method *( "/" argument ) ] ] -method = ( ALPHA / "$" / "_" ) *( ALPHA / DIGIT / "$" / "_" ) -argument = boolArg - \ uintArg - \ intArg - \ addressArg - \ bytesArg - \ stringArg -boolArg = "bool!" ( "true" / "false" ) -uintArg = [ "uint" [ intSizes ] "!" ] 1*DIGIT -intArg = "int" [ intSizes ] "!" 1*DIGIT -intSizes = "8" / "16" / "24" / "32" / "40" / "48" / "56" / "64" / "72" / "80" / "88" / "96" / "104" / "112" / "120" / "128" / "136" / "144" / "152" / "160" / "168" / "176" / "184" / "192" / "200" / "208" / "216" / "224" / "232" / "240" / "248" / "256" -addressArg = [ "address!" ] ( address / domainName ) -bytesArg = [ "bytes!" ] bytes - \ "bytes1!0x" 1( HEXDIG HEXDIG ) - \ "bytes2!0x" 2( HEXDIG HEXDIG ) - \ "bytes3!0x" 3( HEXDIG HEXDIG ) - \ "bytes4!0x" 4( HEXDIG HEXDIG ) - \ "bytes5!0x" 5( HEXDIG HEXDIG ) - \ "bytes6!0x" 6( HEXDIG HEXDIG ) - \ "bytes7!0x" 7( HEXDIG HEXDIG ) - \ "bytes8!0x" 8( HEXDIG HEXDIG ) - \ "bytes9!0x" 9( HEXDIG HEXDIG ) - \ "bytes10!0x" 10( HEXDIG HEXDIG ) - \ "bytes11!0x" 11( HEXDIG HEXDIG ) - \ "bytes12!0x" 12( HEXDIG HEXDIG ) - \ "bytes13!0x" 13( HEXDIG HEXDIG ) - \ "bytes14!0x" 14( HEXDIG HEXDIG ) - \ "bytes15!0x" 15( HEXDIG HEXDIG ) - \ "bytes16!0x" 16( HEXDIG HEXDIG ) - \ "bytes17!0x" 17( HEXDIG HEXDIG ) - \ "bytes18!0x" 18( HEXDIG HEXDIG ) - \ "bytes19!0x" 19( HEXDIG HEXDIG ) - \ "bytes20!0x" 20( HEXDIG HEXDIG ) - \ "bytes21!0x" 21( HEXDIG HEXDIG ) - \ "bytes22!0x" 22( HEXDIG HEXDIG ) - \ "bytes23!0x" 23( HEXDIG HEXDIG ) - \ "bytes24!0x" 24( HEXDIG HEXDIG ) - \ "bytes25!0x" 25( HEXDIG HEXDIG ) - \ "bytes26!0x" 26( HEXDIG HEXDIG ) - \ "bytes27!0x" 27( HEXDIG HEXDIG ) - \ "bytes28!0x" 28( HEXDIG HEXDIG ) - \ "bytes29!0x" 29( HEXDIG HEXDIG ) - \ "bytes30!0x" 30( HEXDIG HEXDIG ) - \ "bytes31!0x" 31( HEXDIG HEXDIG ) - \ "bytes32!0x" 32( HEXDIG HEXDIG ) -stringArg = "string!" *pchar [ "." fileExtension ] - -queryAuto = attribute *( "&" attribute ) -attribute = attrName "=" attrValue -attrName = "returns" - / "returnTypes" -attrValue = [ "(" [ retTypes ] ")" ] -retTypes = retType *( "," retType ) -retType = *( "[]" ) retRawType -retRawType = "bool" / "uint" [ intSizes ] / "int" [ intSize ] / "address" / "bytes" [ bytesSizes ] / "string" -bytesSizes = DIGIT - / ( "1" / "2" ) DIGIT - / "31" / "32" - -domainName = domainPart *( "." domainPart ) "." nsSuffix -domainPart = 1*( ALPHA / DIGIT / "-" ) -nsSuffix = 1*( ALPHA / DIGIT / "-" ) - -fileExtension = 1*( ALPHA / DIGIT ) - -address = "0x" 20( HEXDIG HEXDIG ) -bytes = "0x" *( HEXDIG HEXDIG ) - -pchar = unreserved / pct-encoded / sub-delims / ":" / "@" ; As in RFC 3986 -qchar = unreserved / pct-encoded / other-delims / ":" / "@" - -pct-encoded = "%" HEXDIG HEXDIG ; As in RFC 3986 - -unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" ; As in RFC 3986 -sub-delims = query-delims / other-delims ; As in RFC 3986 -query-delims = "=" / "&" -other-delims = "!" / "$" / "'" / "(" / ")" - / "*" / "+" / "," / ";" - -``` - -### Appendix B: Changes versus [ERC-4804](./eip-4804.md) - -#### Corrections - -- Manual mode : [ERC-4804](./eip-4804.md) stipulates that there is no interpretation of the path [ "?" query ]. This ERC indicates that there is in fact an interpretation of the path, for MIME type determination purpose. -- Auto mode : If there is no **returns** attribute in **query**, [ERC-4804](./eip-4804.md) stipulates that the returned data is treated as ABI-encoded bytes32. This ERC indicates that in fact the returned data is treated as ABI-encoded bytes. - -#### Clarifications - -- Formal specification: This ERC add a ABNF definition of the URL format. -- Resolve mode: This ERC indicates more details on how the resolve mode is determined. -- Manual mode : This ERC indicates how to deal with URI-percent-encoding, the return data, and how the MIME type is determined. -- Auto mode : This ERC indicates in more details the encoding of the argument values, as well as the format and handling of the **returns** value. -- Examples : This ERC add more details to the examples. - -#### Modifications - -- Protocol name: [ERC-4804](./eip-4804.md) mentionned ``ethereum-web3://`` and ``eth-web3://``, these are removed. -- Auto mode: Supported types: [ERC-4804](./eip-4804.md) supported only uint256, bytes32, address, bytes, and string. This ERC add more types. -- Auto mode: Encoding of returned integers when a **returns** attribute is specified: [ERC-4804](./eip-4804.md) suggested in example 5 to encode integers as strings. This ERC indicates to follow the Ethereum JSON RPC spec and encode integers as a hex string, prefixed with "0x". - -## Rationale - -The purpose of the proposal is to add a decentralized presentation layer for Ethereum. With the layer, we are able to render any web content (including HTML/CSS/JPG/PNG/SVG, etc) on-chain using human-readable URLs, and thus EVM can be served as a decentralized backend. The design of the standard is based on the following principles: - -- **Human-readable**. The Web3 URL should be easily recognized by human similar to Web2 URL (`http://`). As a result, we support names from name services to replace address for better readability. In addition, instead of using calldata in hex, we use human-readable method + arguments and translate them to calldata for better readability. - -- **Maximum-Compatible with HTTP-URL standard**. The Web3 URL should be compatible with HTTP-URL standard including relative pathing, query, fragment, percent-encoding, etc so that the support of existing HTTP-URL (e.g., by browser) can be easily extended to Web3 URL with minimal modification. This also means that existing Web2 users can easily migrate to Web3 with minimal extra knowledge of this standard. - -- **Simple**. Instead of providing explicit types in arguments, we use a "maximum likelihood" principle of auto-detecting the types of the arguments such as address, bytes32, and uint256. This could greatly minimize the length of URL, while avoiding confusion. In addition, explicit types are also supported to clear the confusion if necessary. - -- **Flexible**. The contract is able to override the encoding rule so that the contract has fine-control of understanding the actual Web resources that the users want to locate. - -## Security Considerations - -No security considerations were found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6860.md diff --git a/EIPS/eip-6864.md b/EIPS/eip-6864.md index c4ddcac52423b8..56da35bb3961ea 100644 --- a/EIPS/eip-6864.md +++ b/EIPS/eip-6864.md @@ -1,211 +1 @@ ---- -eip: 6864 -title: Upgradable Fungible Token -description: Upgradable fungible token, a simple extension to ERC-20 -author: Jeff Huang (@jeffishjeff) -discussions-to: https://ethereum-magicians.org/t/eip-6864-upgradable-fungible-token-a-simple-extension-to-erc-20/13781 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-05 -requires: 20 ---- - -## Abstract - -This proposal outlines a smart contract interface for upgrading/downgrading existing [ERC-20](./eip-20.md) smart contracts while maintaining user balances. The interface itself is an extension to the ERC-20 standard so that other smart contracts can continue to interact with the upgraded smart contract without changing anything other than the address. - -## Motivation - -By design, smart contracts are immutable and token standards like ERC-20 are minimalistic. While these design principles are fundamental in decentalized applications, there are sensible and practical situations where the ability to upgrade an ERC-20 token is desirable, such as: - -- to address bugs and remove limitations -- to adopt new features and standards -- to comply w/ changing regulations - -Proxy pattern using `delegatecall` opcode offers a reasonable, generalized solution to reconcile the immutability and upgradability features but has its own shortcomings: - -- the smart contracts must support proxy pattern from the get go, i.e. it cannot be used on contracts that were not deployed with proxies -- upgrades are silent and irreversible, i.e. users do not have the option to opt-out - -In contrast, by reducing the scope to specifically ERC-20 tokens, this proposal standardizes an ERC-20 extension that works with any existing or future ERC-20 smart contracts, is much simpler to implement and to maintain, can be reversed or nested, and offers a double confirmation opportunity for any and all users to explicitly opt-in on the upgrade. - -[ERC-4931](./eip-4931.md) attepts to address the same problem by introducing a third "bridge" contract to help facilitate the upgrade/downgrade operations. While this design decouples upgrade/downgrade logic from token logic, ERC-4931 would require tokens to be pre-minted at the destination smart contract and owned by the bridge contrtact rather then just-in-time when upgrade is invoked. It also would not be able to support upgrade-while-transfer and see-through functions as described below. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -```solidity -pragma solidity ^0.8.0; - -/** - @title Upgradable Fungible Token - @dev See https://eips.ethereum.org/EIPS/eip-6864 - */ -interface IERC6864 is IERC20 { - /** - @dev MUST be emitted when tokens are upgraded - @param from Previous owner of base ERC-20 tokens - @param to New owner of ERC-6864 tokens - @param amount The amount that is upgraded - */ - event Upgrade(address indexed from, address indexed to, uint256 amount); - - /** - @dev MUST be emitted when tokens are downgraded - @param from Previous owner of ERC-6864 tokens - @param to New owner of base ERC-20 tokens - @param amount The amount that is downgraded - */ - event Downgrade(address indexed from, address indexed to, uint256 amount); - - /** - @notice Upgrade `amount` of base ERC-20 tokens owned by `msg.sender` to ERC-6864 tokens under `to` - @dev `msg.sender` must directly own sufficient base ERC-20 tokens - MUST revert if `to` is the zero address - MUST revert if `msg.sender` does not directly own `amount` or more of base ERC-20 tokens - @param to The address to receive ERC-6864 tokens - @param amount The amount of base ERC-20 tokens to upgrade - */ - function upgrade(address to, uint256 amount) external; - - /** - @notice Downgrade `amount` of ERC-6864 tokens owned by `from` to base ERC-20 tokens under `to` - @dev `msg.sender` must either directly own or be approved to spend sufficient ERC-6864 tokens for `from` - MUST revert if `to` is the zero address - MUST revert if `from` does not directly own `amount` or more of ERC-6864 tokens - MUST revret if `msg.sender` is not `from` and is not approved to spend `amount` or more of ERC-6864 tokens for `from` - @param from The address to release ERC-6864 tokens - @param to The address to receive base ERC-20 tokens - @param amount The amount of ERC-6864 tokens to downgrade - */ - function downgrade(address from, address to, uint256 amount) external; - - /** - @notice Get the base ERC-20 smart contract address - @return The address of the base ERC-20 smart contract - */ - function baseToken() external view returns (address); -} -``` - -### See-through Extension - -The **see-through extension** is OPTIONAL. It allows for easy viewing of combined states between this [ERC-6864](./eip-6864.md) and base ERC-20 smart contracts. - -```solidity -pragma solidity ^0.8.0; - -interface IERC6864SeeThrough is IERC6864 { - /** - @notice Get the combined total token supply between this ERC-6864 and base ERC-20 smart contracts - @return The combined total token supply - */ - function combinedTotalSupply() external view returns (uint256); - - /** - @notice Get the combined token balance of `account` between this ERC-6864 and base ERC-20 smart contracts - @param account The address that owns the tokens - @return The combined token balance - */ - function combinedBalanceOf(address account) external view returns (uint256); - - /** - @notice Get the combined allowance that `spender` is allowed to spend for `owner` between this ERC-6864 and base ERC-20 smart contracts - @param owner The address that owns the tokens - @param spender The address that is approve to spend the tokens - @return The combined spending allowance - */ - function combinedAllowance(address owner, address spender) external view returns (uint256); -} - -``` - -## Rationale - -### Extending ERC-20 standard - -The goal of this proposal is to upgrade without affecting user balances, therefore leveraging existing data structure and methods is the path of the least engineering efforts as well as the most interoperability. - -### Supporting downgrade - -The ability to downgrade makes moving between multiple IERC-6864 implementations on the same base ERC-20 smart contract possible. It also allows a way out should bugs or limitations discovered on ERC-6864 smart contract, or the user simply changes his or her mind. - -### Optional see-through extension - -While these functions are useful in many situations, they are trivial to implement and results can be calculated via other public functions, hence the decision to include them in an optional extension rather than the core interface. - -## Backwards Compatibility - -ERC-6864 is generally compatible with the ERC-20 standard. The only caveat is that some smart contracts may opt to implement `transfer` to work with the entire combined balance (this reduces user friction, see reference implementation) rather than the standard `balanceOf` amount. In this case it is RECOMMENDED that such contract to implement `totalSupply` and `balanceOf` to return combined amount between this ERC-6864 and base ERC-20 smart contracts - -## Reference Implementation - -```solidity -import {IERC20, ERC20} from "@openzeppelin-contracts/token/ERC20/ERC20.sol"; - -contract ERC6864 is IERC6864, ERC20 { - IERC20 private immutable s_baseToken; - - constructor(string memory name, string memory symbol, address baseToken_) ERC20(name, symbol) { - s_baseToken = IERC20(baseToken_); - } - - function baseToken() public view virtual override returns (address) { - return address(s_baseToken); - } - - function upgrade(address to, uint256 amount) public virtual override { - address from = _msgSender(); - - s_baseToken.transferFrom(from, address(this), amount); - _mint(to, amount); - - emit Upgrade(from, to, amount); - } - - function downgrade(address from, address to, uint256 amount) public virtual override { - address spender = _msgSender(); - - if (from != spender) { - _spendAllowance(from, spender, amount); - } - _burn(from, amount); - s_baseToken.transfer(to, amount); - - emit Downgrade(from, to, amount); - } - - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address from = _msgSender(); - uint256 balance = balanceOf(from); - - if (balance < amount) { - upgrade(from, amount - balance); - } - - _transfer(from, to, amount); - return true; - } - - function totalSupply() public view virtual override returns (uint256) { - return return super.totalSupply() + s_baseToken.totalSupply() - s_baseToken.balanceOf(address(this)); - } - - function balanceOf(address account) public view virtual override returns (uint256) { - return super.balanceOf(account) + s_baseToken.balanceOf(account); - } -} -``` - -## Security Considerations - -- User who opts to upgrade base ERC-20 tokens must first `approve` the ERC-6864 smart contract to spend them. Therefore it's the user's responsibility to verify that the ERC-6864 smart contract is sound and secure, and the amount that he or she is approving is approperiate. This represents the same security considerations as with any `approve` operation. -- The ERC-6864 smart contract may implement any conversion function for upgrade/downgrade as approperiate: 1-to-1, linear, non-linear. In the case of a non-linear conversion function, `upgrade` and `downgrade` may be vulnerable for front running or sandwich attacks (whether or not to the attacker's benefit). This represents the same security considerations as with any automated market maker (AMM) that uses a similar non-linear curve for conversion. -- The ERC-6864 smart contract may ask user to approve unlimited allowance and/or attempt to automatically upgrade during `transfer` (see reference implementation). This removes the chance for user to triple confirm his or her intension to upgrade (`approve` being the double confirmation). -- Multiple IERC-6864 implementations can be applied to the same base ERC-20 token, and ERC-6864 smart contracts can be nested. This would increase token complexity and may cause existing dashboards to report incorrect or inconsistent results. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6864.md diff --git a/EIPS/eip-6865.md b/EIPS/eip-6865.md index 0bf78ab6806b7b..5e2c2f384f52c5 100644 --- a/EIPS/eip-6865.md +++ b/EIPS/eip-6865.md @@ -1,155 +1 @@ ---- -eip: 6865 -title: On-Chain EIP-712 Visualization -description: Visualize structured data highlighting the potential consequences for users' assets -author: Abderrahmen Hanafi (@a6-dou) -discussions-to: https://ethereum-magicians.org/t/eip-6865-on-chain-eip-712-visualization/13800 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-10 -requires: 712 ---- - -## Abstract - -Numerous protocols employ distinct [EIP-712](./eip-712.md) schemas, leading to unavoidable inconsistencies across the ecosystem. To address this issue, we propose a standardized approach for dApps to implement an on-chain view function called `visualizeEIP712Message`. This function takes an abi encoded EIP-712 payload message as input and returns a universally agreed-upon structured data format that emphasizes the potential impact on users' assets. Wallets can then display this structured data in a user-friendly manner, ensuring a consistent experience for end-users when interacting with various dApps and protocols. - -## Motivation - -The rapid expansion of the web3.0 ecosystem has unlocked numerous opportunities and innovations. However, this growth has also heightened users' vulnerability to security threats, such as phishing scams. Ensuring that users have a comprehensive understanding of the transactions they sign is crucial for mitigating these risks. - -In an attempt to address this issue, we developed an in-house, open-source off-chain SDK for wallets to visualize various protocols. However, we encountered several challenges along the way: - -- Scalability: Identifying and understanding all protocols that utilize EIP-712 and their respective business logic is a daunting task, particularly with limited resources. Crafting an off-chain solution for all these protocols is nearly impossible. -- Reliability: Grasping each protocol's business logic is difficult and may lead to misunderstandings of the actual implementation. This can result in inaccurate visualizations, which could be more detrimental than providing no visualization at all. -- Maintainability: Offering support for protocols with an off-chain solution is insufficient in a rapidly evolving ecosystem. Protocols frequently upgrade their implementations by extending features or fixing bugs, further complicating the maintenance process. - -To overcome these challenges, we propose a standardized, on-chain solution that can accommodate the diverse and ever-changing web3.0 ecosystem. This approach would enhance scalability, reliability, and maintainability by shifting the responsibility of visualizing EIP-712 payloads from the wallets to the protocols themselves. Consequently, wallets can use a consistent and effective approach to EIP-712 message visualization. - -The adoption of a universal solution will not only streamline the efforts and reduce the maintenance burden for wallet providers, but it will also allow for faster and more extensive coverage across the ecosystem. This will ultimately result in users gaining a clearer understanding of the transactions they're signing, leading to increased security and an improved overall user experience within the crypto space. - -Currently, most of the wallets display something similar to image below - -![](../assets/eip-6865/current-EIP-712-signature-wallet-interface.png) - -With visualization we can achieve something similar to image below where more insightful details are revealed to user thanks to the structured data returned from the EIP - -![](../assets/eip-6865/vision-EIP-712-signature-wallet-interface.png) - -## 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. - -Contracts implementing this proposal MUST include the `visualizeEIP712Message` function in the `verifyingContract` implementation so that wallets upon receiving a request to sign an EIP-712 message(`eth_signTypedData`) MAY call the function `visualizeEIP712Message` at the smart contract and chain specified in the EIP-712 message domain separator `verifyingContract` and `chainId` fields, respectively. - -Wallets SHOULD ignore this proposal if the domain separator does not include the `verifyingContract` and `chainId` fields. - -```solidity -/** -* @notice This function processes an EIP-712 payload message and returns a structured data format emphasizing the potential impact on users' assets. -* @dev The function returns assetsOut (assets the user is offering), assetsIn (assets the user would receive), and liveness (validity duration of the EIP-712 message). -* @param encodedMessage The ABI-encoded EIP-712 message (abi.encode(types, params)). -* @param domainHash The hash of the EIP-712 domain separator as defined in the EIP-712 proposal; see https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator. -* @return Result struct containing the user's assets impact and message liveness. -*/ -function visualizeEIP712Message( - bytes memory encodedMessage, - bytes32 domainHash -) external view returns (Result memory); -``` - -### Params - -`encodedMessage` is bytes that represents the encoded EIP-712 message with `abi.encode` and it can be decoded using `abi.decode` - -`domainHash` is the bytes32 hash of the EIP-712 domain separator as defined in the EIP-712 proposal - -### Outputs - -The function MUST return `Result`, a struct that contains information's about user’s assets impact and the liveness of such a message if it gets signed. - -```solidity -struct Liveness { - uint256 from; - uint256 to; -} - -struct UserAssetMovement { - address assetTokenAddress; - uint256 id; - uint256[] amounts; -} - -struct Result { - UserAssetMovement[] assetsIn; - UserAssetMovement[] assetsOut; - Liveness liveness; -} -``` - -#### `Liveness` - -`Liveness` is a struct that defines the timestamps which the message is valid where: - -- `from` is the starting timestamp. -- `to` is the expiry timestamp -- `from` MUST be less than `to` - -#### `UserAssetMovement` - -`UserAssetMovement` defines the user’s asset where: - -- `assetTokenAddress` is the token ([ERC-20](./eip-20.md), [ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md)) smart contract address where the zero address MUST represents the Native coin (Native ETH in the case of Ethereum network). -- `id` is the NFT ID, this item MUST ignored if the asset is not an NFT - - if token with `id` doesn’t exist in an NFT collection, this SHOULD be considered as any token within that collection -- `amounts` is an Array of `uint256` where items MUST define the amount per time curve, with time defined within liveness boundaries - - the first amount in `amounts` Array (amounts[0]) MUST be the amount of the asset at `liveness.from` timestamp - - the last amount in `amounts` Array (amounts[amounts.length-1]) MUST be the amount of the asset at `liveness.to` timestamp - - in most of the cases, `amounts` will be an Array with a single item which is MUST be the minimum amount of the asset. - -#### `assetsIn` - -`assetsIn` are the minimum assets which the user MUST get if the message is signed and fulfilled - -#### `assetsOut` - -`assetsOut` are the maximum assets which the user MUST offer if the message is signed and fulfilled - -## Rationale - -### on-chain - -One might argue that certain processes can be done off-chain, which is true, but our experience building an off-chain TypeScript SDK to solve this matter revealed some issues: - -- Reliability: Protocols developers are the ones responsible for developing the protocol itself, thus crafting the visualization is much more accurate when done by them. -- Scalability: Keeping up with the rapidly expanding ecosystem is hard. Wallets or 3rd party entities must keep an eye on each new protocol, understand it carefully (which poses the reliability issues mentioned above), and then only come up with an off-chain implementation. -- Maintainability: Many protocols implement smart contracts in an upgradable manner. This causes the off-chain visualization to differ from the real protocol behaviors (if updated), making the solution itself unreliable and lacking the scalability to handle various protocols. - -### `DomainHash` - -The `domainHash` is much needed by protocols to revert against unsupported versions of its EIP-712 implementation. It identifies the needed implementation in case the protocol implements various EIP-712 implementations (`name`) or to revert if the `domainHash` belongs to a different protocol. - -In the future, if there is a registry that reroutes this EIP implementation for already deployed protocols that can't upgrade the existing deployed smart contract, `domainHash` can be used to identify protocols. - -### Amounts Array - -We suggest using an array of amounts (uint256[]) instead of a single uint256 to cover auctions, which are common in NFT protocols. - -## Backwards Compatibility - -No backward compatibility issues found. - -## Reference Implementation - -openSea Seaport NFT marketplace implementation example is available [here](../assets/eip-6865/contracts/SeaPortEIP712Visualizer.sol) - -## Security Considerations - -`visualizeEIP712Message` function should be reliable and accurately represent the potential impact of the EIP-712 message on users' assets. Wallet providers and users must trust the protocol's implementation of this function to provide accurate and up-to-date information. - -`visualizeEIP712Message` function results should be treated based on the reputation of its `verifyingContract`, if the `verifyingContract` is trusted it means the `visualizeEIP712Message` function results are trusted as the this proposal implementation lives at the same address of `verifyingContract`. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6865.md diff --git a/EIPS/eip-6900.md b/EIPS/eip-6900.md index 70dbe1a4bb423b..b804ef8a93f5b6 100644 --- a/EIPS/eip-6900.md +++ b/EIPS/eip-6900.md @@ -1,561 +1 @@ ---- -eip: 6900 -title: Modular Smart Contract Accounts and Plugins -description: Interfaces for composable contract accounts optionally supporting upgradability and introspection -author: Adam Egyed (@adamegyed), Fangting Liu (@trinity-0111), Jay Paik (@jaypaik), Yoav Weiss (@yoavw) -discussions-to: https://ethereum-magicians.org/t/eip-modular-smart-contract-accounts-and-plugins/13885 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-18 -requires: 165, 4337 ---- - -## Abstract - -This proposal standardizes smart contract accounts and account plugins, which are smart contract interfaces that allow for composable logic within smart contract accounts. This proposal is compliant with [ERC-4337](./eip-4337.md), and takes inspiration from [ERC-2535](./eip-2535.md) when defining interfaces for updating and querying modular function implementations. - -This modular approach splits account functionality into three categories, implements them in external contracts, and defines an expected execution flow from accounts. - -## Motivation - -One of the goals that ERC-4337 accomplishes is abstracting the logic for execution and validation to each smart contract account. - -Many new features of accounts can be built by customizing the logic that goes into the validation and execution steps. Examples of such features include session keys, subscriptions, spending limits, and role-based access control. Currently, some of these features are implemented natively by specific smart contract accounts, and others are able to be implemented by plugin systems. Examples of proprietary plugin systems include Safe modules and ZeroDev plugins. - -However, managing multiple account instances provides a worse user experience, fragmenting accounts across supported features and security configurations. Additionally, it requires plugin developers to choose which platforms to support, causing either platform lock-in or duplicated development effort. - -We propose a standard that coordinates the implementation work between plugin developers and wallet developers. This standard defines a modular smart contract account capable of supporting all standard-conformant plugins. This allows users to have greater portability of their data, and for plugin developers to not have to choose specific account implementations to support. - -![diagram showing relationship between accounts and plugins with modular functions](../assets/eip-6900/MSCA_Shared_Components_Diagram.svg) - -We take inspiration from ERC-2535's diamond pattern for routing execution based on function selectors, and create a similarly composable account. However, the standard does not require the multi-facet proxy pattern. - -These plugins can contain execution logic, validation schemes, and hooks. Validation schemes define the circumstances under which the smart contract account will approve actions taken on its behalf, while hooks allow for pre- and post-execution controls. - -Accounts adopting this standard will support modular, upgradable execution and validation logic. Defining this as a standard for smart contract accounts will make plugins easier to develop securely and will allow for greater interoperability. - -Goals: - -- Provide standards for how validation, execution, and hook functions for smart contract accounts should be written. -- Provide standards for how compliant accounts should add, update, remove, and inspect plugins. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Terms - -- An **account** (or **smart contract account, SCA**) is a smart contract that can be used to send transactions and hold digital assets. It implements the `IAccount` interface from ERC-4337. -- A **modular account** (or **modular smart contract account, MSCA**) is an account that supports modular functions. There are three types of modular functions: - - **Validation functions** validate the caller's authenticity and authority to the account. - - **Execution functions** execute any custom logic allowed by the account. - - **Hooks** execute custom logic and checks before and/or after an execution function or validation function. -- A **validation function** is a function that validates authentication and authorization of a caller to the account. There are two types of validation functions: - - **User Operation Validation** functions handle calls to `validateUserOp` and check the validity of an ERC-4337 user operation. - - **Runtime Validation** functions run before an execution function when not called via a user operation, and enforce checks. Common checks include allowing execution only by an owner. -- An **execution function** is a smart contract function that defines the main execution step of a function for a modular account. -- The **standard execute** functions are two specific execute functions that are implemented natively by the modular account, and not on a plugin. These allow for open-ended execution. -- A **hook** is a smart contract function executed before or after another function, with the ability to modify state or cause the entire call to revert. There are four types of hooks: - - **Pre User Operation Validation Hook** functions run before user operation validation functions. These can enforce permissions on what actions a validation function may perform via user operations. - - **Pre Runtime Validation Hook** functions run before runtime validation functions. These can enforce permissions on what actions a validation function may perform via direct calls. - - **Pre Execution Hook** functions run before an execution function. They may optionally return data to be consumed by their related post execution hook functions. - - **Post Execution Hook** functions run after an execution function. They may optionally take returned data from their related pre execution hook functions. -- An **associated function** refers to either a validation function or a hook. -- A **native function** refers to a function implemented natively by the modular account, as opposed to a function added by a plugin. -- A **plugin** is a deployed smart contract that hosts any amount of the above three kinds of modular functions: execution functions, validation functions, or hooks. -- A plugin **manifest** is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the plugin’s metadata, dependency requirements, and permissions. - -### Overview - -A modular account handles two kinds of calls: either from the `Entrypoint` through ERC-4337, or through direct calls from externally owned accounts (EOAs) and other smart contracts. This standard supports both use cases. - -A call to the smart contract account can be broken down into the steps as shown in the diagram below. The validation steps validate if the caller is allowed to perform the call. The pre execution hook step can be used to do any pre execution checks or updates. It can also be used along with the post execution hook step to perform additional actions or verification. The execution step performs a defined task or collection of tasks. - -![diagram showing call flow within an modular account](../assets/eip-6900/Modular_Account_Call_Flow.svg) - -The following diagram shows permitted plugin execution flows. During a plugin's execution step from the above diagram, the plugin may perform a "Plugin Execution Function", using either `executeFromPlugin` or `executeFromPluginExternal`. These can be used by plugins to execute using the account's context. - -- `executeFromPlugin` handles calls to other installed plugin's execution function on the modular account. -- `executeFromPluginExternal` handles calls to external contracts. - -![diagram showing a plugin execution flow](../assets/eip-6900/Plugin_Execution_Flow.svg) - -Each step is modular, supporting different implementations for each execution function, and composable, supporting multiple steps through hooks. Combined, these allow for open-ended programmable accounts. - -### Interfaces - -**Modular Smart Contract Accounts** **MUST** implement - -- `IAccount` from [ERC-4337](./eip-4337.md). -- `IPluginManager.sol` to support installing and uninstalling plugins. -- `IStandardExecutor.sol` to support open-ended execution. **Calls to plugins through this SHOULD revert.** -- `IPluginExecutor.sol` to support execution from plugins. **Calls to plugins through `executeFromPluginExternal` SHOULD revert.** - -**Modular Smart Contract Accounts** **MAY** implement - -- `IPluginLoupe.sol` to support visibility in plugin configuration on-chain. - -**Plugins** **MUST** implement - -- `IPlugin` interface described below and implement [ERC-165](./eip-165.md) for `IPlugin`. - -#### `IPluginManager.sol` - -Plugin manager interface. Modular Smart Contract Accounts **MUST** implement this interface to support installing and uninstalling plugins. - -```solidity -interface IPluginManager { - // Pre/post exec hooks added by the user to limit the scope a plugin has - // These hooks are injected at plugin install time - struct InjectedHook { - // The plugin that provides the hook - address providingPlugin; - // Either a plugin-defined execution function, or the native function executeFromPluginExternal - bytes4 selector; - InjectedHooksInfo injectedHooksInfo; - bytes hookApplyData; - } - - struct InjectedHooksInfo { - uint8 preExecHookFunctionId; - bool isPostHookUsed; - uint8 postExecHookFunctionId; - } - - event PluginInstalled(address indexed plugin, bytes32 manifestHash); - event PluginUninstalled(address indexed plugin, bytes32 manifestHash, bool onUninstallSucceeded); - - /// @notice Install a plugin to the modular account. - /// @param plugin The plugin to install. - /// @param manifestHash The hash of the plugin manifest. - /// @param pluginInitData Optional data to be decoded and used by the plugin to setup initial plugin data for - /// the modular account. - /// @param dependencies The dependencies of the plugin, as described in the manifest. - /// @param injectedHooks Optional hooks to be injected over permitted calls this plugin may make. - function installPlugin( - address plugin, - bytes32 manifestHash, - bytes calldata pluginInitData, - address[] calldata dependencies, - InjectedHook[] calldata injectedHooks - ) external; - - /// @notice Uninstall a plugin from the modular account. - /// @param plugin The plugin to uninstall. - /// @param config An optional, implementation-specific field that accounts may use to ensure consistency - /// guarantees. - /// @param pluginUninstallData Optional data to be decoded and used by the plugin to clear plugin data for the - /// modular account. - /// @param hookUnapplyData Optional data to be decoded and used by the plugin to clear injected hooks for the - /// modular account. - function uninstallPlugin( - address plugin, - bytes calldata config, - bytes calldata pluginUninstallData, - bytes[] calldata hookUnapplyData - ) external; -} - -``` - -#### `IStandardExecutor.sol` - -Standard execute interface. Modular Smart Contract Accounts **MUST** implement this interface to support open-ended execution. - -Standard execute functions SHOULD check whether the call's target implements the `IPlugin` interface via ERC-165. - -**If the target is a plugin, the call SHOULD revert.** This prevents accidental misconfiguration or misuse of plugins (both installed and uninstalled). - -```solidity -struct Execution { - // The target contract for account to execute. - address target; - // The value for the execution. - uint256 value; - // The call data for the execution. - bytes data; -} - -interface IStandardExecutor { - /// @notice Standard execute method. - /// @dev If the target is a plugin, the call SHOULD revert. - /// @param target The target contract for account to execute. - /// @param value The value for the execution. - /// @param data The call data for the execution. - /// @return The return data from the call. - function execute(address target, uint256 value, bytes calldata data) external payable returns (bytes memory); - - /// @notice Standard executeBatch method. - /// @dev If the target is a plugin, the call SHOULD revert. - /// @param executions The array of executions. - /// @return An array containing the return data from the calls. - function executeBatch(Execution[] calldata executions) external payable returns (bytes[] memory); -} -``` - -#### `IPluginExecutor.sol` - -Execution interface for calls made from plugins. Modular Smart Contract Accounts **MUST** implement this interface to support execution from plugins. - -The `executeFromPluginExternal` function SHOULD check whether the call's target implements the `IPlugin` interface via ERC-165. - -**If the target of `executeFromPluginExternal` function is a plugin, the call SHOULD revert.** - -This prevents accidental misconfiguration or misuse of plugins (both installed and uninstalled). Installed plugins MAY interact with other installed plugins via the `executeFromPlugin` function. - -```solidity -interface IPluginExecutor { - /// @notice Method from calls made from plugins. - /// @param data The call data for the call. - /// @return The return data from the call. - function executeFromPlugin(bytes calldata data) external payable returns (bytes memory); - - /// @notice Method from cals made from plugins. - /// @dev If the target is a plugin, the call SHOULD revert. - /// @param target The target of the external contract to be called. - /// @param value The value to pass. - /// @param data The data to pass. - function executeFromPluginExternal(address target, uint256 value, bytes calldata data) external payable; -} -``` - -#### `IPluginLoupe.sol` - -Plugin inspection interface. Modular Smart Contract Accounts **MAY** implement this interface to support visibility in plugin configuration on-chain. - -```solidity -// Treats the first 20 bytes as an address, and the last byte as a function identifier. -type FunctionReference is bytes21; - -interface IPluginLoupe { - // Config for a Plugin Execution function - struct ExecutionFunctionConfig { - address plugin; - FunctionReference userOpValidationFunction; - FunctionReference runtimeValidationFunction; - } - - struct ExecutionHooks { - FunctionReference preExecHook; - FunctionReference postExecHook; - } - - function getExecutionFunctionConfig(bytes4 selector) external view returns (ExecutionFunctionConfig memory); - - function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory); - - function getPermittedCallHooks(address callingPlugin, bytes4 selector) - external - view - returns (ExecutionHooks[] memory); - - function getPreUserOpValidationHooks(bytes4 selector) external view returns (FunctionReference[] memory); - - function getPreRuntimeValidationHooks(bytes4 selector) external view returns (FunctionReference[] memory); - - function getInstalledPlugins() external view returns (address[] memory); -} -``` - -#### `IPlugin.sol` - -Plugin interface. Plugins **MUST** implement this interface to support plugin management and interactions with MSCAs. - -```solidity -interface IPlugin { - /// @notice Initialize plugin data for the modular account. - /// @dev Called by the modular account during `installPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to setup initial plugin data for the modular account. - function onInstall(bytes calldata data) external; - - /// @notice Clear plugin data for the modular account. - /// @dev Called by the modular account during `uninstallPlugin`. - /// @param data Optional bytes array to be decoded and used by the plugin to clear plugin data for the modular account. - function onUninstall(bytes calldata data) external; - - /// @notice A hook that runs when a hook this plugin owns is installed onto another plugin - /// @dev Optional, use to implement any required setup logic - /// @param pluginAppliedOn The plugin that the hook is being applied on - /// @param injectedHooksInfo Contains pre/post exec hook information - /// @param data Any optional data for setup - function onHookApply( - address pluginAppliedOn, - IPluginManager.InjectedHooksInfo calldata injectedHooksInfo, - bytes calldata data - ) external; - - /// @notice A hook that runs when a hook this plugin owns is unapplied from another plugin - /// @dev Optional, use to implement any required unapplied logic - /// @param pluginAppliedOn The plugin that the hook was applied on - /// @param injectedHooksInfo Contains pre/post exec hook information - /// @param data Any optional data for the unapplied call - function onHookUnapply( - address pluginAppliedOn, - IPluginManager.InjectedHooksInfo calldata injectedHooksInfo, - bytes calldata data - ) external; - - /// @notice Run the pre user operation validation hook specified by the `functionId`. - /// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. - /// @param userOp The user operation. - /// @param userOpHash The user operation hash. - /// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes). - function preUserOpValidationHook(uint8 functionId, UserOperation memory userOp, bytes32 userOpHash) external returns (uint256); - - /// @notice Run the user operation validationFunction specified by the `functionId`. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. - /// @param userOp The user operation. - /// @param userOpHash The user operation hash. - /// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes). - function userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash) - external - returns (uint256); - - /// @notice Run the pre runtime validation hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. - /// @param sender The caller address. - /// @param value The call value. - /// @param data The calldata sent. - function preRuntimeValidationHook(uint8 functionId, address sender, uint256 value, bytes calldata data) external; - - /// @notice Run the runtime validationFunction specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be - /// more than one. - /// @param sender The caller address. - /// @param value The call value. - /// @param data The calldata sent. - function runtimeValidationFunction(uint8 functionId, address sender, uint256 value, bytes calldata data) - external; - - /// @notice Run the pre execution hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. - /// @param sender The caller address. - /// @param value The call value. - /// @param data The calldata sent. - /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint8 functionId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); - - /// @notice Run the post execution hook specified by the `functionId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param functionId An identifier that routes the call to different internal implementations, should there be more than one. - /// @param preExecHookData The context returned by its associated pre execution hook. - function postExecutionHook(uint8 functionId, bytes calldata preExecHookData) external; - - /// @notice Describe the contents and intended configuration of the plugin. - /// @dev The manifest MUST stay constant over time. - /// @return A manifest describing the contents and intended configuration of the plugin. - function pluginManifest() external pure returns (PluginManifest memory); -} -``` - -### Plugin manifest - -The plugin manifest is responsible for describing the execution functions, validation functions, and hooks that will be configured on the MSCA during installation, as well as the plugin's metadata, dependencies, and permissions. - -```solidity -enum ManifestAssociatedFunctionType { - /// @notice Function is not defined. - NONE, - /// @notice Function belongs to this plugin. - SELF, - /// @notice Function belongs to an external plugin provided as a dependency during plugin installation. - DEPENDENCY, - /// @notice Resolves to a magic value to always bypass runtime validation for a given function. - /// This is only assignable on runtime validation functions. If it were to be used on a user op validationFunction, - /// it would risk burning gas from the account. When used as a hook in any hook location, it is equivalent to not - /// setting a hook and is therefore disallowed. - RUNTIME_VALIDATION_ALWAYS_ALLOW, - /// @notice Resolves to a magic value to always fail in a hook for a given function. - /// This is only assignable to pre hooks (pre validation and pre execution). It should not be used on - /// validation functions themselves, because this is equivalent to leaving the validation functions unset. - /// It should not be used in post-exec hooks, because if it is known to always revert, that should happen - /// as early as possible to save gas. - PRE_HOOK_ALWAYS_DENY -} - -struct ManifestExecutionFunction { - bytes4 selector; - string[] permissions; -} - -// For functions of type `ManifestAssociatedFunctionType.DEPENDENCY`, the MSCA MUST find the plugin address -// of the function at `dependencies[dependencyIndex]` during the call to `installPlugin(config)`. -struct ManifestFunction { - ManifestAssociatedFunctionType functionType; - uint8 functionId; - uint256 dependencyIndex; -} - -struct ManifestAssociatedFunction { - bytes4 executionSelector; - ManifestFunction associatedFunction; -} - -struct ManifestExecutionHook { - bytes4 selector; - ManifestFunction preExecHook; - ManifestFunction postExecHook; -} - -struct PluginManifest { - // A human-readable name of the plugin. - string name; - // The version of the plugin, following the semantic versioning scheme. - string version; - // The author field SHOULD be a username representing the identity of the user or organization - // that created this plugin. - string author; - - // List of ERC-165 interfaceIds to add to account to support introspection checks. - bytes4[] interfaceIds; - - // If this plugin depends on other plugins' validation functions and/or hooks, the interface IDs of - // those plugins MUST be provided here, with its position in the array matching the `dependencyIndex` - // members of `ManifestFunction` structs used in the manifest. - bytes4[] dependencyInterfaceIds; - - // Execution functions defined in this plugin to be installed on the MSCA. - ManifestExecutionFunction[] executionFunctions; - - // Native functions or execution functions already installed on the MSCA that this plugin will be - // able to call. - bytes4[] permittedExecutionSelectors; - - // External contract calls that this plugin will be able to make. - bool permitAnyExternalContract; - ManifestExternalCallPermission[] permittedExternalCalls; - - ManifestAssociatedFunction[] userOpValidationFunctions; - ManifestAssociatedFunction[] runtimeValidationFunctions; - ManifestAssociatedFunction[] preUserOpValidationHooks; - ManifestAssociatedFunction[] preRuntimeValidationHooks; - ManifestExecutionHook[] executionHooks; - ManifestExecutionHook[] permittedCallHooks; -} -``` - -### Expected behavior - -#### Responsibilties of `StandardExecutor` and `PluginExecutor` - -`StandardExecutor` functions are mainly used for open-ended execution of external contracts. - -`PluginExecutor` functions are specifically used by plugins to request the account to execute with account's context. Explicit permissions are required for plugins to use `PluginExecutor`. - -The following behavior must be followed: - -- `StandardExecutor` can NOT call plugin execution functions and/or `PluginExecutor`. This is guaranteed by checking whether the call's target implements the `IPlugin` interface via ERC-165 as required. -- `StandardExecutor` can NOT be called by plugin execution functions and/or `PluginExecutor`. -- Plugin execution functions MUST NOT request access to `StandardExecutor`, they MAY request access to `PluginExecutor`. - -#### Calls to `installPlugin` - -The function `installPlugin` accepts 5 parameters: the address of the plugin to install, the Keccak-256 hash of the plugin's manifest, ABI-encoded data to pass to the plugin's `onInstall` callback, an array of function references that represent the plugin's install dependencies, and permitted call hooks to apply during installation. - -The function MUST retrieve the plugin's manifest by calling `pluginManifest()` using `staticcall`. - -The function MUST perform the following preliminary checks: - -- Revert if the plugin has already been installed on the modular account. -- Revert if the plugin does not implement ERC-165 or does not support the `IPlugin` interface. -- Revert if `manifestHash` does not match the computed Keccak-256 hash of the plugin's returned manifest. This prevents installation of plugins that attempt to install a different plugin configuration than the one that was approved by the client. -- Revert if any address in `dependencies` does not support the interface at its matching index in the manifest's `dependencyInterfaceIds`, or if the two array lengths do not match, or if any of the dependencies are not already installed on the modular account. - -The function MUST record the manifest hash, dependencies, and permitted call hooks that were used for the plugin's installation. Each dependency's record MUST also be updated to reflect that it has a new dependent. These records MUST be used to ensure calls to `uninstallPlugin` are comprehensive and undo all editted configuration state from installation. The mechanism by which these records are stored and validated is up to the implementation. - -The function MUST store the plugin's permitted function selectors and external contract calls to be able to validate calls to `executeFromPlugin` and `executeFromPluginExternal`. - -The function MUST parse through the execution functions, validation functions, and hooks in the manifest and add them to the modular account after resolving each `ManifestFunction` type. - -- Each function selector MUST be added as a valid execution function on the modular account. If the function selector has already been added or matches the selector of a native function, the function SHOULD revert. -- If an associated function that is to be added already exists, the function SHOULD revert. - -Next, the function MUST call the plugin's `onInstall` callback with the data provided in the `installData` parameter. This serves to initialize the plugin state for the modular account. If `onInstall` reverts, the `installPlugin` function MUST revert. - -For each injected hook provided, the function MUST add the permitted call hook using the currently installing plugin as the caller and the provided function selector as the execution function selector. Then, the function MUST call onHookApply with the user-provided initialization data, if the data is nonempty. - -Finally, the function must emit the event `PluginInstalled` with the plugin's address and a hash of its manifest. - -> **⚠️ The ability to install and uninstall plugins is very powerful. The security of these functions determines the security of the account. It is critical for modular account implementers to make sure the implementation of the functions in `IPluginManager` have the proper security consideration and access control in place.** - -#### Calls to `uninstallPlugin` - -The function `uninstallPlugin` accepts 3 parameters: the address of the plugin to uninstall, a bytes field that may have custom requirements or uses by the implementing account, and ABI-encoded data to pass to the plugin's `onUninstall` callback. - -The function MUST revert if the plugin is not installed on the modular account. - -The function SHOULD perform the following checks: - -- Revert if the hash of the manifest used at install time does not match the computed Keccak-256 hash of the plugin's current manifest. This prevents unclean removal of plugins that attempt to force a removal of a different plugin configuration than the one that was originally approved by the client for installation. To allow for removal of such plugins, the modular account MAY implement the capability for the manifest to be encoded in the config field as a parameter. -- Revert if there is at least 1 other installed plugin that depends on execution functions, validation functions, or hooks added by this plugin. Plugins used as dependencies must not be uninstalled while dependent plugins exist. - -The function SHOULD update account storage to reflect the uninstall via inspection functions, such as those defined by `IPluginLoupe`. Each dependency's record SHOULD also be updated to reflect that it has no longer has this plugin as a dependent. - -The function MUST remove records for the plugin's dependencies, injected permitted call hooks, permitted function selectors, and external contract calls. The hooks to remove MUST be exactly the same as what was provided during installation. It is up to the implementing modular account to decide how to keep this invariant. The config parameter field MAY be used. - -The function MUST parse through the execution functions, validation functions, and hooks in the manifest and remove them from the modular account after resolving each `ManifestFunction` type. - -Next, for each previously injected hook, the function MUST remove the permitted call hook using the currently uninstalling plugin as the caller and the provided function selector as the execution function selector. Then, the function MUST call onHookUnapply with the user-provided initialization data, if the data is nonempty. - -Finally, the function MUST call the plugin's `onUninstall` callback with the data provided in the `uninstallData` parameter. This serves to clear the plugin state for the modular account. If `onUninstall` reverts, execution SHOULD continue to allow the uninstall to complete. - -> **⚠️ Incorrectly uninstalled plugins can prevent uninstallation of their dependencies. Therefore, some form of validation that the uninstall step completely and correctly removes the plugin and its usage of dependencies is required.** - -#### Calls to `validateUserOp` - -When the function `validateUserOp` is called on modular account by the `EntryPoint`, it MUST find the user operation validation function associated to the function selector in the first four bytes of `userOp.callData`. If there is no function defined for the selector, or if `userOp.callData.length < 4`, then execution MUST revert. - -If the function selector has associated pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert, the outer call MUST revert. If any are set to `PRE_HOOK_ALWAYS_DENY`, the call must revert. If any return an `authorizer` value other than 0 or 1, execution MUST revert. If any return an `authorizer` value of 1, indicating an invalid signature, the returned validation data of the outer call MUST also be 1. If any return time-bounded validation by specifying either a `validUntil` or `validBefore` value, the resulting validation data MUST be the intersection of all time bounds provided. - -Then, the modular account MUST execute the validation function with the user operation and its hash as parameters using the `call` opcode. The returned validation data from the user operation validation function MUST be updated, if necessary, by the return values of any pre user operation validation hooks, then returned by `validateUserOp`. - -#### Calls to execution functions - -When a function other than a native function is called on an modular account, it MUST find the plugin configuration for the corresponding selector added via plugin installation. If no corresponding plugin is found, the modular account MUST revert. Otherwise, the following steps MUST be performed. - -Additionally, when the modular account natively implements functions in `IPluginManager` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other native functions MAY perform these steps. - -The steps to perform are: - -- If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If any are set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, those must not be run and treated as though they did not revert. -- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If any of these functions revert, execution MUST revert. -- Run the execution function. -- If any associated post execution hooks are defined, run the functions. If a pre execution hook returned data to the account, that data MUST be passed as a parameter to the associated post execution hook. The operation MUST be done with the `call` opcode. If any of these functions revert, execution MUST revert. Notably, for the `uninstallPlugin` native function, the post execution hooks defined for it prior to the uninstall MUST run afterwards. - -#### Calls made from plugins - -Plugins MAY interact with other plugins and external contracts through the modular account using the functions defined in the `IPluginExecutor` interface. These functions MAY be called without a defined validation function, but the modular account MUST enforce these checks and behaviors: - -The `executeFromPlugin` function MUST allow plugins to call execution functions installed by plugins on the modular account. Hooks matching the function selector provided in `data` MUST be called. If the calling plugin's manifest did not include the provided function selector within `permittedExecutionSelectors` at the time of installation, execution MUST revert. - -The `executeFromPluginExternal` function MUST allow plugins to call external contracts as specified by its parameters on behalf of the modular account. If the calling plugin's manifest did not explicitly allow the external contract call within `permittedExternalCalls` at the time of installation, execution MUST revert. - -## Rationale - -ERC-4337 compatible accounts must implement the `IAccount` interface, which consists of only one method that bundles validation with execution: `validateUserOp`. A primary design rationale for this proposal is to extend the possible functions for a smart contract account beyond this single method by unbundling these and other functions, while retaining the benefits of account abstraction. - -The function routing pattern of ERC-2535 is the logical starting point for achieving this extension into multi-functional accounts. It also meets our other primary design rationale of generalizing execution calls across multiple implementing contracts. However, a strict diamond pattern is constrained by its inability to customize validation schemes for specific execution functions in the context of `validateUserOp`, and its requirement of `delegatecall`. - -This proposal includes several interfaces that build on ERC-4337 and are inspired by ERC-2535. First, we standardize a set of modular functions that allow smart contract developers greater flexibility in bundling validation, execution, and hook logic. We also propose interfaces that take inspiration from the diamond standard and provide methods for querying execution functions, validation functions, and hooks on a modular account. The rest of the interfaces describe a plugin's methods for exposing its modular functions and desired configuration, and the modular account's methods for installing and removing plugins and allowing execution across plugins and external contracts. - -## Backwards Compatibility - -No backward compatibility issues found. - -## Reference Implementation - -See `https://github.com/alchemyplatform/ERC-6900-Ref-Implementation` - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6900.md diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index 403fe6cd2d00ba..6d1278b6d58604 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -1,633 +1 @@ ---- -eip: 6909 -title: Minimal Multi-Token Interface -description: A minimal specification for managing multiple tokens by their id in a single contract. -author: Joshua Trujillo (@jtriley-eth) -discussions-to: https://ethereum-magicians.org/t/eip-6909-multi-token-standard/13891 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-19 -requires: 165 ---- - -## Abstract - -The following standard specifies a multi-token contract as a simplified alternative to the [ERC-1155](./eip-1155.md) Multi-Token Standard. - -## Motivation - -The ERC-1155 standard includes unnecessary features such as requiring recipient accounts with code to implement callbacks returning specific values and batch-calls in the specification. In addition, the single operator permission scheme grants unlimited allowance on every token ID in the contract. Backwards compatibility is deliberately removed only where necessary. Additional features such as batch calls, increase and decrease allowance methods, and other user experience improvements are deliberately omitted in the specification to minimize the required external interface. - -According to ERC-1155, callbacks are required for each transfer and batch transfer to contract accounts. This requires potentially unnecessary external calls to the recipient when the recipient account is a contract account. While this behavior may be desirable in some cases, there is no option to opt-out of this behavior, as is the case for [ERC-721](./eip-721.md) having both `transferFrom` and `safeTransferFrom`. In addition to runtime performance of the token contract itself, it also impacts the runtime performance and codesize of recipient contract accounts, requiring multiple callback functions and return values to recieve the tokens. - -Batching transfers, while useful, are excluded from this standard to allow for opinionated batch transfer operations on different implementations. For example, a different ABI encoding may provide different benefits in different environments such as calldata size optimization for rollups with calldata storage commitments or runtime performance for environments with expensive gas fees. - -A hybrid allowance-operator permission scheme enables granular yet scalable controls on token approvals. Allowances enable an external account to transfer tokens of a single token ID on a user's behalf w by their ID while operators are granted full transfer permission for all token IDs for the user. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Definitions - -- infinite: The maximum value for a uint256 (`2 ** 256 - 1`). -- caller: The caller of the current context (`msg.sender`). -- spender: An account that transfers tokens on behalf of another account. -- operator: An account that has unlimited transfer permissions on all token ids for another account. -- mint: The creation of an amount of tokens. This MAY happen in a mint method or as a transfer from the zero address. -- burn: The removal an amount of tokens. This MAY happen in a burn method or as a transfer to the zero address. - -### Methods - -#### `totalSupply` - -The total `amount` of a token `id` that exists. - -MUST be equal to the sum of the `balanceOf` of all accounts of the token `id`. - -```yaml -- name: totalSupply - type: function - stateMutability: view - - inputs: - - name: id - type: uint256 - - outputs: - - name: amount - type: uint256 -``` - -#### `balanceOf` - -The total `amount` of a token `id` that an `owner` owns. - -```yaml -- name: balanceOf - type: function - stateMutability: view - - inputs: - - name: owner - type: address - - name: id - type: uint256 - - outputs: - - name: amount - type: uint256 -``` - -#### `allowance` - -The total `amount` of a token id that a spender is permitted to transfer on behalf of an owner. - -```yaml -- name: allowance - type: function - stateMutability: view - - inputs: - - name: owner - type: address - - name: spender - type: address - - name: id - type: uint256 - - outputs: - - name: amount - type: uint256 -``` - -#### `isOperator` - -Returns `true` if the `spender` is approved as an operator for an `owner`. - -```yaml -- name: isOperator - type: function - stateMutability: view - - inputs: - - name: owner - type: address - - name: spender - type: address - - outputs: - - name: status - type: bool -``` - -#### `transfer` - -Transfers an `amount` of a token `id` from the caller to the `receiver`. - -MUST revert when the caller's balance for the token `id` is insufficient. - -MUST log the `Transfer` event. - -MUST return True. - -```yaml -- name: transfer - type: function - stateMutability: nonpayable - - inputs: - - name: receiver - type: address - - name: id - type: uint256 - - name: amount - type: uint256 - - outputs: - - name: success - type: bool -``` - -#### `transferFrom` - -Transfers an `amount` of a token `id` from a `sender` to a `receiver` by the caller. - -MUST revert when the caller is not an operator for the `sender` and the caller's allowance for the token `id` for the `sender` is insufficient. - -MUST revert when the `sender`'s balance for the token id is insufficient. - -MUST log the `Transfer` event. - -MUST decrease the caller's `allowance` by the same `amount` of the `sender`'s balance decrease if the caller is not an operator for the `sender` and the caller's `allowance` is not infinite. - -SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the `allowance` is infinite. - -SHOULD NOT decrease the caller's `allowance` for the token `id` for the `sender` if the caller is an operator. - -MUST return True. - -```yaml -- name: transferFrom - type: function - stateMutability: nonpayable - - inputs: - - name: sender - type: address - - name: receiver - type: address - - name: id - type: uint256 - - name: amount - type: uint256 - - outputs: - - name: success - type: bool -``` - -#### `approve` - -Approves an `amount` of a token `id` that a `spender` is permitted to transfer on behalf of the caller. - -MUST set the `allowance` of the `spender` of the token `id` for the caller to the `amount`. - -MUST log the `Approval` event. - -MUST return True. - -```yaml -- name: approve - type: function - stateMutability: nonpayable - - inputs: - - name: spender - type: address - - name: id - type: uint256 - - name: amount - type: uint256 - - outputs: - - name: success - type: bool -``` - -#### `setOperator` - -Grants or revokes unlimited transfer permissions for a `spender` for any token `id` on behalf of the caller. - -MUST set the operator status to the `approved` value. - -MUST log the `OperatorSet` event. - -MUST return True. - -```yaml -- name: setOperator - type: function - stateMutability: nonpayable - - inputs: - - name: spender - type: address - - name: approved - type: bool - - outputs: - - name: success - type: bool -``` - -### Events - -#### `Transfer` - -The `caller` initiates a transfer of an `amount` of a token `id` from a `sender` to a `receiver`. - -MUST be logged when an `amount` of a token `id` is transferred from one account to another. - -SHOULD be logged with the `sender` address as the zero address when an `amount` of a token `id` is minted. - -SHOULD be logged with the `receiver` address as the zero address when an `amount` of a token `id` is burned. - -```yaml -- name: Transfer - type: event - - inputs: - - name: caller - indexed: false - type: address - - name: sender - indexed: true - type: address - - name: receiver - indexed: true - type: address - - name: id - indexed: true - type: uint256 - - name: amount - indexed: false - type: uint256 -``` - -#### `OperatorSet` - -The `owner` has set the `approved` status to a `spender`. - -MUST be logged when the operator status is set. - -MAY be logged when the operator status is set to the same status it was before the current call. - -```yaml -- name: OperatorSet - type: event - - inputs: - - name: owner - indexed: true - type: address - - name: spender - indexed: true - type: address - - name: approved - indexed: false - type: bool -``` - -#### `Approval` - -The `owner` has approved a `spender` to transfer an `amount` of a token `id` to be transferred on the owner's behalf. - -MUST be logged when the `allowance` is set by an `owner`. - -```yaml -- name: Approval - type: event - - inputs: - - name: owner - indexed: true - type: address - - name: spender - indexed: true - type: address - - name: id - indexed: true - type: uint256 - - name: amount - indexed: false - type: uint256 -``` - -## Rationale - -### 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. - -### Removal of Batching - -While batching operations is useful, its place should not be in the standard itself, but rather on a case-by-case basis. This allows for different tradeoffs to be made in terms of calldata layout, which may be especially useful for specific applications such as roll-ups that commit calldata to global storage. - -### 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. - -### Removal of "Safe" Naming - -The `safeTransfer` and `safeTransferFrom` naming conventions are misleading, especially in the context of the ERC-1155 and ERC-721 standards, as they require external calls to receiver accounts with code, passing the execution flow to an arbitrary contract, provided the receiver contract returns a specific value. The combination of removing mandatory callbacks and removing the word "safe" from all method names improves the safety of the control flow by default. - -### Interface ID - -The interface ID is `0xb2e69f8a`. - -### Metadata Extension - -#### Methods - -##### name - -The `name` of the contract. - -```yaml -- name: name - type: function - stateMutability: view - - inputs: [] - - outputs: - - name: name - type: string -``` - -##### symbol - -The ticker `symbol` of the contract. - -```yaml -- name: symbol - type: function - stateMutability: view - - inputs: [] - - outputs: - - name: symbol - type: string -``` - -##### decimals - -The `amount` of decimals for a token `id`. - -```yaml -- name: decimals - type: function - stateMutability: view - - inputs: - - name: id - type: uint256 - - outputs: - - name: amount - type: uint8 -``` - -### Metadata URI Extension - -#### Methods - -##### tokenURI - -The `URI` for a token `id`. - -MAY revert if the token `id` does not exist. - -MUST replace occurrences of `{id}` in the returned URI string by the client. - -```yaml -- name: tokenURI - type: function - stateMutability: view - - inputs: - - name: id - type: uint256 - - outputs: - - name: uri - type: string -``` - -#### Metadata Structure - -The metadata specification closely follows that of the ERC-721 JSON schema. - -MUST replace occurrences of `{id}` in the returned URI string by the client. - -```json -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the token" - }, - "description": { - "type": "string", - "description": "Describes the token" - }, - "image": { - "type": "string", - "description": "A URI pointing to an image resource." - } - } -} -``` - -## Backwards Compatibility - -This is not backwards compatible with ERC-1155 as some methods are removed. However, wrappers can be implemented for the ERC-20, ERC-721, and ERC-1155 standards. - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.19; - -/// @title ERC6909 Multi-Token Reference Implementation -/// @author jtriley.eth -contract ERC6909 { - /// @dev Thrown when owner balance for id is insufficient. - /// @param owner The address of the owner. - /// @param id The id of the token. - error InsufficientBalance(address owner, uint256 id); - - /// @dev Thrown when spender allowance for id is insufficient. - /// @param spender The address of the spender. - /// @param id The id of the token. - error InsufficientPermission(address spender, uint256 id); - - /// @notice The event emitted when a transfer occurs. - /// @param sender The address of the sender. - /// @param receiver The address of the receiver. - /// @param id The id of the token. - /// @param amount The amount of the token. - event Transfer(address caller, address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount); - - /// @notice The event emitted when an operator is set. - /// @param owner The address of the owner. - /// @param spender The address of the spender. - /// @param approved The approval status. - event OperatorSet(address indexed owner, address indexed spender, bool approved); - - /// @notice The event emitted when an approval occurs. - /// @param owner The address of the owner. - /// @param spender The address of the spender. - /// @param id The id of the token. - /// @param amount The amount of the token. - event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount); - - /// @notice The total supply of each id. - mapping(uint256 id => uint256 amount) public totalSupply; - - /// @notice Owner balance of an id. - mapping(address owner => mapping(uint256 id => uint256 amount)) public balanceOf; - - /// @notice Spender allowance of an id. - mapping(address owner => mapping(address spender => mapping(uint256 id => uint256 amount))) public allowance; - - /// @notice Checks if a spender is approved by an owner as an operator. - mapping(address owner => mapping(address spender => bool)) public isOperator; - - /// @notice Transfers an amount of an id from the caller to a receiver. - /// @param receiver The address of the receiver. - /// @param id The id of the token. - /// @param amount The amount of the token. - function transfer(address receiver, uint256 id, uint256 amount) public returns (bool) { - if (balanceOf[msg.sender][id] < amount) revert InsufficientBalance(msg.sender, id); - balanceOf[msg.sender][id] -= amount; - balanceOf[receiver][id] += amount; - emit Transfer(msg.sender, msg.sender, receiver, id, amount); - return true; - } - - /// @notice Transfers an amount of an id from a sender to a receiver. - /// @param sender The address of the sender. - /// @param receiver The address of the receiver. - /// @param id The id of the token. - /// @param amount The amount of the token. - function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public returns (bool) { - if (sender != msg.sender && !isOperator[sender][msg.sender]) { - uint256 senderAllowance = allowance[sender][msg.sender][id]; - if (senderAllowance < amount) revert InsufficientPermission(msg.sender, id); - if (senderAllowance != type(uint256).max) { - allowance[sender][msg.sender][id] = senderAllowance - amount; - } - } - if (balanceOf[sender][id] < amount) revert InsufficientBalance(sender, id); - balanceOf[sender][id] -= amount; - balanceOf[receiver][id] += amount; - emit Transfer(msg.sender, sender, receiver, id, amount); - return true; - } - - /// @notice Approves an amount of an id to a spender. - /// @param spender The address of the spender. - /// @param id The id of the token. - /// @param amount The amount of the token. - function approve(address spender, uint256 id, uint256 amount) public returns (bool) { - allowance[msg.sender][spender][id] = amount; - emit Approval(msg.sender, spender, id, amount); - return true; - } - - - /// @notice Sets or removes a spender as an operator for the caller. - /// @param spender The address of the spender. - /// @param approved The approval status. - function setOperator(address spender, bool approved) public returns (bool) { - isOperator[msg.sender][spender] = approved; - emit OperatorSet(msg.sender, spender, approved); - return true; - } - - /// @notice Checks if a contract implements an interface. - /// @param interfaceId The interface identifier, as specified in ERC-165. - /// @return supported True if the contract implements `interfaceId`. - function supportsInterface(bytes4 interfaceId) public pure returns (bool supported) { - return interfaceId == 0xb2e69f8a || interfaceId == 0x01ffc9a7; - } - - function _mint(address receiver, uint256 id, uint256 amount) internal { - // WARNING: important safety checks should precede calls to this method. - balanceOf[receiver][id] += amount; - totalSupply[id] += amount; - emit Transfer(msg.sender, address(0), receiver, id, amount); - } - - function _burn(address sender, uint256 id, uint256 amount) internal { - // WARNING: important safety checks should precede calls to this method. - balanceOf[sender][id] -= amount; - totalSupply[id] -= amount; - emit Transfer(msg.sender, sender, address(0), id, amount); - } -} -``` - -## Security Considerations - -### Approvals and Operators - -The specification includes two token transfer permission systems, the "allowance" and "operator" -models. There are two security considerations in regards to delegating permission to transfer. - -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. - -```solidity -contract ERC6909OperatorPrecedence { - // -- snip -- - - function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public { - // check if `isOperator` first - if (msg.sender != sender && !isOperator[sender][msg.sender]) { - require(allowance[sender][msg.sender][id] >= amount, "insufficient allowance"); - allowance[sender][msg.sender][id] -= amount; - } - - // -- snip -- - } -} - -contract ERC6909AllowancePrecedence { - // -- snip -- - - function transferFrom(address sender, address receiver, uint256 id, uint256 amount) public { - // check if allowance is sufficient first - if (msg.sender != sender && allowance[sender][msg.sender][id] < amount) { - require(isOperator[sender][msg.sender], "insufficient allowance"); - } - - // ERROR: when allowance is insufficient, this panics due to arithmetic underflow, regardless of - // whether the caller has operator permissions. - allowance[sender][msg.sender][id] -= amount; - - // -- snip - } -} -``` - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6909.md diff --git a/EIPS/eip-6944.md b/EIPS/eip-6944.md index ed934544f9e9ae..469612cf6b6b15 100644 --- a/EIPS/eip-6944.md +++ b/EIPS/eip-6944.md @@ -1,59 +1 @@ ---- -eip: 6944 -title: ERC-5219 Resolve Mode -description: Adds an ERC-4804 resolveMode to support ERC-5219 requests -author: Gavin John (@Pandapip1), Qi Zhou (@qizhou) -discussions-to: https://ethereum-magicians.org/t/erc-5219-resolve-mode/14088 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-27 -requires: 4804, 5219 ---- - -## Abstract - -This EIP adds a new [ERC-4804](./eip-4804.md) `resolveMode` to resolve [ERC-5219](./eip-5219.md) contract resource requests. - -## 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. - -Contracts wishing to use ERC-5219 as their ERC-4804 resolve mode must implement the following interface: - -```solidity -/// @dev IDecentralizedApp is the ERC-5219 interface -interface IERC5219Resolver is IDecentralizedApp { - // @notice The ERC-4804 resolve mode - // @dev This MUST return "5219" (0x3532313900000000000000000000000000000000000000000000000000000000) for ERC-5219 resolution (case-insensitive). The other options, as of writing this, are "auto" for automatic resolution, or "manual" for manual resolution. - function resolveMode() external pure returns (bytes32 mode); -} -``` - -## Rationale - -[ERC-165](./eip-165.md) was not used because interoperability can be checked by calling `resolveMode`. - -## Backwards Compatibility - -No backward compatibility issues found. - - -## Reference Implementation - -```solidity -abstract contract ERC5219Resolver is IDecentralizedApp { - function resolveMode() public pure returns (bytes32 mode) { - return "5219"; - } -} -``` - - -## Security Considerations - -The security considerations of [ERC-4804](./eip-4804.md#security-considerations) and [ERC-5219](./eip-5219.md#security-considerations) apply. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6944.md diff --git a/EIPS/eip-6956.md b/EIPS/eip-6956.md index 58c9ec4726516b..dfa8ce546ca365 100644 --- a/EIPS/eip-6956.md +++ b/EIPS/eip-6956.md @@ -1,652 +1 @@ ---- -eip: 6956 -title: Asset-bound Non-Fungible Tokens -description: Asset-bound NFTs anchor a token 1-1 to an asset and operations are authorized through oracle-attestation of control over the asset -author: Thomas Bergmueller (@tbergmueller), Lukas Meyer (@ibex-technology) -discussions-to: https://ethereum-magicians.org/t/erc-6956-asset-bound-non-fungible-tokens/14056 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-29 -requires: 165, 721 ---- - -## Abstract - -This standard allows integrating physical and digital ASSETS without signing capabilities into dApps/web3 by extending [ERC-721](eip-721.md). - -An ASSET, for example a physical object, is marked with a uniquely identifiable ANCHOR. The ANCHOR is bound in a secure and inseparable manner 1:1 to an NFT on-chain - over the complete life cylce of the ASSET. - -Through an ATTESTATION, an ORACLE testifies that a particular ASSET associated with an ANCHOR has been CONTROLLED when defining the `to`-address for certain operations (mint, transfer, burn, approve, ...). The ORACLE signs the ATTESTATION off-chain. The operations are authorized through verifying on-chain that ATTESTATION has been signed by a trusted ORACLE. Note that authorization is solely provided through the ATTESTATION, or in other words, through PROOF-OF-CONTROL over the ASSET. The controller of the ASSET is guaranteed to be the controller of the Asset-Bound NFT. - -The proposed ATTESTATION-authorized operations such as `transferAnchor(attestation)` are permissionless, meaning neither the current owner (`from`-address) nor the receiver (`to`-address) need to sign. - -Figure 1 shows the data flow of an ASSET-BOUND NFT transfer. The simplified system is utilizing a smartphone as user-device to interact with a physical ASSET and specify the `to`-address. - -![Figure 1: Sample system](../assets/eip-6956/img/erc6956_concept.svg) - -## Motivation - -The well-known [ERC-721](eip-721.md) establishes that NFTs may represent "ownership over physical properties [...] as well as digital collectables and even more abstract things such as responsibilities" - in a broader sense, we will refer to all those things as ASSETS, which typically have value to people. - -### The Problem - -ERC-721 outlines that "NFTs can represent ownership over digital or physical assets". ERC-721 excels in this task when used to represent ownership over digital, on-chain assets, that is when the asset is "holding a token of a specific contract" or the asset is an NFT's metadata. Today, people commonly treat an NFT's metadata (images, traits, ...) as asset-class, with their rarity often directly defining the value of an individual NFT. - -However, we see integrity issues not solveable with ERC-721, primarily when NFTS are used to represent off-chain ASSETS ("ownership over physical products", "digital collectables", "in-game assets", "responsibilities", ...). Over an ASSET's lifecycle, the ASSET's ownership and possession state changes multiple, sometimes thousands, of times. Each of those state changes may result in shifting obligations and privileges for the involved parties. Therefore tokenization of an ASSET *without* enforcably anchoring the ASSET's associated obligation and properties to the token is not complete. Nowadays, off-chain ASSETs are often "anchored" through adding an ASSET-identifier to a NFT's metadata. - -**NFT-ASSET integrity:** Contrary to a popular belief among NFT-investors, metadata is data that is, more often than not, mutable and off-chain. Therefore the link between an ASSET through an asset-identifier stored in mutable metadata, which is only linked to the NFT through tokenURI, can be considered weak at best. - -Approaches to ensure integrity between metadata (=reference to ASSET) and a token exist. This is most commonly achieved by storing metadata-hashes onchain. Additional problems arise through hashing; For many applications, metadata (besides the asset-identifier) should be update-able. Therefore making metadata immutable through storing a hash is problematic. Further the offchain metadata-resource specified via tokenURI must be made available until eternity, which has historically been subject to failure (IPFS bucket disappears, central tokenURI-provider has downtimes, ...) - -**Off-chain-on-chain-integrity:** There are approaches where off-chain ASSET ownership is enforced or conditioned through having ownership over the on-chain representation. A common approach is to burn tokens in order to get the (physical) ASSET, as the integrity cannot be maintained. However, there are no approaches known, where on-chain ownership is enforced through having off-chain ownership of the ASSET. Especially when the current owner of an NFT is incooperative or incapacitated, integrity typically fail due to lack of signing-power from the current NFT owner. - -Metadata is off-chain. The majority of implementations completely neglect that metadata is mutable. More serious implementations strive to preserve integrity by for example hashing metadata and storing the hash mapped to the tokenId on-chain. However, this approach does not allow for use-case, where metadata besides the asset-identifier, for example traits, "hours played", ... shall be mutable or evolvable. - -### ASSET-BOUND NON-FUNGIBLE TOKENS - -In this standard we propose to - -1. Elevate the concept of representing physical or digital off-chain ASSETS by on-chain ANCHORING the ASSET inseperably into an NFT. -1. Being off-chain in control over the ASSET must mean being on-chain in control over the anchored NFT. -1. (Related) A change in off-chain ownership over the ASSET inevitably should be reflected by a change in on-chain ownership over the anchored NFT, even if the current owner is uncooperative or incapacitated. - -As 2. and 3. indicate, the control/ownership/possession of the ASSET should be the single source of truth, *not* the possession of an NFT. Hence, we propose an ASSET-BOUND NFT, where off-chain CONTROL over the ASSET enforces on-chain CONTROL over the anchored NFT. -Also the proposed ASSET-BOUND NFTs allow to anchor digital metadata inseperably to the ASSET. When the ASSET is a physical asset, this allows to design "phygitals" in their purest form, namely creating a "phygital" asset with a physical and digital component that are inseparable. Note that metadata itself can still change, for instance for "Evolvable NFT". - -We propose to complement the existing transfer control mechanisms of a token according to ERC-721, `Approval` according to [ERC-721](eip-721.md) and `Permit` according to [ERC-4494](eip-4494.md), by another mechanism; ATTESTATION. An ATTESTATION is signed off-chain by the ORACLE and must only be issued when the ORACLE verified that whoever specifies the `to` address or beneficiary address has simultaneously been in control over the ASSET. The `to` address of an attestation may be used for Transfers as well as for approvals and other authorizations. - -Transactions authorized via ATTESTATION shall not require signature or approval from neither the `from` (donor, owner, sender) nor `to` (beneficiary, receiver) account, namely making transfers permissionless. Ideally, transaction are signed independent from the ORACLE as well, allowing different scenarios in terms of gas-fees. - -Lastly we want to mention two major side-benefits of using the proposed standard, which drastically lowers hurdles in onboarding web2 users and increase their security; - -- New users, e.g `0xaa...aa` (Fig.1), can use gasless wallets, hence participate in Web3/dApps/DeFi and mint+transfer tokens without ever owning crypto currency. Gas-fees may be paid through a third-party account `0x..gasPayer` (Fig.1). The gas is typically covered by the ASSET issuer, who signs `transferAnchor()` transactions -- Users cannot get scammed. Common attacks (for example wallet-drainer scams) are no longer possible or easily reverted, since only the anchored NFT can be stolen, not the ASSET itself. Also mishaps like transferring the NFT to the wrong account, losing access to an account etc can be mitigated by executing another `transferAnchor()` transaction based on proofing control over the ASSET, namely the physical object. - -### Related work - -We primarily aim to onboard physical or digital ASSETS into dApps, which do not signing-capabilities of their own (contrary to [ERC-5791](eip-5791.md) approach using crypto-chip based solutions). Note that we do not see any restrictions preventing to use ERC-5791 in combination with this standard, as the address of the crypto-chip qualifies as an ANCHOR. - - - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Definitions (alphabetical) - -- **ANCHOR** uniquely identifies the off-chain ASSET, whether it is physical or digital. -- **ANCHOR-TECHNOLOGY** MUST ensure that - - the ANCHOR is inseparable from the ASSET (physically or otherwise) - - an ORACLE can establish PROOF-OF-CONTROL over the ASSET beyond reasonable doubt - - For physical ASSETS, additional [Security considerations for Physical Assets](#security-considerations-for-physical-assets) MUST be taken into account - -- **ASSET** refers to the "thing", being it physical or digital, which is represented through NFTs according to the proposed standard. Typically, an ASSET does not have signing capabilities. - -- **ATTESTATION** is the confirmation that PROOF OF CONTROL was established when specifying the `to` (receiver, beneficiary) address. - -- **PROOF-OF-CONTROL** over the ASSET means owning or otherwise controlling an ASSET. How Proof of Control is established depends on the ASSET and may be implemented using technical, legal or other means. For physical ASSETS, CONTROL is typically verified by proofing physical proximity between a physical ASSET and an input device (for example a smartphone) used to specify the `to` address. - -- An **ORACLE** has signing capabilities. MUST be able to sign ATTESTATIONS off-chain in a way such that signatures can be verified on-chain. - - -### Base Interface - -Every contract compliant to this standard MUST implement the [the proposed standard interface](../assets/eip-6956/contracts/IERC6956.sol), [ERC-721](eip-721.md) and [ERC-165](eip-165.md) interfaces and is subject to [Caveats](#caveats-for-base-interface) below: - -```solidity -// SPDX-License-Identifier: MIT OR CC0-1.0 -pragma solidity ^0.8.18; - -/** - * @title IERC6956 Asset-Bound Non-Fungible Tokens - * @notice Asset-bound Non-Fungible Tokens anchor a token 1:1 to a (physical or digital) asset and token transfers are authorized through attestation of control over the asset - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0xa9cf7635 - */ -interface IERC6956 { - - /** @dev Authorization, typically mapped to authorizationMaps, where each bit indicates whether a particular ERC6956Role is authorized - * Typically used in constructor (hardcoded or params) to set burnAuthorization and approveAuthorization - * Also used in optional updateBurnAuthorization, updateApproveAuthorization, I - */ - enum Authorization { - NONE, // = 0, // None of the above - OWNER, // = (1<0) of the anchored token - */ - event AnchorApproval(address indexed owner, address approved, bytes32 indexed anchor, uint256 tokenId); - - /** - * @notice This emits when the ownership of any anchored NFT changes by any mechanism - * @dev This emits together with tokenId-based ERC-721.Transfer and provides an anchor-perspective on transfers - * @param from The previous owner, address(0) indicate there was none. - * @param to The new owner, address(0) indicates the token is burned - * @param anchor The anchor which is bound to tokenId - * @param tokenId ID (>0) of the anchored token - */ - event AnchorTransfer(address indexed from, address indexed to, bytes32 indexed anchor, uint256 tokenId); - /** - * @notice This emits when an attestation has been used indicating no second attestation with the same attestationHash will be accepted - * @param to The to address specified in the attestation - * @param anchor The anchor specificed in the attestation - * @param attestationHash The hash of the attestation, see ERC-6956 for details - * @param totalUsedAttestationsForAnchor The total number of attestations already used for the particular anchor - */ - event AttestationUse(address indexed to, bytes32 indexed anchor, bytes32 indexed attestationHash, uint256 totalUsedAttestationsForAnchor); - - /** - * @notice This emits when the trust-status of an oracle changes. - * @dev Trusted oracles must explicitly be specified. - * If the last event for a particular oracle-address indicates it's trusted, attestations from this oracle are valid. - * @param oracle Address of the oracle signing attestations - * @param trusted indicating whether this address is trusted (true). Use (false) to no longer trust from an oracle. - */ - event OracleUpdate(address indexed oracle, bool indexed trusted); - - /** - * @notice Returns the 1:1 mapped anchor for a tokenId - * @param tokenId ID (>0) of the anchored token - * @return anchor The anchor bound to tokenId, 0x0 if tokenId does not represent an anchor - */ - function anchorByToken(uint256 tokenId) external view returns (bytes32 anchor); - /** - * @notice Returns the ID of the 1:1 mapped token of an anchor. - * @param anchor The anchor (>0x0) - * @return tokenId ID of the anchored token, 0 if no anchored token exists - */ - function tokenByAnchor(bytes32 anchor) external view returns (uint256 tokenId); - - /** - * @notice The number of attestations already used to modify the state of an anchor or its bound tokens - * @param anchor The anchor(>0) - * @return attestationUses The number of attestation uses for a particular anchor, 0 if anchor is invalid. - */ - function attestationsUsedByAnchor(bytes32 anchor) view external returns (uint256 attestationUses); - /** - * @notice Decodes and returns to-address, anchor and the attestation hash, if the attestation is valid - * @dev MUST throw when - * - Attestation has already been used (an AttestationUse-Event with matching attestationHash was emitted) - * - Attestation is not signed by trusted oracle (the last OracleUpdate-Event for the signer-address does not indicate trust) - * - Attestation is not valid yet or expired - * - [if IERC6956AttestationLimited is implemented] attestationUsagesLeft(attestation.anchor) <= 0 - * - [if IERC6956ValidAnchors is implemented] validAnchors(data) does not return true. - * @param attestation The attestation subject to the format specified in ERC-6956 - * @param data Optional additional data, may contain proof as the first abi-encoded argument when IERC6956ValidAnchors is implemented - * @return to Address where the ownership of an anchored token or approval shall be changed to - * @return anchor The anchor (>0) - * @return attestationHash The attestation hash computed on-chain as `keccak256(attestation)` - */ - function decodeAttestationIfValid(bytes memory attestation, bytes memory data) external view returns (address to, bytes32 anchor, bytes32 attestationHash); - - /** - * @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to burn - */ - function burnAuthorization() external view returns(Authorization burnAuth); - - /** - * @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to approve - */ - function approveAuthorization() external view returns(Authorization approveAuth); - - /** - * @notice Corresponds to transferAnchor(bytes,bytes) without additional data - * @param attestation Attestation, refer ERC-6956 for details - */ - function transferAnchor(bytes memory attestation) external; - - /** - * @notice Changes the ownership of an NFT mapped to attestation.anchor to attestation.to address. - * @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation. - * - Uses decodeAttestationIfValid() - * - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited. - * - Matches the behavior of ERC-721.safeTransferFrom(ownerOf[tokenByAnchor(attestation.anchor)], attestation.to, tokenByAnchor(attestation.anchor), ..) and mint an NFT if `tokenByAnchor(anchor)==0`. - * - Throws when attestation.to == ownerOf(tokenByAnchor(attestation.anchor)) - * - Emits AnchorTransfer - * - * @param attestation Attestation, refer EIP-6956 for details - * @param data Additional data, may be used for additional transfer-conditions, may be sent partly or in full in a call to safeTransferFrom - * - */ - function transferAnchor(bytes memory attestation, bytes memory data) external; - - /** - * @notice Corresponds to approveAnchor(bytes,bytes) without additional data - * @param attestation Attestation, refer ERC-6956 for details - */ - function approveAnchor(bytes memory attestation) external; - - /** - * @notice Approves attestation.to the token bound to attestation.anchor. . - * @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation. - * - Uses decodeAttestationIfValid() - * - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited. - * - Matches the behavior of ERC-721.approve(attestation.to, tokenByAnchor(attestation.anchor)). - * - Throws when ASSET is not authorized to approve. - * - * @param attestation Attestation, refer EIP-6956 for details - */ - function approveAnchor(bytes memory attestation, bytes memory data) external; - - /** - * @notice Corresponds to burnAnchor(bytes,bytes) without additional data - * @param attestation Attestation, refer ERC-6956 for details - */ - function burnAnchor(bytes memory attestation) external; - - /** - * @notice Burns the token mapped to attestation.anchor. Uses ERC-721._burn. - * @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation. - * - Uses decodeAttestationIfValid() - * - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited. - * - Throws when ASSET is not authorized to burn - * - * @param attestation Attestation, refer EIP-6956 for details - */ - function burnAnchor(bytes memory attestation, bytes memory data) external; -} -``` - -#### Caveats for Base Interface - -- MUST implement ERC-721 and ERC-165 -- MUST have bidirectional mapping `tokenByAnchor(anchor)` and `anchorByToken(tokenId)`. This implies that a maximum of one token per ANCHOR exists. -- MUST have a mechnism to determine whether an ANCHOR is valid for the contract. RECOMMENDED to implement the proposed [ValidAnchors-Interface](#validanchors-interface) -- MUST implement `decodeAttestationIfValid(attestation, data)` to validate and decode ATTESTATIONS as specified in the [ORACLE-Section](#oracle) - - MUST return `attestation.to`, `attestation.anchor`, `attestation.attestationHash`. - - MUST not modify state, as this function can be used to check an ATTESTATION's validity without redeeming it. - - MUST throw when - - ATTESTATION is not signed from a trusted ORACLE. - - ATTESTATION has expired or is not valid yet - - ATTESTATION has not been redeemed. "Redeemed" being defined in at least one state-changing operation has been authorized through a particular ATTESTATION. - - If [AttestationLimited-Interface](#attestationlimited-interface) implemented: When `attestationUsagesLeft(attestation.to) <= 0` - - If [ValidAnchors-Interface](#validanchors-interface) implemented: When `validAnchor() != true`. - - If [ValidAnchors-Interface](#validanchors-interface) implemented: MUST call `validAnchor(attestation.to, abi.decode('bytes32[]',data))`, meaning the first abi-encoded value in the `data` parameter corresponds to `proof`. -- MUST have a ANCHOR-RELEASED mechanism, indicating whether the anchored NFT is released/transferable. - - Any ANCHOR MUST NOT be released by default. -- MUST extend any ERC-721 token transfer mechanism by: - - MUST throw when `ANCHOR` is not released. - - MUST throw when batchSize > 1, namely no batch transfers are supported with this contract. - - MUST emit `AnchorTransfer(from, to, anchorByToken[tokenId], tokenId)` - -- MUST implement `attestationsUsedByAnchor(anchor)`, returning how many attestations have already been used for a specific anchor. - -- MUST implement the state-changing `transferAnchor(..)`, `burnAnchor(..)`, `approveAnchor(..)` and OPTIONAL MAY implement additional state-changing operations which - - MUST use the `decodeAttestationIfValid()` to determine `to`, `anchor` and `attestationHash` - - MUST redeem each ATTESTATION in the same transaction as any authorized state-changing operation. RECOMMENDED by storing each used `attestationHash` - - MUST increment `attestationsUsedByAnchor[anchor]` - - MUST emit `AttestationUsed` - - `transferAnchor(attestation)` MUST behave and emit events like `ERC-721.safeTransferFrom(ownerOf[tokenByAnchor(attestation.anchor)], attestation.to, tokenByAnchor(attestation.anchor), ..)` and mint an NFT if `tokenByAnchor(anchor)==0`. - -- RECOMMENDED to implement `tokenURI(tokenId)` to return an anchorBased-URI, namely `baseURI/anchor`. This anchoring metadata to ASSET. Before an anchor is not used for the first time, the ANCHOR's mapping to tokenId is unknown. Hence, using the anchor in instead of the tokenId is preferred. - - -### ORACLE - -- MUST provide an ATTESTATION. Below we define the format how an ORACLE testifies that the `to` address of a transfer has been specified under the pre-condition of PROOF-OF-CONTROL associated with the particular ANCHOR being transferred to `to`. -- The ATTESTATION MUST abi-encode the following: - - `to`, MUST be address, specifying the beneficiary, for example the to-address, approved account etc. - - ANCHOR, aka the ASSET identifier, MUST have a 1:1 relation to the ASSET - - `attestationTime`, UTC seconds, time when attestation was signed by ORACLE, - - `validStartTime` UTC seconds, start time of the ATTESTATION's validity timespan - - `validEndTime`, UTC seconds, end time of the ATTESTATION's validity timespan - - `signature`, ETH-signature (65 bytes). Output of an ORACLE signing the `attestationHash = keccak256([to, anchor, attestationTime, validStartTime, validEndTime])`. -- How PROOF-OF-CONTROL is establish in detail through an ANCHOR-TECHNOLOGY is not subject to this standard. Some ORACLE requirements and ANCHOR-TECHNOLOGY requirements when using PHYSICAL ASSETS are outlined in [Security considerations for Physical Assets](#security-considerations-for-physical-assets). - -Minimal Typescript sample to generate an ATTESTATION using ethers library: - -```typescript -export async function minimalAttestationSample() { - // #################################### ACCOUNTS - // Alice shall get the NFT, oracle signs the attestation off-chain - // Oracle needs to be a trusted Oracle of the smart-contract that shall accept the generated attestation - const [alice, oracle] = await ethers.getSigners(); - - // #################################### CREATE AN ATTESTATION - const to = alice.address; - const anchor = '0x4cc52563699fb1e3333b8aab3ecf016f8fd084e6fc48edf8603d83d4c5b97536' - - const attestationTime = Math.floor(Date.now() / 1000.0); // Now in seconds UTC - const validStartTime = 0; - const validEndTime = attestationTime + 15 * 60; // 15 minutes valid from attestation - - const messageHash = ethers.utils.solidityKeccak256( - ["address", "bytes32", "uint256", 'uint256', "uint256"], - [to, anchor, attestationTime, validStartTime, validEndTime] - ); - const sig = await signer.signMessage(ethers.utils.arrayify(messageHash)); - - return ethers.utils.defaultAbiCoder.encode( - ['address', 'bytes32', 'uint256', 'uint256', 'uint256', 'bytes'], - [to, anchor, attestationTime, validStartTime, validEndTime, sig] - ); -} -``` - -### AttestationLimited-Interface - -Every contract compliant to this standard MAY implement the [proposed AttestationLimited interface](../assets/eip-6956/contracts/IERC6956AttestationLimited.sol) and is subject to [Caveats](#caveats-for-attestationlimited-interface) below: - -```solidity -// SPDX-License-Identifier: MIT OR CC0-1.0 -pragma solidity ^0.8.18; -import "./IERC6956.sol"; - -/** - * @title Attestation-limited Asset-Bound NFT - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0x75a2e933 - */ -interface IERC6956AttestationLimited is IERC6956 { - enum AttestationLimitPolicy { - IMMUTABLE, - INCREASE_ONLY, - DECREASE_ONLY, - FLEXIBLE - } - - /// @notice Returns the attestation limit for a particular anchor - /// @dev MUST return the global attestation limit per default - /// and override the global attestation limit in case an anchor-based limit is set - function attestationLimit(bytes32 anchor) external view returns (uint256 limit); - - /// @notice Returns number of attestations left for a particular anchor - /// @dev Is computed by comparing the attestationsUsedByAnchor(anchor) and the current attestation limit - /// (current limited emitted via GlobalAttestationLimitUpdate or AttestationLimt events) - function attestationUsagesLeft(bytes32 anchor) external view returns (uint256 nrTransfersLeft); - - /// @notice Indicates the policy, in which direction attestation limits can be updated (globally or per anchor) - function attestationLimitPolicy() external view returns (AttestationLimitPolicy policy); - - /// @notice This emits when the global attestation limt is updated - event GlobalAttestationLimitUpdate(uint256 indexed transferLimit, address updatedBy); - - /// @notice This emits when an anchor-specific attestation limit is updated - event AttestationLimitUpdate(bytes32 indexed anchor, uint256 indexed tokenId, uint256 indexed transferLimit, address updatedBy); - - /// @dev This emits in the transaction, where attestationUsagesLeft becomes 0 - event AttestationLimitReached(bytes32 indexed anchor, uint256 indexed tokenId, uint256 indexed transferLimit); -} -``` - -#### Caveats for AttestationLimited-Interface - -- MUST extend the proposed standard interface -- MUST define one of the above listed AttestationLimit update policies and expose it via `attestationLimitPolicy()` - - MUST support different update modes, namely FIXED, INCREASE_ONLY, DECREASE_ONLY, FLEXIBLE (= INCREASABLE and DECREASABLE) - - RECOMMENDED to have a global transfer limit, which can be overwritten on a token-basis (when `attestationLimitPolicy() != FIXED`) -- MUST implement `attestationLimit(anchor)`, specifying how often an ANCHOR can be transferred in total. Changes in the return value MUST reflect the AttestationLimit-Policy. -- MUST implement `attestationUsagesLeft(anchor)`, returning the number of usages left (namely `attestationLimit(anchor)-attestationsUsedByAnchor[anchor]`) for a particular anchor - - -### Floatable-Interface - -Every contract compliant to this extension MAY implement the proposed [Floatable interface](../assets/eip-6956/contracts/IERC6956Floatable.sol) and is subject to [Caveats](#caveats-for-floatable-interface) below: - -```solidity -// SPDX-License-Identifier: MIT OR CC0-1.0 -pragma solidity ^0.8.18; -import "./IERC6956.sol"; - -/** - * @title Floatable Asset-Bound NFT - * @notice A floatable Asset-Bound NFT can (temporarily) be transferred without attestation - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0xf82773f7 - */ -interface IERC6956Floatable is IERC6956 { - enum FloatState { - Default, // 0, inherits from floatAll - Floating, // 1 - Anchored // 2 - } - - /// @notice Indicates that an anchor-specific floating state changed - event FloatingStateChange(bytes32 indexed anchor, uint256 indexed tokenId, FloatState isFloating, address operator); - /// @notice Emits when FloatingAuthorization is changed. - event FloatingAuthorizationChange(Authorization startAuthorization, Authorization stopAuthorization, address maintainer); - /// @notice Emits, when the default floating state is changed - event FloatingAllStateChange(bool areFloating, address operator); - - /// @notice Indicates whether an anchored token is floating, namely can be transferred without attestation - function floating(bytes32 anchor) external view returns (bool); - - /// @notice Indicates whether any of OWNER, ISSUER, (ASSET) is allowed to start floating - function floatStartAuthorization() external view returns (Authorization canStartFloating); - - /// @notice Indicates whether any of OWNER, ISSUER, (ASSET) is allowed to stop floating - function floatStopAuthorization() external view returns (Authorization canStartFloating); - - /** - * @notice Allows to override or reset to floatAll-behavior per anchor - * @dev Must throw when newState == Floating and floatStartAuthorization does not authorize msg.sender - * @dev Must throw when newState == Anchored and floatStopAuthorization does not authorize msg.sender - * @param anchor The anchor, whose anchored token shall override default behavior - * @param newState Override-State. If set to Default, the anchor will behave like floatAll - */ - function float(bytes32 anchor, FloatState newState) external; -} -``` - - -#### Caveats for Floatable-Interface - -If `floating(anchor)` returns true, the token identified by `tokenByAnchor(anchor)` MUST be transferable without attestation, typically authorized via `ERC721.isApprovedOrOwner(msg.sender, tokenId)` - -### ValidAnchors-Interface - -Every contract compliant to this extension MAY implement the proposed [ValidAnchors interface](../assets/eip-6956/contracts/IERC6956ValidAnchors.sol) and is subject to [Caveats](#caveats-for-validanchors-interface) below: - -```solidity -// SPDX-License-Identifier: MIT OR CC0-1.0 -pragma solidity ^0.8.18; -import "./IERC6956.sol"; - -/** - * @title Anchor-validating Asset-Bound NFT - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0x051c9bd8 - */ -interface IERC6956ValidAnchors is IERC6956 { - /** - * @notice Emits when the valid anchors for the contract are updated. - * @param validAnchorHash Hash representing all valid anchors. Typically Root of Merkle-Tree - * @param maintainer msg.sender when updating the hash - */ - event ValidAnchorsUpdate(bytes32 indexed validAnchorHash, address indexed maintainer); - - /** - * @notice Indicates whether an anchor is valid in the present contract - * @dev Typically implemented via MerkleTrees, where proof is used to verify anchor is part of the MerkleTree - * MUST return false when no ValidAnchorsUpdate-event has been emitted yet - * @param anchor The anchor in question - * @param proof Proof that the anchor is valid, typically MerkleProof - * @return isValid True, when anchor and proof can be verified against validAnchorHash (emitted via ValidAnchorsUpdate-event) - */ - function anchorValid(bytes32 anchor, bytes32[] memory proof) external view returns (bool isValid); -} -``` - -#### Caveats for ValidAnchors-Interface - -- MUST implement `validAnchor(anchor, proof)` which returns true when anchor is valid, namely MerkleProof is correct, false otherwise. - - -## Rationale - -**Why do you use an anchor<>tokenId mapping and not simply use tokenIds directly?** -Especially for collectable use-cases, special or sequential tokenIds (for example low numbers), have value. Holders may be proud to have claimed tokenId=1 respectively the off-chain ASSET with tokenId=1 may increase in value, because it was the first ever claimed. Or an Issuer may want to address the first 100 owners who claimed their ASSET-BOUND NFT. While these use-cases technically can certainly be covered by observing the blockchain state-changes, we consider reflecting the order in the tokenIds to be the user-friendly way. Please refer [Security considerations](#security-considerations) on why sequential anchors shall be avoided. - -**Why is tokenId=0 and anchor=0x0 invalid?** -For gas efficiency. This allows to omit checks and state-variables for the existence of a token or anchor, since mappings of a non-existent key return 0 and cannot be easily distinguished from anchor=0 or tokenId=0. - -**ASSETS are often batch-produced with the goal of identical properties, for example a batch of automotive spare parts. Why should do you extend ERC-721 and not Multi-Token standards?** -Even if a (physical) ASSET is mass produced with fungible characteristics, each ASSET has an individual property/ownership graph and thus shall be represented in a non-fungible way. Hence this EIP follows the design decision that ASSET (represented via a unique asset identifier called ANCHOR) and token are always mapped 1-1 and not 1-N, so that a token represents the individual property graph of the ASSET. - -**Why is there a burnAnchor() and approveAnchor()?** -Due to the permissionless nature ASSET-BOUND NFTs can even be transferred to or from any address. This includes arbitrary and randomly generated accounts (where the private key is unknown) and smart-contracts which would traditionally not support ERC-721 NFTs. Following that owning the ASSET must be equivalent to owning the NFT, this means that we also need to support ERC-721 operations like approval and burning in such instances through authorizing the operations with an attestation. - -**Implementation alternatives considered** Soulbound burn+mint combination, for example through Consensual Soulbound Tokens ([ERC-5484](eip-5484.md)). Disregarded because appearance is highly dubious, when the same asset is represented through multiple tokens over time. An predecessor of this EIP has used this approach and can be found deployed to Mumbai Testnet under address `0xd04c443913f9ddcfea72c38fed2d128a3ecd719e`. - -**When should I implement AttestationLimited-Interface** -Naturally, when your use-case requires each ASSET being transferable only a limited number of times. But also for security reasons, see [Security Considerations](#security-considerations) - -**Why is there the `IERC6956Floatable.FloatState` enum?** In order to allow gas-efficient implementation of floatAll(), which can be overruled by anchor-based floatability in all combinations. (See rationale for tokenId=0 above). - -**Why is there no `floating(tokenId)` function?** -This would behave identically to an `isTransferable(tokenId,...)` mechanism proposed in many other EIPs (refer e.g. [ERC-6454](eip-6454.md)). Further, the proposed `floating(anchorByToken(tokenId))` can be used. - -**Why are there different FloatingAuthorizations for start and stop?** -Depending on the use-case, different roles should be able to start or stop floating. Note that for many applications the ISSUER may want to have control over the floatability of the collection. - - -### Example Use Cases and recommended combination of interfaces - -Possession based use cases are covered by the standard interface `IERC6956`: The holder of ASSET is in possession of ASSET. Possession is an important social and economical tool: In many sports games possession of ASSET, commonly referred to as "the ball", is of essence. Possession can come with certain obligations and privileges. Ownership over an ASSET can come with rights and benefits as well as being burdened with liens and obligations. For example, an owned ASSET can be used for collateral, can be rented or can even yield a return. Example use-cases are - -- **Possession based token gating:** Club guest in possession of limited T-Shirt (ASSET) gets a token which allows him to open the door to the VIP lounge. - -- **Possession based digital twin:** A gamer is in possession of a pair of physical sneakers (ASSET), and gets a digital twin (NFT) to wear them in metaverse. - -- **Scarce possession based digital twin:** The producer of the sneakers (ASSET) decided that the product includes a limit of 5 digital twins (NFTs), to create scarcity. - -- **Lendable digital twin:** The gamer can lend his sneaker-tokens (NFT) to a friend in the metaverse, so that the friend can run faster. - -- **Securing ownership from theft:** If ASSET is owned off-chain, the owner wants to secure the anchored NFT, namely not allow transfers to prevent theft or recover the NFT easily through the ASSET. - -- **Selling a house with a mortgage:** The owner holds NFT as proof of ownership. The DeFi-Bank finances the house and puts a lock on the transfer of NFT. Allow Transfers of the NFT require the mortgage to be paid off. Selling the ASSET (house) off-chain will be impossible, as it's no longer possible to finance the house. - -- **Selling a house with a lease:** A lease contract puts a lien on an ASSET's anchored NFT. The old owner removes the lock, the new owner buys and refinances the house. Transfer of NFT will also transfer the obligations and benefits of the lien to the new owner. As a lien-interface, the proposed EIP can for example be extended with [ERC-5604](eip-5604.md) - -- **Buying a brand new car with downpayment:** A buyer configures a car and provides a downpayment, for a car that will have an ANCHOR. As long as the car is not produced, the NFT can float and be traded on NFT market places. The owner of the NFT at time of delivery of the ASSET has the the permission to pick up the car and the obligation to pay full price. - -- **Buying a barrel of oil by forward transaction:** A buyer buys an oil option on a forward contract for one barrel of oil (ASSET). On maturity date the buyer has the obligation to pick up the oil. - -The use case matrix below shows which extensions and settings must (additionally to `IERC6956`!) be implemented for the example use-cases together with relevant configurations. - -Note that for `Lockable` listed in the table below, the proposed EIP can be extended with any Lock- or Lien-Mechanism known to extend for ERC-721. Suitable extensions to achieve `Lockable` are for example [ERC-5058](eip-5058.md) or [ERC-5753](eip-5753.md). We recommend to verify whether a token is locked in the `_beforeTokenTransfer()`-hook, as this is called from `safeTransferFrom()` as well as `transferAnchor()`, hence suitable to block "standard" ERC-721 transfers as well as the proposed attestation-based transfers. - -| Use Case | approveAuthorization | burnAuthorization | `IERC6956Floatable` | `IERC6956AttestationLimited` | Lockable | -|---------------|---|---|---|---|---| -| **Managing Possession** | -| Token gating | ASSET | ANY | incompatible | - | - | -| Digital twin | ASSET | ANY | incompatible | - | - | -| Scarce digital twin | ASSET | ANY | incompatible | required | - | -| Lendable digital twin | OWNER_AND_ASSET | ASSET | required | - | - | -| **Managing Ownership** | -| Securing ownership from theft | OWNER or OWNER_AND_ASSET | ANY | optional | - | required | -| Selling an house with a mortgage | ASSET or OWNER_AND_ASSET | ANY | optional | optional | required | -| Selling a house with a lease | ASSET or OWNER_AND_ASSET | ANY | optional | optional | required | -| Buying a brand new car with downpayment | ASSET or OWNER_AND_ASSET | ANY | optional | optional | required | -| Buying a barrel of oil by forward transaction | ASSET or OWNER_AND_ASSET | ANY | optional | optional | required | - -Legend: - -- required ... we don't see a way how to implement the use-case without it -- incompatible ... this MUSTN'T be implemented, as it is a security risk for the use-case -- optional ... this MAY optionally be implemented - -## Backwards Compatibility - - - -No backward compatibility issues found. - -This EIP is fully compatible with ERC-721 and (when extended with the `IERC6956Floatable`-interface) corresponds to the well-known ERC-721 behavior with an additional authorization-mechanism via attestations. Therefore we recommend - especially for physical assets - to use the present EIP instead of ERC-721 and amend it with extensions designed for ERC-721. - -However, it is RECOMMENDED to extend implementations of the proposed standard with an interface indicating transferability of NFTs for market places. Examples include [ERC-6454](eip-6454.md) and [ERC-5484](eip-5484.md). - -Many ERC-721 extensions suggest to add additional throw-conditions to transfer methods. This standard is fully compatible, as - -- The often-used ERC-721 `_beforeTokenTransfer()` hook must be called for all transfers including attestation-authorized transfers. -- A `_beforeAnchorUse()` hook is suggested in the reference implementation, which only is called when using attestation as authorization. - -## Test Cases - -Test cases are available: - -- For only implementing [the proposed standard interface](../assets/eip-6956/contracts/IERC6956.sol) can be found [here](../assets/eip-6956/test/ERC6956.ts) -- For implementing [the proposed standard interface](../assets/eip-6956/contracts/IERC6956.sol), [the Floatable extension](../assets/eip-6956/contracts/IERC6956Floatable.sol), [the ValidAnchors extension](../assets/eip-6956/contracts/IERC6956ValidAnchors.sol) and [the AttestationLimited extension](../assets/eip-6956/contracts/IERC6956AttestationLimited.sol) can be found [here](../assets/eip-6956/test/ERC6956Full.ts) - -## Reference Implementation - -- Minimal implementation, only supporting [the proposed standard interface](../assets/eip-6956/contracts/IERC6956.sol) can be found [here](../assets/eip-6956/contracts/ERC6956.sol) -- Full implementation, with support for [the proposed standard interface](../assets/eip-6956/contracts/IERC6956.sol), [the Floatable extension](../assets/eip-6956/contracts/IERC6956Floatable.sol), [the ValidAnchors extension](../assets/eip-6956/contracts/IERC6956ValidAnchors.sol) and [the AttestationLimited extension](../assets/eip-6956/contracts/IERC6956AttestationLimited.sol) can be found [here](../assets/eip-6956/contracts/ERC6956Full.sol) - -## Security Considerations - - - -**If the asset is stolen, does this mean the thief has control over the NFT?** -Yes.The standard aims to anchor an NFT to the asset inseperably and unconditionally. This includes reflecting theft, as the ORACLE will testify that PROOF-OF-CONTROL over the ASSET is established. The ORACLE does not testify whether the controller is the legitimate owner, -Note that this may even be a benefit. If the thief (or somebody receiving the asset from the thief) should interact with the anchor, an on-chain address of somebody connected to the crime (directly or another victim) becomes known. This can be a valuable starting point for investigation. -Also note that the proposed standard can be combined with any lock-mechanism, which could lock attestation-based action temporarily or permanently (after mint). - -**How to use AttestationLimits to avoid fund-draining** -A central security mechanism in blockchain applications are gas fees. Gas fees ensure that executing a high number of transactions get penalized, hence all DoS or other large-scale attacks are discouraged. Due to the permissionless nature of attestation-authorized operations, many use-cases will arise, where the issuer of the ASSET (which normally is also the issuer of the ASSET-BOUND NFT) will pay for all transactions - contrary to the well-known ERC-721 behavior, where either from- or to-address are paying. So a user with malicious intent may just let the ORACLE approve PROOF-OF-CONTROL multiple times with specifying alternating account addresses. These ATTESTATIONS will be handed to the central gas-payer, who will execute them in a permissionless way, paying gas-fees for each transactions. This effectively drains the funds from the gas-payer, making the system unusable as soon as the gas-payer can no longer pay for transactions. - -**Why do you recommend hashing serial numbers over using them plain?** -Using any sequential identifier allows to at least conclude of the number between the lowest and highest ever used serial number. This therefore provides good indication over the total number of assets on the market. While a limited number of assets is often desirable for collectables, publishing exact production numbers of assets is undesirable for most industries, as it equals to publishing sales/revenue numbers per product group, which is often considered confidential. Within supply chains, serial numbers are often mandatory due to their range-based processing capability. The simplest approach to allow using physical serial numbers and still obfuscating the actual number of assets is through hashing/encryption of the serial number. - -**Why is anchor-validation needed, why not simply trust the oracle to attest only valid anchors?** -The oracle testifies PROOF-OF-CONTROL. As the ORACLE has to know the merkle-tree of valid anchors, it could also modify the merkle-tree with malicious intent. Therefore, having an on-chain verification, whether the original merkle-tree has been used, is needed. Even if the oracle gets compromised, it should not have the power to introduce new anchors. This is achieved by requiring that the oracle knows the merkle-tree, but updateValidAnchors() can only be called by a maintainer. Note that the oracle must not be the maintainer. As a consequence, care shall be taken off-chain, in order to ensure that compromising one system-part not automatically compromises oracle and maintainer accounts. - -**Why do you use merkle-trees for anchor-validation?** -For security- and gas-reasons. Except for limited collections, anchors will typically be added over time, e.g. when a new batch of the asset is produced or issued. While it is already ineffective to store all available anchors on-chain gas-wise, publishing all anchors would also expose the total number of assets. When using the data from anchor-updates one could even deduce the production capabilities of that asset, which is usually considered confidential information. - -**Assume you have N anchors. If all anchored NFTs are minted, what use is a merkle-tree?** -If all anchored NFTs are minted this implies that all anchors have been published and could be gathered on-chain. Consequently, the merkle-tree can be reconstructed. While this may not be an issue for many use cases (all supported anchors are minted anyway), we still recommend to add one "salt-leave" to the merkle-tree, characterized in that the ORACLE will never issue an attestation for an ANCHOR matching that salt-leave. Therefore, even if all N anchors are - -### Security Considerations for PHYSICAL ASSETS - -In case the ASSET is a physical object, good or property, the following ADDITIONAL specifications MUST be satisfied: - -#### ORACLE for Physical Anchors - -- Issuing an ATTESTATION requires that the ORACLE - - MUST proof physical proximity between an input device (for example smartphone) specifying the `to` address and a particular physical ANCHOR and it's associated physical object. Typical acceptable proximity is ranges between some millimeters to several meters. - - The physical presence MUST be verified beyond reasonable doubt, in particular the employed method - - MUST be robust against duplication or reproduction attempts of the physical ANCHOR, - - MUST be robust against spoofing (for example presentation attacks) etc. - - MUST be implemented under the assumption that the party defining the `to` address has malicious intent and to acquire false ATTESTATION, without currently or ever having access to the physical object comprising the physical ANCHOR. - -#### Physical ASSET - -- MUST comprise an ANCHOR, acting as the unique physical object identifier, typically a serial number (plain (NOT RECOMMENDED) or hashed (RECOMMENDED)) -- MUST comprise a physical security device, marking or any other feature that enables proofing physical presence for ATTESTATION through the ORACLE -- Is RECOMMENDED to employ ANCHOR-TECHNOLOGIES featuring irreproducible security features. -- In general it is NOT RECOMMENDED to employ ANCHOR-TECHNOLOGIES that can easily be replicated (for example barcodes, "ordinary" NFC chips, .. ). Replication includes physical and digital replication. - - - -### Security Considerations for DIGITAL ASSETS - - - - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6956.md diff --git a/EIPS/eip-6960.md b/EIPS/eip-6960.md index 7d1214061f4344..4d8753089146fa 100644 --- a/EIPS/eip-6960.md +++ b/EIPS/eip-6960.md @@ -1,325 +1 @@ ---- -eip: 6960 -title: Dual Layer Token -description: Token with a two-level classification system using mainId and subId -author: Adam Boudjemaa (@aboudjem), Mohamad Hammoud (@mohamadhammoud), Nawar Hisso (@nawar-hisso), Khawla Hassan (@khawlahssn), Mohammad Zakeri Rad (@zakrad), Ashish Sood -discussions-to: https://ethereum-magicians.org/t/eip-6960-dual-layer-token/14070 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-30 ---- - -## Abstract - -The dual-layer token combines the functionalities of [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), and [ERC-1155](./eip-1155.md) while adding a classification layer that uses `mainId` as the main asset type identifier and `subId` as the unique attributes or variations of the main asset. -![Dual Layer Token](../assets/eip-6960/eip-6960-dual-layer-token-dlt.png) - -The proposed token aims to offer more granularity in token management, facilitating a well-organized token ecosystem and simplifying the process of tracking tokens within a contract. This standard is particularly useful for tokenizing and enabling the fractional ownership of Real World Assets (RWAs). It also allows for efficient and flexible management of both fungible and non-fungible assets. - -The following are examples of assets that the DLT standard can represent fractional ownership of: - -- Invoices -- Company stocks -- Digital collectibles -- Real estate - -## Motivation - -The [ERC-1155](./eip-1155.md) standard has experienced considerable adoption within the Ethereum ecosystem; however, its design exhibits constraints when handling tokens with multiple classifications, particularly in relation to Real World Assets (RWAs) and fractionalization of assets. - -This EIP strives to overcome this limitation by proposing a token standard incorporating a dual-layer classification system, allowing for enhanced organization and management of tokens, especially in situations where additional sub-categorization of token types is necessary. - -## 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. - -### DLT Interface - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.17; - -/** - * @title DLT token standard interface - * @dev Interface for any contract that wants to implement the DLT standard - */ -interface IDLT { - - /** - * @dev MUST emit when `subId` token is transferred from `sender` to `recipient` - * @param sender is the address of the previous holder whose balance is decreased - * @param recipient is the address of the new holder whose balance is increased - * @param mainId is the main token type ID to be transferred - * @param subId is the token subtype ID to be transferred - * @param amount is the amount to be transferred of the token subtype - */ - event Transfer( - address indexed sender, - address indexed recipient, - uint256 indexed mainId, - uint256 subId, - uint256 amount - ); - - /** - * @dev MUST emit when `subIds` token array is transferred from `sender` to `recipient` - * @param sender is the address of the previous holder whose balance is decreased - * @param recipient is the address of the new holder whose balance is increased - * @param mainIds is the main token type ID array to be transferred - * @param subIds is the token subtype ID array to be transferred - * @param amounts is the amount array to be transferred of the token subtype - */ - event TransferBatch( - address indexed sender, - address indexed recipient, - uint256[] mainIds, - uint256[] subIds, - uint256[] amounts - ); - - /** - * @dev MUST emit when `owner` enables `operator` to manage the `subId` token - * @param owner is the address of the token owner - * @param operator is the authorized address to manage the allocated amount for an owner address - * @param mainId is the main token type ID to be approved - * @param subId is the token subtype ID to be approved - * @param amount is the amount to be approved of the token subtype - */ - event Approval( - address indexed owner, - address indexed operator, - uint256 mainId, - uint256 subId, - uint256 amount - ); - - /** - * @dev MUST emit when `owner` enables or disables (`approved`) `operator` to manage all of its assets - * @param owner is the address of the token owner - * @param operator is the authorized address to manage all tokens for an owner address - * @param approved true if the operator is approved, false to revoke approval - */ - event ApprovalForAll( - address indexed owner, - address indexed operator, - bool approved - ); - - /** - * @dev MUST emit when the URI is updated for a main token type ID. - * URIs are defined in RFC 3986. - * The URI MUST point to a JSON file that conforms to the "DLT Metadata URI JSON Schema". - * @param oldValue is the old URI value - * @param newValue is the new URI value - * @param mainId is the main token type ID - */ - event URI(string oldValue, string newValue, uint256 indexed mainId); - - /** - * @dev Approve or remove `operator` as an operator for the caller. - * Operators can call {transferFrom} or {safeTransferFrom} for any subId owned by the caller. - * The `operator` MUST NOT be the caller. - * MUST emit an {ApprovalForAll} event. - * @param operator is the authorized address to manage all tokens for an owner address - * @param approved true if the operator is approved, false to revoke approval - */ - function setApprovalForAll(address operator, bool approved) external; - - /** - * @dev Moves `amount` tokens from `sender` to `recipient` using the - * allowance mechanism. `amount` is then deducted from the caller's - * allowance. - * MUST revert if `sender` or `recipient` is the zero address. - * MUST revert if balance of holder for token `subId` is lower than the `amount` sent. - * MUST emit a {Transfer} event. - * @param sender is the address of the previous holder whose balance is decreased - * @param recipient is the address of the new holder whose balance is increased - * @param mainId is the main token type ID to be transferred - * @param subId is the token subtype ID to be transferred - * @param amount is the amount to be transferred of the token subtype - * @param data is additional data with no specified format - * @return True if the operation succeeded, false if operation failed - */ - function safeTransferFrom( - address sender, - address recipient, - uint256 mainId, - uint256 subId, - uint256 amount, - bytes calldata data - ) external returns (bool); - - /** - * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. - * The `operator` MUST NOT be the caller. - * MUST revert if `operator` is the zero address. - * MUST emit an {Approval} event. - * @param operator is the authorized address to manage tokens for an owner address - * @param mainId is the main token type ID to be approved - * @param subId is the token subtype ID to be approved - * @param amount is the amount to be approved of the token subtype - * @return True if the operation succeeded, false if operation failed - */ - function approve( - address operator, - uint256 mainId, - uint256 subId, - uint256 amount - ) external returns (bool); - - /** - * @notice Get the token with a particular subId balance of an `account` - * @param account is the address of the token holder - * @param mainId is the main token type ID - * @param subId is the token subtype ID - * @return The amount of tokens owned by `account` in subId - */ - function subBalanceOf( - address account, - uint256 mainId, - uint256 subId - ) external view returns (uint256); - - /** - * @notice Get the tokens with a particular subIds balance of an `accounts` array - * @param accounts is the address array of the token holder - * @param mainIds is the main token type ID array - * @param subIds is the token subtype ID array - * @return The amount of tokens owned by `accounts` in subIds - */ - function balanceOfBatch( - address[] calldata accounts, - uint256[] calldata mainIds, - uint256[] calldata subIds - ) external view returns (uint256[] calldata); - - /** - * @notice Get the allowance allocated to an `operator` - * @dev This value changes when {approve} or {transferFrom} are called - * @param owner is the address of the token owner - * @param operator is the authorized address to manage assets for an owner address - * @param mainId is the main token type ID - * @param subId is the token subtype ID - * @return The remaining number of tokens that `operator` will be - * allowed to spend on behalf of `owner` through {transferFrom}. This is - * zero by default. - */ - function allowance( - address owner, - address operator, - uint256 mainId, - uint256 subId - ) external view returns (uint256); - - /** - * @notice Get the approval status of an `operator` to manage assets - * @param owner is the address of the token owner - * @param operator is the authorized address to manage assets for an owner address - * @return True if the `operator` is allowed to manage all of the assets of `owner`, false if approval is revoked - * See {setApprovalForAll} - */ - function isApprovedForAll( - address owner, - address operator - ) external view returns (bool); -} -``` - -### `DLTReceiver` Interface - -Smart contracts MUST implement all the functions in the `DLTReceiver` interface to accept transfers. - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.17; - -/** - * @title DLT token receiver interface - * @dev Interface for any contract that wants to support safeTransfers - * from DLT asset contracts. - */ -interface IDLTReceiver { - /** - * @notice Handle the receipt of a single DLT token type. - * @dev Whenever an {DLT} `subId` token is transferred to this contract via {IDLT-safeTransferFrom} - * by `operator` from `sender`, this function is called. - * MUST return its Solidity selector to confirm the token transfer. - * MUST revert if any other value is returned or the interface is not implemented by the recipient. - * The selector can be obtained in Solidity with `IDLTReceiver.onDLTReceived.selector`. - * @param operator is the address which initiated the transfer - * @param from is the address which previously owned the token - * @param mainId is the main token type ID being transferred - * @param subId subId is the token subtype ID being transferred - * @param amount is the amount of tokens being transferred - * @param data is additional data with no specified format - * @return `IDLTReceiver.onDLTReceived.selector` - */ - function onDLTReceived( - address operator, - address from, - uint256 mainId, - uint256 subId, - uint256 amount, - bytes calldata data - ) external returns (bytes4); - - /** - * @notice Handle the receipts of a DLT token type array. - * @dev Whenever an {DLT} `subIds` token is transferred to this contract via {IDLT-safeTransferFrom} - * by `operator` from `sender`, this function is called. - * MUST return its Solidity selector to confirm the token transfers. - * MUST revert if any other value is returned or the interface is not implemented by the recipient. - * The selector can be obtained in Solidity with `IDLTReceiver.onDLTReceived.selector`. - * @param operator is the address which initiated the transfer - * @param from is the address which previously owned the token - * @param mainIds is the main token type ID being transferred - * @param subIds subId is the token subtype ID being transferred - * @param amounts is the amount of tokens being transferred - * @param data is additional data with no specified format - * @return `IDLTReceiver.onDLTReceived.selector` - */ - function onDLTBatchReceived( - address operator, - address from, - uint256[] calldata mainIds, - uint256[] calldata subIds, - uint256[] calldata amounts, - bytes calldata data - ) external returns (bytes4); -} -``` - -## Rationale - -The two-level classification system introduced in this EIP allows for a more organized token ecosystem, enabling users to manage and track tokens with greater granularity. It is particularly useful for projects that require token classifications beyond the capabilities of the current ERC-1155 standard. - -As assets can have various properties or variations, our smart contract design reflects this by assigning a mainId to each asset category and a unique subId to each derivative or sub-category. This approach expands the capabilities of ERC-1155 to support a broader range of assets with complex requirements. Additionally, it enables tracking of mainBalance for the main asset and subBalance for its sub-assets individual accounts. - -The contract can be extended to support the use of subIds in two ways: - -- Shared SubIds: where all mainIds share the same set of subIds. -- Mixed SubIds: where mainIds have unique sets of subIds. - -DLT provides a more versatile solution compared to other token standards such as ERC-20, ERC-721, and ERC-1155 by effectively managing both fungible and non-fungible assets within the same contract. - -The following are questions that we considered during the design process: - -- How to name the proposal? -The standard introduces a two-level classification to tokens where one main asset (layer 1) can be further sub-divided into several sub-assets (layer 2) hence we decided to name it as "Dual-layer" token to reflect the hierarchical structure of the token classification. -- Should we limit the classification to two levels? -The standard’s implementation maintains a mapping to track the total supply of each sub-asset. If we allow sub-assets to have their own children, it would be necessary to introduce additional methods to track each sub-asset, which would be impractical and increases the complexity of the contract. -- Should we extend the ERC-1155 standard? -As the ERC-1155 standard is not designed to support a layered classification and requires significant modifications to do so, we concluded that it would not be appropriate to extend it for the dual-layer token standard. Hence, a standalone implementation would be a more suitable approach. - -## Backwards Compatibility - -No backward compatibility issues found. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6960.md diff --git a/EIPS/eip-6981.md b/EIPS/eip-6981.md index 4ac21cdf2710e5..6a39129625a05b 100644 --- a/EIPS/eip-6981.md +++ b/EIPS/eip-6981.md @@ -1,532 +1 @@ ---- -eip: 6981 -title: Reserved Ownership Accounts -description: A registry for generating future-deployed smart contract accounts owned by users on external services -author: Paul Sullivan (@sullivph) , Wilkins Chung (@wwchung) , Kartik Patel (@Slokh) -discussions-to: https://ethereum-magicians.org/t/erc-6981-reserved-ownership-accounts/14118 -status: Draft -type: Standards Track -category: ERC -created: 2023-04-25 -requires: 1167, 1271, 6492 ---- - -## Abstract - -The following specifies a system for services to link their users to a claimable Ethereum address. Services can provide a signed message and unique salt to their users which can be used to deploy a smart contract wallet to the deterministic address through a registry contract using the `create2` opcode. - -## Motivation - -It is common for web services to allow their users to hold on-chain assets via custodial wallets. These wallets are typically EOAs, deployed smart contract wallets or omnibus contracts, with private keys or asset ownership information stored on a traditional database. This proposal outlines a solution that avoids the security concerns associated with historical approaches, and rids the need and implications of services controlling user assets - -Users on external services that choose to leverage the following specification can be given an Ethereum address to receive assets without the need to do any on-chain transaction. These users can choose to attain control of said addresses at a future point in time. Thus, on-chain assets can be sent to and owned by a user beforehand, therefore enabling the formation of an on-chain identity without requiring the user to interact with the underlying blockchain. - -## 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 - -The system for creating reserved ownership accounts consists of: - -1. An Account Registry which provides deterministic addresses based on the service users' identifying salts, and implements a signature verified function that enables claiming of Account Instances by the service's end users. -2. Account Instances created through the Account Registry by end users which allow access to the assets received at the deterministic address prior to Account Instance deployment. - -External services wishing to provide their users with reserved ownership accounts MUST maintain a relationship between a user's identifying credentials and a salt. The external service SHALL refer to an Account Registry Instance to retrieve the deterministic account address for a given salt. Users of a given service MUST be able to create an Account Instance by validating their identifying credentials via the external service, which SHOULD give the user a signed message for their salt. Signatures SHOULD be generated by the external service using an signing address known to the Account Registry Instance. Users SHALL pass this message and signature to the service's Account Registry Instance in a call to `claimAccount` to deploy and claim an Account Instance at the deterministic address. - -### Account Registry - -The Account Registry MUST implement the following interface: - -```solidity -interface IAccountRegistry { - /** - * @dev Registry instances emit the AccountCreated event upon successful account creation - */ - event AccountCreated(address account, address accountImplementation, uint256 salt); - - /** - * @dev Registry instances emit the AccountClaimed event upon successful claim of account by owner - */ - event AccountClaimed(address account, address owner); - - /** - * @dev Creates a smart contract account. - * - * If account has already been created, returns the account address without calling create2. - * - * @param salt - The identifying salt for which the user wishes to deploy an Account Instance - * - * Emits AccountCreated event - * @return the address for which the Account Instance was created - */ - function createAccount(uint256 salt) external returns (address); - - /** - * @dev Allows an owner to claim a smart contract account created by this registry. - * - * If the account has not already been created, the account will be created first using `createAccount` - * - * @param owner - The initial owner of the new Account Instance - * @param salt - The identifying salt for which the user wishes to deploy an Account Instance - * @param expiration - If expiration > 0, represents expiration time for the signature. Otherwise - * signature does not expire. - * @param message - The keccak256 message which validates the owner, salt, expiration - * @param signature - The signature which validates the owner, salt, expiration - * - * Emits AccountClaimed event - * @return the address of the claimed Account Instance - */ - function claimAccount( - address owner, - uint256 salt, - uint256 expiration, - bytes32 message, - bytes calldata signature - ) external returns (address); - - /** - * @dev Returns the computed address of a smart contract account for a given identifying salt - * - * @return the computed address of the account - */ - function account(uint256 salt) external view returns (address); - - /** - * @dev Fallback signature verification for unclaimed accounts - */ - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4); -} -``` - -#### createAccount - -`createAccount` is used to deploy the Account Instance for a given salt. - -- This function MUST deploy a new Account Instance as a [ERC-1167](./eip-1167.md) proxy pointing to the account implementation. -- This function SHOULD set the initial owner of the Account Instance to the Account Registry Instance. -- The account implementation address MUST be immutable, as it is used to compute the deterministic address for the Account Instance. -- Upon successful deployment of the Account Instance, the registry SHOULD emit an `AccountCreated` event. - -#### claimAccount - -`claimAccount` is used to claim ownership of the Account Instance for a given salt. - -- This function MUST create a new Account Instance if one does not already exist for the given salt. -- This function SHOULD verify that the msg.sender has permission to claim ownership over the Account Instance for the identifying salt and initial owner. Verification SHOULD be done by validating the message and signature against the owner, salt and expiration using ECDSA for EOA signers, or [ERC-1271](./eip-1271.md) for smart contract signers. -- This function SHOULD verify that the block.timestamp < expiration or that expiration == 0. -- Upon successful signature verification on calls to `claimAccount`, the registry MUST completely relinquish control over the Account Instance, and assign ownership to the initial owner by calling `setOwner` on the Account Instance. -- Upon successful claim of the Account Instance, the registry SHOULD emit an `AccountClaimed` event. - -#### isValidSignature - -`isValidSignature` is a fallback signature verification function used by unclaimed accounts. Valid signatures SHALL be generated by the registry signer by signing a composite hash of the original message hash, and the Account Instance address (e.g. `bytes32 compositeHash = keccak256(abi.encodePacked(originalHash, accountAddress))`). The function MUST reconstruct the composite hash, where `originalHash` is the hash passed to the function, and `accountAddress` is `msg.sender` (the unclaimed Account Instance). The function MUST verify the signature against the composite hash and registry signer. - -### Account Instance - -The Account Instance MUST implement the following interface: - -```solidity -interface IAccount is IERC1271 { - /** - * @dev Sets the owner of the Account Instance. - * - * Only callable by the current owner of the instance, or by the registry if the Account - * Instance has not yet been claimed. - * - * @param owner - The new owner of the Account Instance - */ - function setOwner(address owner) external; -} -``` - -- All Account Instances MUST be created using an Account Registry Instance. -- Account Instances SHOULD provide access to assets previously sent to the address at which the Account Instance is deployed to. -- `setOwner` SHOULD update the owner and SHOULD be callable by the current owner of the Account Instance. -- If an Account Instance is deployed, but not claimed, the owner of the Account Instance MUST be initialized to the Account Registry Instance. -- An Account Instance SHALL determine if it has been claimed by checking if the owner is the Account Registry Instance. - -#### Account Instance Signatures - -Account Instances MUST support [ERC-1271](./eip-1271.md) by implementing an `isValidSignature` function. When the owner of an Account Instance wants to sign a message (e.g. to log in to a dApp), the signature MUST be generated in one of the following ways, depending the state of the Account Instance: - -1. If the Account instance is deployed and claimed, the owner should generate the signature, and `isValidSignature` SHOULD verify that the message hash and signature are valid for the current owner of the Account Instance. -2. If the Account Instance is deployed, but unclaimed, the registry signer should generate the signature using a composite hash of the original message and address of the Account Instance described [above](#isvalidsignature), and `isValidSignature` SHOULD forward the message hash and signature to the Account Registry Instance's `isValidSignature` function. -3. If the Account Instance is not deployed, the registry signer should generate a signature on the composite hash as done in situation 2, and wrap the signature according to [ERC-6492](./eip-6492.md#signer-side) (e.g. `concat(abi.encode((registryAddress, createAccountCalldata, compositeHashSignature), (address, bytes, bytes)), magicBytes)`). - -Signature validation for Account Instances should be done according to [ERC-6492](./eip-6492.md#verifier-side). - -## Rationale - -### Service-Owned Registry Instances - -While it might seem more user-friendly to implement and deploy a universal registry for reserved ownership accounts, we believe that it is important for external service providers to have the option to own and control their own Account Registry. This provides the flexibility of implementing their own permission controls and account deployment authorization frameworks. - -We are providing a reference Registry Factory which can deploy Account Registries for an external service, which comes with: - -- Immutable Account Instance implementation -- Validation for the `claimAccount` method via ECDSA for EOA signers, or [ERC-1271](./eip-1271.md) validation for smart contract signers -- Ability for the Account Registry deployer to change the signing addressed used for `claimAccount` validation - -### Account Registry and Account Implementation Coupling - -Since Account Instances are deployed as [ERC-1167](./eip-1167.md) proxies, the account implementation address affects the addresses of accounts deployed from a given Account Registry. Requiring that registry instances be linked to a single, immutable account implementation ensures consistency between a user's salt and linked address on a given Account Registry Instance. - -This also allows services to gain the the trust of users by deploying their registries with a reference to a trusted account implementation address. - -Furthermore, account implementations can be designed as upgradeable, so users are not necessarily bound to the implementation specified by the Account Registry Instance used to create their account. - -### Separate `createAccount` and `claimAccount` Operations - -Operations to create and claim Account Instances are intentionally separate. This allows services to provide users with valid [ERC-6492](./eip-6492.md) signatures before their Account Instance has been deployed. - -## Reference Implementation - -The following is an example of an Account Registry Factory which can be used by external service providers to deploy their own Account Registry Instance. - -### Account Registry Factory - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.13; - -/// @author: manifold.xyz - -import {Create2} from "openzeppelin/utils/Create2.sol"; - -import {Address} from "../../lib/Address.sol"; -import {ERC1167ProxyBytecode} from "../../lib/ERC1167ProxyBytecode.sol"; -import {IAccountRegistryFactory} from "./IAccountRegistryFactory.sol"; - -contract AccountRegistryFactory is IAccountRegistryFactory { - using Address for address; - - error InitializationFailed(); - - address private immutable registryImplementation = 0x076B08EDE2B28fab0c1886F029cD6d02C8fF0E94; - - function createRegistry( - uint96 index, - address accountImplementation, - bytes calldata accountInitData - ) external returns (address) { - bytes32 salt = _getSalt(msg.sender, index); - bytes memory code = ERC1167ProxyBytecode.createCode(registryImplementation); - address _registry = Create2.computeAddress(salt, keccak256(code)); - - if (_registry.isDeployed()) return _registry; - - _registry = Create2.deploy(0, salt, code); - - (bool success, ) = _registry.call( - abi.encodeWithSignature( - "initialize(address,address,bytes)", - msg.sender, - accountImplementation, - accountInitData - ) - ); - if (!success) revert InitializationFailed(); - - emit AccountRegistryCreated(_registry, accountImplementation, index); - - return _registry; - } - - function registry(address deployer, uint96 index) external view override returns (address) { - bytes32 salt = _getSalt(deployer, index); - bytes memory code = ERC1167ProxyBytecode.createCode(registryImplementation); - return Create2.computeAddress(salt, keccak256(code)); - } - - function _getSalt(address deployer, uint96 index) private pure returns (bytes32) { - return bytes32(abi.encodePacked(deployer, index)); - } -} -``` - -### Account Registry - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.13; - -/// @author: manifold.xyz - -import {Create2} from "openzeppelin/utils/Create2.sol"; -import {ECDSA} from "openzeppelin/utils/cryptography/ECDSA.sol"; -import {Ownable} from "openzeppelin/access/Ownable.sol"; -import {Initializable} from "openzeppelin/proxy/utils/Initializable.sol"; -import {IERC1271} from "openzeppelin/interfaces/IERC1271.sol"; -import {SignatureChecker} from "openzeppelin/utils/cryptography/SignatureChecker.sol"; - -import {Address} from "../../lib/Address.sol"; -import {IAccountRegistry} from "../../interfaces/IAccountRegistry.sol"; -import {ERC1167ProxyBytecode} from "../../lib/ERC1167ProxyBytecode.sol"; - -contract AccountRegistryImplementation is Ownable, Initializable, IAccountRegistry { - using Address for address; - using ECDSA for bytes32; - - struct Signer { - address account; - bool isContract; - } - - error InitializationFailed(); - error ClaimFailed(); - error Unauthorized(); - - address public accountImplementation; - bytes public accountInitData; - Signer public signer; - - constructor() { - _disableInitializers(); - } - - function initialize( - address owner, - address accountImplementation_, - bytes calldata accountInitData_ - ) external initializer { - _transferOwnership(owner); - accountImplementation = accountImplementation_; - accountInitData = accountInitData_; - } - - /** - * @dev See {IAccountRegistry-createAccount} - */ - function createAccount(uint256 salt) external override returns (address) { - bytes memory code = ERC1167ProxyBytecode.createCode(accountImplementation); - address _account = Create2.computeAddress(bytes32(salt), keccak256(code)); - - if (_account.isDeployed()) return _account; - - _account = Create2.deploy(0, bytes32(salt), code); - - (bool success, ) = _account.call(accountInitData); - if (!success) revert InitializationFailed(); - - emit AccountCreated(_account, accountImplementation, salt); - - return _account; - } - - /** - * @dev See {IAccountRegistry-claimAccount} - */ - function claimAccount( - address owner, - uint256 salt, - uint256 expiration, - bytes32 message, - bytes calldata signature - ) external override returns (address) { - _verify(owner, salt, expiration, message, signature); - address _account = this.createAccount(salt); - - (bool success, ) = _account.call( - abi.encodeWithSignature("transferOwnership(address)", owner) - ); - if (!success) revert ClaimFailed(); - - emit AccountClaimed(_account, owner); - return _account; - } - - /** - * @dev See {IAccountRegistry-account} - */ - function account(uint256 salt) external view override returns (address) { - bytes memory code = ERC1167ProxyBytecode.createCode(accountImplementation); - return Create2.computeAddress(bytes32(salt), keccak256(code)); - } - - /** - * @dev See {IAccountRegistry-isValidSignature} - */ - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4) { - bytes32 expectedHash = keccak256(abi.encodePacked(hash, msg.sender)); - bool isValid = SignatureChecker.isValidSignatureNow( - signer.account, - expectedHash, - signature - ); - if (isValid) { - return IERC1271.isValidSignature.selector; - } - - return ""; - } - - function updateSigner(address newSigner) external onlyOwner { - uint32 signerSize; - assembly { - signerSize := extcodesize(newSigner) - } - signer.account = newSigner; - signer.isContract = signerSize > 0; - } - - function _verify( - address owner, - uint256 salt, - uint256 expiration, - bytes32 message, - bytes calldata signature - ) internal view { - address signatureAccount; - - if (signer.isContract) { - if (!SignatureChecker.isValidSignatureNow(signer.account, message, signature)) - revert Unauthorized(); - } else { - signatureAccount = message.recover(signature); - } - - bytes32 expectedMessage = keccak256( - abi.encodePacked("\x19Ethereum Signed Message:\n84", owner, salt, expiration) - ); - - if ( - message != expectedMessage || - (!signer.isContract && signatureAccount != signer.account) || - (expiration != 0 && expiration < block.timestamp) - ) revert Unauthorized(); - } -} -``` - -### Example Account Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.13; - -/// @author: manifold.xyz - -import {IERC1271} from "openzeppelin/interfaces/IERC1271.sol"; -import {SignatureChecker} from "openzeppelin/utils/cryptography/SignatureChecker.sol"; -import {IERC165} from "openzeppelin/utils/introspection/IERC165.sol"; -import {ERC165Checker} from "openzeppelin/utils/introspection/ERC165Checker.sol"; -import {IERC721} from "openzeppelin/token/ERC721/IERC721.sol"; -import {IERC721Receiver} from "openzeppelin/token/ERC721/IERC721Receiver.sol"; -import {IERC1155Receiver} from "openzeppelin/token/ERC1155/IERC1155Receiver.sol"; -import {Initializable} from "openzeppelin/proxy/utils/Initializable.sol"; -import {Ownable} from "openzeppelin/access/Ownable.sol"; -import {IERC1967Account} from "./IERC1967Account.sol"; - -import {IAccount} from "../../interfaces/IAccount.sol"; - -/** - * @title ERC1967AccountImplementation - * @notice A lightweight, upgradeable smart contract wallet implementation - */ -contract ERC1967AccountImplementation is - IAccount, - IERC165, - IERC721Receiver, - IERC1155Receiver, - IERC1967Account, - Initializable, - Ownable -{ - address public registry; - - constructor() { - _disableInitializers(); - } - - function initialize() external initializer { - registry = msg.sender; - _transferOwnership(registry); - } - - function supportsInterface(bytes4 interfaceId) external pure returns (bool) { - return (interfaceId == type(IAccount).interfaceId || - interfaceId == type(IERC1967Account).interfaceId || - interfaceId == type(IERC1155Receiver).interfaceId || - interfaceId == type(IERC721Receiver).interfaceId || - interfaceId == type(IERC165).interfaceId); - } - - function onERC721Received( - address, - address, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC721Received.selector; - } - - function onERC1155Received( - address, - address, - uint256, - uint256, - bytes memory - ) public pure returns (bytes4) { - return this.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address, - address, - uint256[] memory, - uint256[] memory, - bytes memory - ) public pure returns (bytes4) { - return this.onERC1155BatchReceived.selector; - } - - /** - * @dev {See IERC1967Account-executeCall} - */ - function executeCall( - address _target, - uint256 _value, - bytes calldata _data - ) external payable override onlyOwner returns (bytes memory _result) { - bool success; - // solhint-disable-next-line avoid-low-level-calls - (success, _result) = _target.call{value: _value}(_data); - require(success, string(_result)); - emit TransactionExecuted(_target, _value, _data); - return _result; - } - - /** - * @dev {See IAccount-setOwner} - */ - function setOwner(address _owner) external override onlyOwner { - _transferOwnership(_owner); - } - - receive() external payable {} - - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4) { - if (owner() == registry) { - return IERC1271(registry).isValidSignature(hash, signature); - } - - bool isValid = SignatureChecker.isValidSignatureNow(owner(), hash, signature); - if (isValid) { - return IERC1271.isValidSignature.selector; - } - - return ""; - } -} -``` - -## Security Considerations - -### Front-running - -Deployment of reserved ownership accounts through an Account Registry Instance through calls to `createAccount` could be front-run by a malicious actor. However, if the malicious actor attempted to alter the `owner` parameter in the calldata, the Account Registry Instance would find the signature to be invalid, and revert the transaction. Thus, any successful front-running transaction would deploy an identical Account Instance to the original transaction, and the original owner would still gain control over the address. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6981.md diff --git a/EIPS/eip-6982.md b/EIPS/eip-6982.md index 2a64676f88366e..36026cee33697a 100644 --- a/EIPS/eip-6982.md +++ b/EIPS/eip-6982.md @@ -1,90 +1 @@ ---- -eip: 6982 -title: Efficient Default Lockable Tokens -description: A gas-efficient approach to lockable ERC-721 tokens -author: Francesco Sullo (@sullof), Alexe Spataru (@urataps) -discussions-to: https://ethereum-magicians.org/t/erc721-default-lockable-proposal/13366 -status: Review -type: Standards Track -category: ERC -created: 2023-05-02 -requires: 165, 721 ---- - -## Abstract - -This proposal introduces a lockable interface for [ERC-721](./eip-721.md) tokens that optimizes gas usage by eliminating unnecessary events. This interface forms the foundation for the creation and management of lockable [ERC-721](./eip-721.md) tokens. It provides a gas-efficient approach by emitting a `DefaultLocked(bool locked)` event upon deployment, setting the initial lock status for all tokens, while individual `Locked(uint256 indexed tokenId, bool locked)` events handle subsequent status changes for specific tokens. The interface also includes a view function `locked(uint256 tokenId)` to return the current lock status of a token, and a view function `defaultLocked()` to query the default status of a newly minted token. - -## Motivation - -Existing lockable token proposals often mandate the emission of an event each time a token is minted. This results in unnecessary gas consumption, especially in cases where tokens are permanently locked from inception to destruction (e.g., soulbounds or non-transferable badges). This proposal offers a more gas-efficient solution that only emits events upon contract deployment and status changes of individual tokens. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -The interface is defined as follows: - -```solidity -// ERC165 interfaceId 0x6b61a747 -interface IERC6982 { - // This event MUST be emitted upon deployment of the contract, establishing - // the default lock status for any tokens that will be minted in the future. - // If the default lock status changes for any reason, this event - // MUST be re-emitted to update the default status for all tokens. - // Note that emitting a new DefaultLocked event does not affect the lock - // status of any tokens for which a Locked event has previously been emitted. - event DefaultLocked(bool locked); - - // This event MUST be emitted whenever the lock status of a specific token - // changes, effectively overriding the default lock status for this token. - event Locked(uint256 indexed tokenId, bool locked); - - // This function returns the current default lock status for tokens. - // It reflects the value set by the latest DefaultLocked event. - function defaultLocked() external view returns (bool); - - // This function returns the lock status of a specific token. - // If no Locked event has been emitted for a given tokenId, it MUST return - // the value that defaultLocked() returns, which represents the default - // lock status. - // This function MUST revert if the token does not exist. - function locked(uint256 tokenId) external view returns (bool); -} -``` - -The [ERC-165](./eip-165.md) interfaceId is `0x6b61a747`. - -## Rationale - -This standard seeks to optimize gas consumption by minimizing the frequency of event emission. The `DefaultLocked` event is designed to establish the lock status for all tokens, thereby circumventing the need to emit an event each time a new token is minted. It's crucial to note that the `DefaultLocked` event can be emitted at any point in time, and is not restricted to only before the `Locked` events are emitted. - -Tokens may alter their behavior under certain circumstances (such as after a reveal), prompting the re-emission of the `DefaultLocked` event to reflect the new default status. The primary objective here is to economize on gas usage by avoiding the need to emit a `Locked` event for each token when the default status changes. - -The `Locked` event is utilized to document changes in the lock status of individual tokens. - -The `defaultLocked` function returns the prevailing default lock status of a token. This function is beneficial as it fosters interaction with other contracts and averts potential conflicts with [ERC-5192](./eip-5192), which is in its final stage. - -The `locked` function gives the current lock status of a particular token, further facilitating interaction with other contracts. If no changes have been made to a specific token ID, this function should return the value provided by the `defaultLocked` function. - -Bear in mind that a token being designated as "locked" doesn't necessarily imply that it is entirely non-transferable. There might be certain conditions under which a token can still be transferred despite its locked status. Primarily, the locked status relates to a token's transferability on marketplaces and external exchanges. - -To illustrate, let's consider the Cruna protocol. In this system, an NFT owner has the ability to activate what is termed an 'initiator'. This is essentially a secondary wallet with the unique privilege of initiating key transactions. Upon setting an initiator, the token's status is rendered 'locked'. However, this does not impede the token's transferability if the initiation for the transfer comes from the designated initiator. - -## Backwards Compatibility - -This standard is fully backwards compatible with existing [ERC-721](./eip-721.md) contracts. It can be easily integrated into existing contracts and will not cause any conflicts or disruptions. - -## Reference Implementation - -An example implementation is located in the [assets](../assets/eip-6982) directory. - -It solves a specific use case: token's owners losing the ownership when staking the asset in a pool. The implementation allow the pool to lock the asset, leaving the ownership to the owner. In the [README](../assets/eip-6982/README.md) you can find more details about how to compile and test the contracts. - -## Security Considerations - -This EIP does not introduce any known security considerations. However, as with any smart contract standard, it is crucial to employ rigorous security measures in the implementation of this interface. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6982.md diff --git a/EIPS/eip-6997.md b/EIPS/eip-6997.md index 3c66b382e70621..1db6945c1aaa56 100644 --- a/EIPS/eip-6997.md +++ b/EIPS/eip-6997.md @@ -1,381 +1 @@ ---- -eip: 6997 -title: ERC-721 with transaction validation step. -description: A new validation step for transfer and approve calls, achieving a security step in case of stolen wallet. -author: Eduard López i Fina (@eduardfina) -discussions-to: https://ethereum-magicians.org/t/erc721-with-a-validation-step/14071 -status: Review -type: Standards Track -category: ERC -created: 2023-05-07 -requires: 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It defines new validation functionality to avoid wallet draining: every `transfer` or `approve` will be locked waiting for validation. - -## Motivation - -The power of the blockchain is at the same time its weakness: giving the user full responsibility for their data. - -Many cases of NFT theft currently exist, and current NFT anti-theft schemes, such as transferring NFTs to cold wallets, make NFTs inconvenient to use. - -Having a validation step before every `transfer` and `approve` would give Smart Contract developers the opportunity to create secure NFT anti-theft schemes. - -An implementation example would be a system where a validator address is responsible for validating all Smart Contract transactions. - -This address would be connected to a dApp where the user could see the validation requests of his NFTs and accept the correct ones. - -Giving this address only the power to validate transactions would make a much more secure system where to steal an NFT the thief would have to have both the user's address and the validator address simultaneously. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -[ERC-721](./eip-721.md) compliant contracts MAY implement this EIP. - -All the operations that change the ownership of an NFT, like a `transferFrom`/`safeTransferFrom`, SHALL create a `TransferValidation` pending to be validated and emit a `ValidateTransfer`, and SHALL NOT transfer the ownership of an NFT. - -All the operations that enable an approval to manage an NFT, like an `approve`/`setApprovalForAll`, SHALL create an `ApprovalValidation` pending to be validated and emit a `ValidateApproval`, and SHALL NOT enable an approval. - -When the transfer is called by an approved account and not the owner, it MUST be executed directly without the need for validation. This is in order to adapt to all current marketplaces that require approve to directly move your NFTs. - -When validating a `TransferValidation` or `ApprovalValidation` the valid field MUST be set to true and MUST NOT be validated again. - -The operations that validate a `TransferValidation` SHALL change the ownership of the NFT or enable the approval. - -The operations that validate an `ApprovalValidation` SHALL enable the approval. - -### Contract Interface - -```solidity - interface IERC6997 { - - struct TransferValidation { - // The address of the owner. - address from; - // The address of the receiver. - address to; - // The token Id. - uint256 tokenId; - // Whether is a valid transfer. - bool valid; - } - - struct ApprovalValidation { - // The address of the owner. - address owner; - // The approved address. - address approve; - // The token Id. - uint256 tokenId; - // Wether is a total approvement. - bool approveAll; - // Whether is a valid approve. - bool valid; - } - - /** - * @dev Emitted when a new transfer validation has been requested. - */ - event ValidateTransfer(address indexed from, address to, uint256 indexed tokenId, uint256 indexed transferValidationId); - - /** - * @dev Emitted when a new approval validation has been requested. - */ - event ValidateApproval(address indexed owner, address approve, uint256 tokenId, bool indexed approveAll, uint256 indexed approvalValidationId); - - /** - * @dev Returns true if this contract is a validator ERC721. - */ - function isValidatorContract() external view returns (bool); - - /** - * @dev Returns the transfer validation struct using the transfer ID. - * - */ - function transferValidation(uint256 transferId) external view returns (TransferValidation memory); - - /** - * @dev Returns the approval validation struct using the approval ID. - * - */ - function approvalValidation(uint256 approvalId) external view returns (ApprovalValidation memory); - - /** - * @dev Return the total amount of transfer validations created. - * - */ - function totalTransferValidations() external view returns (uint256); - - /** - * @dev Return the total amount of transfer validations created. - * - */ - function totalApprovalValidations() external view returns (uint256); -} - ``` - -The `isValidatorContract()` function MUST be implemented as `public`. - -The `transferValidation(uint256 transferId)` function MAY be implemented as `public` or `external`. - -The `approvalValidation(uint256 approveId)` function MAY be implemented as `public` or `external`. - -The `totalTransferValidations()` function MAY be implemented as `pure` or `view`. - -The `totalApprovalValidations()` function MAY be implemented as `pure` or `view`. - -## Rationale - -### Universality - -The standard only defines the validation functions, but not how they should be used. It defines the validations as internal and lets the user decide how to manage them. - -An example could be to have an address validator connected to a dApp so that users could manage their validations. - -This validator could be used for all NFTs or only for some users. - -It could also be used as a wrapped Smart Contract for existing ERC-721, allowing 1/1 conversion with existing NFTs. - -### Extensibility - -This standard only defines the validation function, but does not define the system with which it has to be validated. A third-party protocol can define how it wants to call these functions as it wishes. - -## Backwards Compatibility - -This standard is an extension of [ERC-721](./eip-721.md), compatible with all the operations except `transferFrom`/`safeTransferFrom`/`approve`/`setApprovalForAll`. - -This operations will be overridden to create a validation petition instead of transfer ownership of an NFT or enable an approval. - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC6997.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @dev Implementation of ERC6997 - */ -contract ERC6997 is IERC6997, ERC721 { - - // Mapping from transfer ID to transfer validation - mapping(uint256 => TransferValidation) private _transferValidations; - - // Mapping from approval ID to approval validation - mapping(uint256 => ApprovalValidation) private _approvalValidations; - - // Total number of transfer validations - uint256 private _totalTransferValidations; - - // Total number of approval validations - uint256 private _totalApprovalValidations; - - /** - * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. - */ - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){ - } - - /** - * @dev Returns true if this contract is a validator ERC721. - */ - function isValidatorContract() public pure returns (bool) { - return true; - } - - /** - * @dev Returns the transfer validation struct using the transfer ID. - * - */ - function transferValidation(uint256 transferId) public view override returns (TransferValidation memory) { - require(transferId < _totalTransferValidations, "ERC6997: invalid transfer ID"); - TransferValidation memory v = _transferValidation(transferId); - - return v; - } - - /** - * @dev Returns the approval validation struct using the approval ID. - * - */ - function approvalValidation(uint256 approvalId) public view override returns (ApprovalValidation memory) { - require(approvalId < _totalApprovalValidations, "ERC6997: invalid approval ID"); - ApprovalValidation memory v = _approvalValidation(approvalId); - - return v; - } - - /** - * @dev Return the total amount of transfer validations created. - * - */ - function totalTransferValidations() public view override returns (uint256) { - return _totalTransferValidations; - } - - /** - * @dev Return the total amount of approval validations created. - * - */ - function totalApprovalValidations() public view override returns (uint256) { - return _totalApprovalValidations; - } - - /** - * @dev Returns the transfer validation of the `transferId`. Does NOT revert if transfer doesn't exist - */ - function _transferValidation(uint256 transferId) internal view virtual returns (TransferValidation memory) { - return _transferValidations[transferId]; - } - - /** - * @dev Returns the approval validation of the `approvalId`. Does NOT revert if transfer doesn't exist - */ - function _approvalValidation(uint256 approvalId) internal view virtual returns (ApprovalValidation memory) { - return _approvalValidations[approvalId]; - } - - /** - * @dev Validate the transfer using the transfer ID. - * - */ - function _validateTransfer(uint256 transferId) internal virtual { - TransferValidation memory v = transferValidation(transferId); - require(!v.valid, "ERC6997: the transfer is already validated"); - - address from = v.from; - address to = v.to; - uint256 tokenId = v.tokenId; - - super._transfer(from, to, tokenId); - - _transferValidations[transferId].valid = true; - } - - /** - * @dev Validate the approval using the approval ID. - * - */ - function _validateApproval(uint256 approvalId) internal virtual { - ApprovalValidation memory v = approvalValidation(approvalId); - require(!v.valid, "ERC6997: the approval is already validated"); - - if(!v.approveAll) { - require(v.owner == ownerOf(v.tokenId), "ERC6997: The token have a new owner"); - super._approve(v.approve, v.tokenId); - } - else { - super._setApprovalForAll(v.owner, v.approve, true); - } - - _approvalValidations[approvalId].valid = true; - } - - /** - * @dev Create a transfer petition of `tokenId` from `from` to `to`. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * - * Emits a {TransferValidate} event. - */ - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - require(ERC721.ownerOf(tokenId) == from, "ERC6997: transfer from incorrect owner"); - require(to != address(0), "ERC6997: transfer to the zero address"); - - if(_msgSender() == from) { - TransferValidation memory v; - - v.from = from; - v.to = to; - v.tokenId = tokenId; - - _transferValidations[_totalTransferValidations] = v; - - emit ValidateTransfer(from, to, tokenId, _totalTransferValidations); - - _totalTransferValidations++; - } else { - super._transfer(from, to, tokenId); - } - } - - /** - * @dev Create an approval petition from `to` to operate on `tokenId` - * - * Emits an {ValidateApproval} event. - */ - function _approve(address to, uint256 tokenId) internal override virtual { - ApprovalValidation memory v; - - v.owner = ownerOf(tokenId); - v.approve = to; - v.tokenId = tokenId; - - _approvalValidations[_totalApprovalValidations] = v; - - emit ValidateApproval(v.owner, to, tokenId, false, _totalApprovalValidations); - - _totalApprovalValidations++; - } - - /** - * @dev If approved is true create an approval petition from `operator` to operate on - * all of `owner` tokens, if not remove `operator` from operate on all of `owner` tokens - * - * Emits an {ValidateApproval} event. - */ - function _setApprovalForAll( - address owner, - address operator, - bool approved - ) internal override virtual { - require(owner != operator, "ERC6997: approve to caller"); - - if(approved) { - ApprovalValidation memory v; - - v.owner = owner; - v.approve = operator; - v.approveAll = true; - - _approvalValidations[_totalApprovalValidations] = v; - - emit ValidateApproval(v.owner, operator, 0, true, _totalApprovalValidations); - - _totalApprovalValidations++; - } - else { - super._setApprovalForAll(owner, operator, approved); - } - } -} -``` - -## Security Considerations - -As is defined in the Specification the operations that change the ownership of an NFT or enable an approval to manage the NFT SHALL create a `TransferValidation` or an `ApprovalValidation` pending to be validated and SHALL NOT transfer the ownership of an NFT or enable an approval. - -With this premise in mind, the operations in charge of validating a `TransferValidation` or an `ApprovalValidation` must be protected with the maximum security required by the applied system. - -For example, a valid system would be one where there is a validator address in charge of validating the transactions. - -To give another example, a system where each user could choose his validator address would also be correct. - -In any case, the importance of security resides in the fact that no address can validate a `TransferValidation` or an `ApprovalValidation` without the permission of the chosen system. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-6997.md diff --git a/EIPS/eip-7007.md b/EIPS/eip-7007.md index 1f439f0085ceb9..ab664ed0040084 100644 --- a/EIPS/eip-7007.md +++ b/EIPS/eip-7007.md @@ -1,164 +1 @@ ---- -eip: 7007 -title: Zero-Knowledge AI-Generated Content Token -description: An ERC-721 extension interface for zkML based AIGC-NFTs. -author: Cathie So (@socathie), Xiaohang Yu (@xhyumiracle), Huaizhe Xu (@HuaizheXu), 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: Draft -type: Standards Track -category: ERC -created: 2023-05-10 -requires: 165, 721 ---- - -## Abstract - -The Zero-Knowledge Machine Lerning (zkML) AI-Generated Content (AIGC) non-fungible token (NFT) standard is an extension of the [ERC-721](./eip-721.md) token standard for AIGC. It proposes a set of interfaces for basic interactions and enumerable interactions for AIGC-NFTs. The standard includes a new mint event and a JSON schema for AIGC-NFT metadata. Additionally, it incorporates zkML capabilities to enable verification of AIGC-NFT ownership. In this standard, the `tokenId` is indexed by the `prompt`. - -## Motivation - -The zkML AIGC-NFTs standard aims to extend the existing [ERC-721](./eip-721.md) token standard to accommodate the unique requirements of AI-Generated Content NFTs representing models in a collection. This standard provides interfaces to use zkML to verify whether or not the AIGC data for an NFT is generated from a certain ML model with certain input (prompt). The proposed interfaces allow for additional functionality related to minting, verifying, and enumerating AIGC-NFTs. Additionally, the metadata schema provides a structured format for storing information related to AIGC-NFTs, such as the prompt used to generate the content and the proof of ownership. - -With this standard, model owners can publish their trained model and its ZKP verifier to Ethereum. Any user can claim an input (prompt) and publish the inference task, any node that maintains the model and the proving circuit can perform the inference and proving, then submit the output of inference and the ZK proof for the inference trace into the verifier that is deployed by the model owner. The user that initiates the inference task will own the output for the inference of that model and input (prompt). - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -**Every compliant contract must implement the [ERC-7007](./eip-7007.md), [ERC-721](./eip-721.md), and [ERC-165](./eip-165.md) interfaces.** - -The zkML AIGC-NFTs standard includes the following interfaces: - -`IERC7007`: Defines a mint event and a mint function for minting AIGC-NFTs. It also includes a verify function to check the validity of a combination of prompt and proof using zkML techniques. - -```solidity -pragma solidity ^0.8.18; - -/** - * @dev Required interface of an ERC7007 compliant contract. - * Note: the ERC-165 identifier for this interface is 0x7e52e423. - */ -interface IERC7007 is IERC165, IERC721 { - /** - * @dev Emitted when `tokenId` token is minted. - */ - event Mint( - uint256 indexed tokenId, - bytes indexed prompt, - bytes indexed aigcData, - string uri, - bytes proof - ); - - /** - * @dev Mint token at `tokenId` given `prompt`, `aigcData`, `uri` and `proof`. - * - * Requirements: - * - `tokenId` must not exist.' - * - verify(`prompt`, `aigcData`, `proof`) must return true. - * - * Optional: - * - `proof` should not include `aigcData` to save gas. - */ - function mint( - bytes calldata prompt, - bytes calldata aigcData, - string calldata uri, - bytes calldata proof - ) external returns (uint256 tokenId); - - /** - * @dev Verify the `prompt`, `aigcData` and `proof`. - */ - function verify( - bytes calldata prompt, - bytes calldata aigcData, - bytes calldata proof - ) external view returns (bool success); -} -``` - -Optional Extension: Enumerable - -The **enumeration extension** is OPTIONAL for [ERC-7007](./eip-7007.md) smart contracts. This allows your contract to publish its full list of mapping between `tokenId` and `prompt` and make them discoverable. - -```solidity -pragma solidity ^0.8.18; - -/** - * @title ERC7007 Token Standard, optional enumeration extension - * Note: the ERC-165 identifier for this interface is 0xfa1a557a. - */ -interface IERC7007Enumerable is IERC7007 { - /** - * @dev Returns the token ID given `prompt`. - */ - function tokenId(bytes calldata prompt) external view returns (uint256); - - /** - * @dev Returns the prompt given `tokenId`. - */ - function prompt(uint256 tokenId) external view returns (string calldata); -} -``` - -ERC-7007 Metadata JSON Schema for reference - -```json -{ - "title": "AIGC Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - - "prompt": { - "type": "string", - "description": "Identifies the prompt from which this AIGC NFT generated" - }, - "aigc_type": { - "type": "string", - "description": "image/video/audio..." - }, - "aigc_data": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this AIGC NFT represents." - } - } -} -``` - -## Rationale - -TBD - -## Backwards Compatibility - -This standard is backward compatible with the [ERC-721](./eip-721.md) as it extends the existing functionality with new interfaces. - -## Test Cases - -The reference implementation includes sample implementations of the [ERC-7007](./eip-7007.md) interfaces under `contracts/` and corresponding unit tests under `test/`. This repo can be used to test the functionality of the proposed interfaces and metadata schema. - -## Reference Implementation - -* [ERC-7007](../assets/eip-7007/contracts/ERC7007.sol) -* [ERC-7007 Enumerable Extension](../assets/eip-7007/contracts/ERC7007Enumerable.sol) - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7007.md diff --git a/EIPS/eip-7015.md b/EIPS/eip-7015.md index 3cb425dfb97bcf..ab74f445ad83b8 100644 --- a/EIPS/eip-7015.md +++ b/EIPS/eip-7015.md @@ -1,156 +1 @@ ---- -eip: 7015 -title: NFT Creator Attribution -description: Extending NFTs with cryptographically secured creator attribution. -author: Carlos Flores (@strollinghome) -discussions-to: https://ethereum-magicians.org/t/eip-authorship-attribution-for-erc721/14244 -status: Draft -type: Standards Track -category: ERC -created: 2023-05-11 -requires: 55, 155, 712, 721, 1155 ---- - -## Abstract - -This Ethereum Improvement Proposal aims to solve the issue of creator attribution for Non-Fungible Token (NFT) standards ([ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md)). To achieve this, this EIP proposes a mechanism where the NFT creator signs the required parameters for the NFT creation, including the NFT metadata in a hash along with any other relevant information. The signed parameters and the signature are then validated and emitted during the deployment transaction, which allows the NFT to validate the creator and NFT platforms to attribute creatorship correctly. This method ensures that even if a different wallet sends the deployment transaction, the correct account is attributed as the creator. - -## Motivation - -Current NFT platforms assume that the wallet deploying the smart contract is the creator of the NFT, leading to a misattribution in cases where a different wallet sends the deployment transaction. This happens often when working with smart wallet accounts, and new contract deployment strategies such as the first collector deploying the NFT contract. This proposal aims to solve the problem by allowing creators to sign the parameters required for NFT creation so that any wallet can send the deployment transaction with an signal in a verifiable way who is the creator. - -## Specification - -The keywords “MUST,” “MUST NOT,” “REQUIRED,” “SHALL,” “SHALL NOT,” “SHOULD,” “SHOULD NOT,” “RECOMMENDED,” “MAY,” and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -ERC-721 and ERC-1155 compliant contracts MAY implement this NFT Creator Attribution extension to provide a standard event to be emitted that defines the NFT creator at the time of contract creation. - -This EIP takes advantage of the fact that contract addresses can be precomputed before a contract is deployed. Whether the NFT contract is deployed through another contract (a factory) or through an EOA, the creator can be correctly attributed using this specification. - -**Signing Mechanism** - -Creator consent is given by signing an [EIP-712](./eip-712.md) compatible message; all signatures compliant with this EIP MUST include all fields defined. The struct signed can be any arbitrary data that defines how to create the token; it must hashed in an EIP-712 compatible format with a proper EIP-712 domain. - -The following shows some examples of structs that could be encoded into `structHash` (defined below): - -```solidity -// example struct that can be encoded in `structHash`; defines that a token can be created with a metadataUri and price: - -struct TokenCreation { - string metadataUri; - uint256 price; - uint256 nonce; -} -``` - -**Signature Validation** - -Creator attribution is given through a signature verification that MUST be verified by the NFT contract being deployed and an event that MUST be emitted by the NFT contract during the deployment transaction. The event includes all the necessary fields for reconstructing the signed digest and validating the signature to ensure it matches the specified creator. The event name is `CreatorAttribution` and includes the following fields: - -- `structHash`: hashed information for deploying the NFT contract (e.g. name, symbol, admins etc). This corresponds to the value `hashStruct` as defined in the [EIP-712 definition of hashStruct](./eip-712.md#definition-of-hashstruct) standard. -- `domainName`: the domain name of the contract verifying the singature (for EIP-712 signature validation). -- `version`: the version of the contract verifying the signature (for EIP-712 signature validation) -- `creator`: the creator's account -- `signature`: the creator’s signature - -The event is defined as follows: - -```solidity -event CreatorAttribution( - bytes32 structHash, - string domainName, - string version, - address creator, - bytes signature -); -``` - -Note that although the `chainId` parameters is necessary for [EIP-712](./eip-712.md) signatures, we omit the parameter from the event as it can be inferred through the transaction data. Similarly, the `verifyingContract` parameter for signature verification is omitted since it MUST be the same as the `emitter` field in the transaction. `emitter` MUST be the token. - -A platform can verify the validity of the creator attribution by reconstructing the signature digest with the parameters emitted and recovering the signer from the `signature` parameter. The recovered signer MUST match the `creator` emitted in the event. If `CreatorAttribution` event is present creator and the signature is validated correctly, attribution MUST be given to the `creator` instead of the account that submitted the transaction. - -### Reference Implementation - -#### Example signature validator - -```solidity -pragma solidity 0.8.20; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/interfaces/IERC1271.sol"; - -abstract contract ERC7015 is EIP712 { - error Invalid_Signature(); - event CreatorAttribution( - bytes32 structHash, - string domainName, - string version, - address creator, - bytes signature - ); - - /// @notice Define magic value to verify smart contract signatures (ERC1271). - bytes4 internal constant MAGIC_VALUE = - bytes4(keccak256("isValidSignature(bytes32,bytes)")); - - function _validateSignature( - bytes32 structHash, - address creator, - bytes memory signature - ) internal { - if (!_isValid(structHash, creator, signature)) revert Invalid_Signature(); - emit CreatorAttribution(structHash, "ERC7015", "1", creator, signature); - } - - function _isValid( - bytes32 structHash, - address signer, - bytes memory signature - ) internal view returns (bool) { - require(signer != address(0), "cannot validate"); - - bytes32 digest = _hashTypedDataV4(structHash); - - // if smart contract is the signer, verify using ERC-1271 smart-contract - /// signature verification method - if (signer.code.length != 0) { - try IERC1271(signer).isValidSignature(digest, signature) returns ( - bytes4 magicValue - ) { - return MAGIC_VALUE == magicValue; - } catch { - return false; - } - } - - // otherwise, recover signer and validate that it matches the expected - // signer - address recoveredSigner = ECDSA.recover(digest, signature); - return recoveredSigner == signer; - } -} -``` - -## Rationale - -By standardizing the `CreatorAttribution` event, this EIP enables platforms to ascertain creator attribution without relying on implicit assumptions. Establishing a standard for creator attribution empowers platforms to manage the complex aspects of deploying contracts while preserving accurate onchain creator information. This approach ensures a more reliable and transparent method for identifying NFT creators, fostering trust among participants in the NFT ecosystem. - -[ERC-5375](./eip-5375.md) attempts to solve the same issue and although offchain data offers improved backward compatibility, ensuring accurate and immutable creator attribution is vital for NFTs. A standardized onchain method for creator attribution is inherently more reliable and secure. - -In contrast to this proposal, ERC-5375 does not facilitate specifying creators for all tokens within an NFT collection, which is a prevalent practice, particularly in emerging use cases. - -Both this proposal and ERC-5375 share similar limitations regarding address-based creator attribution: - -> The standard defines a protocol to verify that a certain *address* provided consent. However, it does not guarantee that the address corresponds to the expected creator […]. Proving a link between an address and the entity behind it is beyond the scope of this document. - -## Backwards Compatibility - -Since the standard requires an event to be emitted during the NFTs deployment transaction, existing NFTs cannot implement this standard. - -## Security Considerations - -A potential attack exploiting this proposal could involve deceiving creators into signing creator attribution consent messages unintentionally. Consequently, creators MUST ensure that all signature fields correspond to the necessary ones before signing. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7015.md diff --git a/EIPS/eip-7053.md b/EIPS/eip-7053.md index 1d0900906e58d6..77e4881308438b 100644 --- a/EIPS/eip-7053.md +++ b/EIPS/eip-7053.md @@ -1,117 +1 @@ ---- -eip: 7053 -title: Interoperable Digital Media Indexing -description: A universal indexing method to record, discover and retrieve the history of digital media on EVM-compatible blockchains. -author: Bofu Chen (@bafu), Tammy Yang (@tammyyang) -discussions-to: https://ethereum-magicians.org/t/eip-7053-interoperable-digital-media-indexing/14394 -status: Final -type: Standards Track -category: ERC -created: 2023-05-22 ---- - -## Abstract - -This EIP proposes an interoperable indexing strategy designed to enhance the organization and retrieval of digital media information across multiple smart contracts and EVM-compatible blockchains. This system enhances the traceability and verification of cross-contract and cross-chain data, facilitating a more efficient discovery of storage locations and crucial information related to media assets. The major purpose is to foster an integrated digital media environment on the blockchain. - -## Motivation - -Given the significant role digital media files play on the Internet, it's crucial to have a robust and efficient method for indexing immutable information. Existing systems encounter challenges due to the absence of a universal, interoperable identifier for digital media content. This leads to fragmentation and complications in retrieving metadata, storage information, or the provenance of specific media assets. The issues become increasingly critical as the volume of digital media continues to expand. - -The motivation behind this EIP is to establish a standardized, decentralized, and interoperable approach to index digital media across EVM-compatible networks. By integrating Decentralized Content Identifiers (CIDs) and Commit events, this EIP puts forward a mechanism enabling unique identification and indexing of each digital media file. Moreover, this system suggests a way for users to access a complete history of data associated with digital media assets, from creation to the current status. This full view enhances transparency, thereby providing users with the necessary information for future interactions with digital media. - -This method creates a common interface that any digital media system can use to provide a standard way of indexing and searching their content. - -|| -|:--:| -| ![](../assets/eip-7053/digital-media-indexing-system-and-metadata-lookup.jpg) | -| Figure 1: Digital Media Indexing Relationships and Lookup | - -This EIP aims to create an interoperable indexing system to associate all data of the same digital content together (Figure 1). This will make it easier for users to find and trust digital media content, and it will also make it easier for systems to share and exchange information about this digital media content. - -## 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. - -### Content Identifier - -Content Identifier in this EIP is the the content address generated by passing the content of a digital media through a cryptographic hash function. Before the indexing process for digital media can begin, it is REQUIRED to generate unique Content Identifiers for each file. This identifier should the same as the Content Identifiers on the decentralized storage, ensuring each identifier provides access to the metadata, media information, and the content file itself. - -### Commit Function - -To index digital media, we shall call the commit function and generate Commit event: - -```solidity -/** - * @notice Emitted when a new commit is made. - * @param recorder The address of the account making the commit. - * @param assetCid The content identifier of the asset being committed. - * @param commitData The data associated with the commit. - */ -event Commit(address indexed recorder, string indexed assetCid, string commitData); - -/** - * @notice Registers a commit for an asset. - * Emits a Commit event and records the block number of the commit in the recordLogs mapping for the provided assetCid. - * @dev Emits a Commit event and logs the block number of the commit event. - * @param assetCid The content identifier of the asset being committed. - * @param commitData The data associated with the commit. - * @return The block number at which the commit was made. - */ -function commit(string memory assetCid, string memory commitData) public returns (uint256 blockNumber); -``` - -## Rationale - -The design decisions in this EIP prioritize the effectiveness and efficiency of the indexing method. To achieve this, Decentralized Content Identifiers (CIDs) are utilized to uniquely identify digital media content across all systems. This approach offers accurate and precise searching of media, along with the following benefits: - -1. Strengthened data integrity: CIDs serve as cryptographic hashes of the content, ensuring their uniqueness and preventing forgery. With the content in hand, obtaining the CID allows for searching relevant information associated with that content. - -2. Streamlined data portability: CIDs enable the seamless transfer of digital media content across different systems, eliminating the need for re-encoding or reconfiguration of protocols. This promotes a more interoperable and open indexing system. For example, in cases where Non-Fungible Tokens (NFTs) are created prior to Commit events, the digital media content can still be indexed by converting the file referenced by the tokenURI using the same mechanism. This conversion process ensures that the digital media content associated with NFT tokens can be indexed with a consistent identification approach. - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; - -contract CommitRegister is Initializable { - using ECDSA for bytes32; - - mapping(string => uint[]) public commitLogs; - - event Commit(address indexed recorder, string indexed assetCid, string commitData); - - function initialize() public initializer {} - - function commit(string memory assetCid, string memory commitData) public returns (uint256 blockNumber) { - emit Commit(msg.sender, assetCid, commitData); - commitLogs[assetCid].push(block.number); - return block.number; - } - - function getCommits(string memory assetCid) public view returns (uint[] memory) { - return commitLogs[assetCid]; - } -} -``` - -## Security Considerations - -When implementing this EIP, it's essential to address several security aspects to ensure the safety and integrity of the digital media index: - -1. Input Validation: Given that commit function accepts string parameters, it's important to validate these inputs to avoid potential injection attacks. Although such attacks are less common in smart contracts than traditional web development, caution should be exercised. - -2. Data Integrity: The commit function relies on CIDs, which are assumed to be correct and point to the right data. It's important to note that this EIP doesn't validate the content behind the CIDs and the commit data, which remains a responsibility of the users or implementing applications. - -3. Event Listening: Systems relying on listening to the Commit events for changes need to be aware of potential missed events or incorrect ordering, especially during periods of network congestion or reorganizations. - -Implementers should consider these security aspects in the context of their specific use case and deployment scenario. It is strongly recommended to perform a comprehensive security audit before deploying any implementation of this EIP to a live network. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7053.md diff --git a/EIPS/eip-7066.md b/EIPS/eip-7066.md index e6910b8d1b43bf..fcda829afd5476 100644 --- a/EIPS/eip-7066.md +++ b/EIPS/eip-7066.md @@ -1,156 +1 @@ ---- -eip: 7066 -title: Lockable Extension for ERC-721 -description: Interface for enabling locking of ERC-721 using locker and approved -author: Piyush Chittara (@piyush-chittara), StreamNFT (@streamnft-tech), Srinivas Joshi (@SrinivasJoshi) -discussions-to: https://ethereum-magicians.org/t/eip-7066-lockable-extension-for-erc721/14425 -status: Review -type: Standards Track -category: ERC -created: 2023-05-25 -requires: 165, 721 ---- - -## Abstract - -An extension of [ERC-721](./eip-721.md), this standard incorporates `locking` features into NFTs, allowing for various uses while preventing sale or transfer. The token's `owner` can `lock` it, setting up locker address (either an EOA or a contract) that exclusively holds the power to unlock the token. Owner can also provide approval for tokenId, enabling ability to lock asset while address holds the token approval. Token can also be locked by `approved`, assigning locker to itself. Upon token transfer, these rights get purged. - -## Motivation - -[ERC-721](./eip-721.md) has sparked an unprecedented surge in demand for NFTs. However, despite this tremendous success, NFT economy suffers from secondary liquidity where it remains Illiquid in owner’s wallet. There are projects such as NFTfi, Paraspace which aims to address the liquidity challenge, but they entail below mentioned inconveniences and risks for owners as they necessitate transferring the participating NFTs to the projects' contracts. - -- Loss of utility: The utility value of NFTs diminishes when they are transferred to an escrow account, no longer remaining under the direct custody of the owners. -- Lack of composability: The market could benefit from increased liquidity if NFT owners had access to multiple financial tools, such as leveraging loans and renting out their assets for maximum returns. Composability serves as the missing piece in creating a more efficient market. -- Smart contract vulnerabilities: NFTs are susceptible to loss or theft due to potential bugs or vulnerabilities present in the smart contracts they rely on. - -The aforementioned issues contribute to a poor user experience (UX), and we propose enhancing the [ERC-721](./eip-721.md) standard by implementing a native locking mechanism: -Rather than being transferred to a smart contract, an NFT remains securely stored in self-custody but is locked. -During the lock period, the NFT's transfer is restricted while its other properties remain unchanged. -NFT Owner retains the ability to use or distribute it’s utility - -NFTs have numerous use cases where the NFT must remain within the owner's wallet, even when it serves as collateral for a loan. Whether it's authorizing access to a Discord server, or utilizing NFT within a play-to-earn (P2E) game, owner should have the freedom to do so throughout the lending period. Just as real estate owner can continue living in their mortgaged house, take personal loan or keep tenants to generate passive income, these functionalities should be available to NFT owners to bring more investors in NFT economy. - - -Lockable NFTs enable the following use cases : - -- NFT-collateralized loans: Utilize NFT as collateral for a loan without locking it on the lending protocol contract. Instead, lock it within owner’s wallet while still enjoying all the utility of NFT. -- No collateral rentals of NFTs: Borrow an NFT for a fee without the need for significant collateral. Renter can use the NFT but not transfer it, ensuring the lender's safety. The borrowing service contract automatically returns the NFT to the lender once the borrowing period expires. -- Buy Now Pay Later: The buyer receives the locked NFT and can immediately begin using it. However, they are unable to sell the NFT until all installments are paid. Failure to complete the full payment results in the NFT returning to the seller, along with a fee. -- Composability: Maximize liquidity by having access to multiple financial tools. Imagine taking a loan against NFT and putting it on rentals to generate passive income. -- Primary sales: Mint an NFT for a partial payment and settle the remaining amount once owner is satisfied with the collection's progress. -- Soulbound: Organization can mint and self-assign `locker`, send token to user and lock the asset. -- Safety: Safely and conveniently use exclusive blue chip NFTs. Lockable extension allows owner to lock NFT and designate secure cold wallet as the unlocker. This way, owner can keep NFT on MetaMask and easily use it, even if a hacker gains access to MetaMask account. Without access to the cold wallet, the hacker cannot transfer NFT, ensuring its safety. - -This proposal is different from other locking proposals in number of ways: - -- This implementation provides a minimal implementation of `lock` and `unlock` and believes other conditions like time-bound are great ideas but can be achieved without creating a specific implementation. Locking and Unlocking can be based on any conditions (e.g. repayment, expiry). Therefore time-bound unlocks a relatively specific use case that can be achieved via smart-contracts themselves without that being a part of the token contract. -- This implementation proposes a separation of rights between locker and approver. Token can be locked with approval and approved can unlock and withdraw tokens (opening up opportunities like renting, lending, bnpl etc), and token can be locked lacking the rights to revoke token, yet can unlock if required (opening up opportunities like account-bound NFTs). -- Our proposal implement ability to `transferAndLock` which can be used to transfer, lock and optionally approve token. Enabling the possibility of revocation after transfer. - -By extending the [ERC-721](./eip-721.md) standard, the proposed standard enables secure and convenient management of underlying NFT assets. It natively supports prevalent NFTFi use cases such as staking, lending, and renting. We anticipate that this proposed standard will foster increased engagement of NFT owners in NFTFi projects, thereby enhancing the overall vitality of the NFT ecosystem. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Overview - -[ERC-721](./eip-721.md) compliant contracts MAY implement this EIP to provide standard methods of locking and unlocking the token at its current owner address. - -Token owner MAY `lock` the token and assign `locker` to some `address` using `lock(uint256 tokenId, address _locker)` function, this MUST set `locker` to `_locker`. Token owner or approved MAY `lock` the token using `lock(uint256 tokenId)` function, this MUST set `locker` to `msg.sender`. Token MAY be `unlocked` by `locker` using `unlock` function. `unlock` function MUST delete `locker` mapping and default to `address(0)`. - -If the token is `locked`, the `lockerOf` function MUST return an address that is `locker` and can `unlock` the token. For tokens that are not `locked`, the `lockerOf` function MUST return `address(0)`. - -`lock` function MUST revert if token is not already locked. `unlock` function MUST revert if token is not locked. ERC-721 `approve` function MUST revert if token is locked. ERC-721 `_tansfer` function MUST revert if token is locked. ERC-721 `_transfer` function MUST pass if token is locked and `msg.sender` is `approved` and `locker` both. After ERC-721 `_transfer`, values of `locker` and `approved` MUST be purged. - -Token MAY be transferred and `locked`, and OPTIONAL setup `approval` to `locker` using `transferAndLock` function. This is RECOMMENDED for use-cases where Token transfer and subsequent revocation is REQUIRED. - -### Interface - -``` -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.7.0 <0.9.0; - -/// @title Lockable Extension for ERC721 -/// @dev Interface for the Lockable extension -/// @author StreamNFT - -interface IERC7066{ - - /** - * @dev Emitted when tokenId is locked - */ - event Lock (uint256 indexed tokenId, address _locker); - - /** - * @dev Emitted when tokenId is unlocked - */ - event Unlock (uint256 indexed tokenId); - - /** - * @dev Lock the tokenId if msg.sender is owner or approved and set locker to msg.sender - */ - function lock(uint256 tokenId) external; - - /** - * @dev Lock the tokenId if msg.sender is owner and set locker to _locker - */ - function lock(uint256 tokenId, address _locker) external; - - /** - * @dev Unlocks the tokenId if msg.sender is locker - */ - function unlock(uint256 tokenId) external; - - /** - * @dev Tranfer and lock the token if the msg.sender is owner or approved. - * Lock the token and set locker to caller - * Optionally approve caller if bool setApprove flag is true - */ - function transferAndLock(uint256 tokenId, address from, address to, bool setApprove) external; - - /** - * @dev Returns the wallet, that is stated as unlocking wallet for the tokenId. - * If address(0) returned, that means token is not locked. Any other result means token is locked. - */ - function lockerOf(uint256 tokenId) external view returns (address); -} -``` - -## Rationale - -This proposal set `locker[tokenId]` to `address(0)` when token is `unlocked` because we delete mapping on `locker[tokenId]` freeing up space. Also, this assertion helps our contract to validate if token is `locked` or `unlocked` for internal function calls. - -This proposal exposes `transferAndLock(uint256 tokenId, address from, address to, bool setApprove)` which can be used to transfer token and lock at the receiver's address. This additionally accepts input `bool setApprove` which on `true` assign `approval` to `locker`, hence enabling `locker` to revoke the token (revocation conditions can be defined in contracts and `approval` provided to contract). This provides conditional ownership to receiver, without the privilege to `transfer` token. - -## Backwards Compatibility - -This standard is compatible with [ERC-721](./eip-721.md) standards. - -Existing Upgradedable [ERC-721](./eip-721.md) can upgrade to this standard, enabling locking capability inherently and unlock underlying liquidity features. - -## Test Cases - -Test cases can be found [here](../assets/eip-7066/test/test.js). - -## Reference Implementation - -Reference Interface can be found [here](../assets/eip-7066/IERC7066.sol). - -Reference Implementation can be found [here](../assets/eip-7066/ERC7066.sol). - -## Security Considerations - -There are no security considerations related directly to the implementation of this standard for the contract that manages [ERC-721](./eip-721.md). - -### Considerations for the contracts that work with lockable tokens - -- Once `locked`, token can not be further `approved` or `transfered`. -- If token is `locked` and caller is `locker` and `appoved` both, caller can transfer the token. -- `locked` token with `locker` as in-accesible account or un-verified contract address can lead to permanent lock of the token. -- There are no MEV considerations regarding lockable tokens as only authorized parties are allowed to lock and unlock. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7066.md diff --git a/EIPS/eip-7085.md b/EIPS/eip-7085.md index 3b752fec9d7e1c..9deb70118af61b 100644 --- a/EIPS/eip-7085.md +++ b/EIPS/eip-7085.md @@ -1,222 +1 @@ ---- -eip: 7085 -title: NFT Relationship Enhancement -description: Establish relationships between NFTs and setting quantifiable attributes for those relationships. -author: Guang (@xg1990) -discussions-to: https://ethereum-magicians.org/t/introducing-new-eip-nft-relationship-standard/14468 -status: Draft -type: Standards Track -category: ERC -created: 2023-05-02 -requires: 721, 1155 ---- - - -## Abstract - -This proposal builds on [ERC-1155](./eip-1155.md) and creates a standard for referring relationships and quantifiable attributes between non-isolated [ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md) non-fungible tokens (NFTs). It enables users to build a graph of NFTs and set quantifiable attributes for each NFT, facilitating more complex NFT ecosystems. While a similar proposal exists for [ERC-721](./eip-721.md) tokens, it does not provide a way to establish quantifiable relationships or object attributes. - -## Motivation - -The current standard for NFTs lacks the ability to establish relationships and attributes between tokens. This limitation makes it difficult for users to build more complex NFT ecosystems that require referring relationships and quantifiable attributes between tokens. For example, a user may create a derivative NFT that refers to the original NFT and sets a quantifiable attribute for the relationship between the two NFTs, but without a standardized way to establish relationships and attributes between NFTs, managing these ecosystems becomes increasingly difficult and inefficient. - -This proposal aims to address this issue by extending the [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) standards to include the ability to establish referring relationships and quantifiable attributes between NFTs. - -By enabling users to build more complex NFT ecosystems, this proposal will enhance the NFT ecosystem and open up new possibilities for NFT use cases. However, it's important to consider potential drawbacks such as increased complexity and gas cost, and carefully design rules to mitigate these issues. - -## Specification - -This EIP proposes the addition of five new functions to the [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) standards: `setRelationship`, `setAttribute`, `getRelationship`, `getAttribute`, and `getAttributeNames`. These functions allow users to establish referring relationships and set quantifiable attributes between NFTs. - -### `setRelationship` - -The `setRelationship` function establishes a referring relationship between two NFTs. It takes the following parameters: - -```solidity -function setRelationship(uint256 _originalID, uint256 _derivativeID, uint256 _attribute) external; -``` - -- `_originalID`: the ID of the original NFT -- `_derivativeID`: the ID of the derivative NFT that refers to the original NFT -- `_attribute`: the quantifiable attribute for this relationship, which defaults to 1 if not specified - -When called, this function establishes a referring relationship between the two NFTs. - -### `setAttribute` - -The `setAttribute` function sets a quantifiable attribute for an NFT. It takes the following parameters: - -```solidity -function setAttribute(uint256 _id, string calldata _name, uint256 _value) external; -``` - -- `_id`: the ID of the NFT -- `_name`: the name of the attribute to be set -- `_value`: the value of the attribute to be set - -When called, this function sets a quantifiable attribute for the NFT. - -### `getAttribute` - -The `getAttribute` function allows anyone to retrieve the value of a specific attribute associated with an NFT. It takes the following parameters: - -```solidity -function getAttribute(uint256 _id, string calldata _name) external view returns (bytes32); -``` - -- `_id`: The ID of the NFT for which you want to retrieve the attribute. -- `_name`: The name of the attribute you wish to retrieve. - -This function returns the value of the specified attribute as a bytes32 data type. - -### `getAttributeNames` - -The getAttributeNames function allows anyone to retrieve the names of all attributes associated with an NFT. It takes the following parameter: - -```solidity -function getAttributeNames(uint256 _id) external view returns (bytes32[] memory); -``` - -- `_id`: The ID of the NFT for which you want to retrieve the attribute names. - -This function returns an array of bytes32 values representing the names of all attributes associated with the specified NFT. - -### `getRelationship` - -The `getRelationship` function allows anyone to retrieve the value of a referring relationship between two NFTs. It takes the following parameters: - -```solidity -function getRelationship(uint256 _originalID, uint256 _derivativeID) external view returns (uint256); -``` - -- `_originalID`: The ID of the original NFT. -- `_derivativeID`: The ID of the derivative NFT that refers to the original NFT. - -This function returns the value of the referring relationship between the two NFTs as a uint256 data type. - -### Example Usage - -```solidity -NFTGraph nftContract = NFTGraph(addressOfContract); - -// Retrieve the value of an attribute named "Color" for NFT with ID 123 -bytes32 colorValue = nftContract.getAttribute(123, "Color"); - -// Retrieve the names of all attributes associated with NFT with ID 456 -bytes32[] memory attributeNames = nftContract.getAttributeNames(456); -``` - -By including these functions and methods in the specification, you establish a clear and standardized way for users and developers to read attributes associated with NFTs. - -## Rationale - -In developing this EIP, some key design decisions were made. For example, we limited the complexity of the relationship graph that can be created by only allowing for one referring relationship between two NFTs. This helps to ensure that the graph remains manageable and does not become too complex to be useful. Additionally, we kept the gas cost of setting attributes to a minimum by only allowing for one attribute to be set at a time. - -While there are currently no similar features in other blockchain languages or standards, we drew inspiration from the concept of Graph Theory, which is a branch of mathematics that studies the relationships between objects. By adding the ability to establish relationships between NFTs and set quantifiable attributes for those relationships, we believe that the extended NFT standard will become even more useful and versatile for NFT creators and users. - -## Backwards Compatibility - -This EIP is designed to be fully backward-compatible with existing [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) contracts and tokens. Existing NFT contracts and tokens will continue to function as they did before, and the new `setRelationship` and `setAttribute` functions will only be available to contracts that explicitly implement this EIP. - -## Reference Implementation - -To assist in understanding and implementing this proposal, we provide a reference Solidity interface and contract that define the functions for establishing relationships and reading attributes. Developers can use this interface as a foundation for integrating the NFT Relationship Enhancement into their own contracts. - -### [ERC-165](./eip-165.md) Interface Support - -The NFT Relationship Enhancement contract implements the ERC-165 standard interface to allow for interface detection. This enables smart contracts and applications to check if a given contract supports the functions defined in this proposal before interacting with it. - -### INFTGraph Interface - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC165/IERC165.sol"; // Import IERC165 for interface detection - -interface INFTGraph is IERC165 { - // setRelationship: Establishes relationships between NFTs. - function setRelationship(uint256 _originalID, uint256 _derivativeID, uint256 _attribute) external; - // setAttribute: Sets quantifiable attributes for NFTs. - function setAttribute(uint256 _id, string calldata _name, uint256 _value) external; - // getRelationship: Retrieves relationship values between NFTs. - function getRelationship(uint256 _originalID, uint256 _derivativeID) external view returns (uint256); - // getAttribute: Retrieves the value of specific attributes associated with NFTs. - function getAttribute(uint256 _id, string calldata _name) external view returns (bytes32); - // getAttributeNames: Retrieves all attribute names associated with an NFT. - function getAttributeNames(uint256 _id) external view returns (bytes32[] memory); -} -``` - -The INFTGraph interface specifies the functions for setting relationships and attributes, as well as retrieving attribute information and relationship values. - -### NFTGraph Contract - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/introspection/ERC165.sol"; // Import ERC165 for interface detection - -import "./INFTGraph.sol"; // Import INFTGraph interface - -contract NFTGraph is INFTGraph{ - mapping(uint256 => mapping(uint256 => uint256)) public relationship; - mapping(uint256 => mapping(bytes32 => bytes32)) public attributes; - - // Implement the setRelationship and setAttribute functions as described in the EIP specification. - - - // Implement the supportsInterface function for ERC-165. - function supportsInterface(bytes4 interfaceID) public view override returns (bool) { - return interfaceID == type(INFTGraph).interfaceId || super.supportsInterface(interfaceID); - } - - // Additional implementation details... - function getRelationship(uint256 _originalID, uint256 _derivativeID) external view returns (uint256) { - return relationship[_originalID][_derivativeID]; - } - - function getAttribute(uint256 _id, string calldata _name) external view returns (bytes32) { - return bytes32(attributes[_id][_name]); - } - - function getAttributeNames(uint256 _id) external view returns (bytes32[] memory) { - bytes32[] memory names = new bytes32[](attributes[_id].length); - for (uint256 i = 0; i < attributes[_id].length; i++) { - names[i] = bytes32(attributes[_id][i]); - } - return names; - } - - function setRelationship(uint256 originalNFT, uint256 derivativeNFT, uint256 relationshipValue) public { - require(originalNFT != derivativeNFT, "Original and derivative NFTs must be different"); - relationship[originalNFT][derivativeNFT] = relationshipValue; - } - - function setAttribute(uint256 nft, bytes32 attributeName, bytes32 attributeValue) public { - attributes[nft][attributeName] = attributeValue; - } - -} -``` - -The NFTGraph contract implements the functions specified in the INFTGraph interface and provides storage for relationships and attributes. - -Developers can use this reference interface and contract as a starting point for integrating the NFT Relationship Enhancement functionality into their own projects. -The interface provides a clear and standardized way to interact with the contract, promoting consistency and ease of integration. - -## Security Considerations - -When implementing this proposal, contract developers should consider the following security aspects: - -1. **Validation of Relationships**: Contracts utilizing the setRelationship function must ensure that the relationships being established are valid and authorized by the relevant parties. Unauthorized or malicious relationships could lead to unintended consequences. -2. **Attribute Validation**: Contracts implementing the setAttribute function should carefully validate attributes to prevent malicious or harmful values. Invalid or unvalidated attributes could disrupt the functionality of the NFT ecosystem. -3. **Access Control**: Contracts should implement appropriate access control mechanisms to restrict who can call critical functions, especially those that modify relationships or attributes. Unauthorized access can lead to misuse or exploitation. -4. **Reentrancy Protection**: Consider adding reentrancy protection mechanisms to functions that modify relationships or attributes. Reentrancy attacks could otherwise be exploited to manipulate contract behavior. - -By addressing these considerations, developers can enhance the security of their contracts and protect the integrity of the NFT ecosystem. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7085.md diff --git a/EIPS/eip-7092.md b/EIPS/eip-7092.md index 77d2216e214310..c39b0a63024a78 100644 --- a/EIPS/eip-7092.md +++ b/EIPS/eip-7092.md @@ -1,412 +1 @@ ---- -eip: 7092 -title: Financial Bonds -description: This interface defines a specification for debt issued by corporations, goverments or other entities to investors in order to raise funds. -author: Samuel Gwlanold Edoumou (@Edoumou) -discussions-to: https://ethereum-magicians.org/t/financial-bonds/14461 -status: Draft -type: Standards Track -category: ERC -created: 2023-05-28 ---- - -## Abstract - -This proposal introduces fixed income financial bonds. Important bond characteristics such as the International Securities Identification Number (ISIN), -the issue volume, the issue date, the maturity date, the coupon rate, the coupon frequency, the principal, or the day count basis are defined to allow issuing -bonds in the primary market (origination), and different transfer functions allow to buy or sell bonds in the secondary market. The standard also provides a -functionality to allow bonds to be approved by owners in order to be spent by third party. - -## Motivation - -Fixed income instruments is one of the asset classes that is widely used by corporations and other entities to raise funds. Bonds -are considered more secured than equity since the issuer is supposed to repay the principal at maturity in addition to coupons -that are paid to investsors. - -This standard interface allows fixed income instruments to be represented as on-chain tokens, so as they can be managed through wallets, -and be used by applications like decentrailized exchanges. - -The existing standard [ERC-3475](./eip-3475) that may be used to create abstract storage bonds does not follow same standards as -with traditional bonds. By introducing concepts such as **classes** and **nonces** that are not used for traditional bonds, the ERC-3475 standard makes it difficult for -traditional entities to migrate to tokenized bonds. Morever, the use of on-chain metadata with ERC-3475, like **classMetadata** and **nonceMetadata**, and also **classValues** -leads to unnecessary gas consumption. The lack of named variables like coupon, maturity date, principal, etc... makes it difficult to implement the ERC-3475 since developers -need to rember which metadata is assigned to each parameter. - -By keeping same standards as with traditional bonds, the [ERC-7092](./eip-7092.md) allows to create a new token for bonds with same caracteristics as traditional bonds so as to make it simple -to migrate to tokenized bonds. - -Tokenizing bonds will offer several advantages compared to traditional bond issuance and trading, among with: - -1. Fractional ownership: The bond standard does not limit the bond denomination to some minimum value compared to traditioanal bonds where the denomination is typically equal to $100 or $1,000. -2. Accessibility: By allowing lower investment thresholds, tokenized bonds are supposed to attract retail investors who could not participate in traditional markets due to high minimum investment requirements. -3. Increased liquidity: Fractioanal ownership will bring new investors in the bond market, this will increase liquidity in the bond market. -4. Cost savings: By replacing intermediaries with smart contracts, bond's tokenization will reduce costs associated with the bond issuance and management. -5. Easy accessibility and 24/7 trading: Tokenized bonds are supposed to be traded on digital platforms such as decentralized exchanges. Therefore, they will be more accessible compared to tradional bond market. - -## 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. - -**Every contract compliant with the [ERC-7092](./eip-7092.md) MUST implement the following interface** - -```solidity -pragma solidity ^0.8.0; - -/** -* @title ERC-7092 Financial Bonds tandard -*/ -interface IERC7092 { - /** - * @notice Returns the bond isin - */ - function isin() external view returns(string memory); - - /** - * @notice Returns the bond name - */ - function name() external view returns(string memory); - - /** - * @notice Returns the bond symbol - * It is RECOMMENDED to represent the symbol as a combination of the issuer Issuer'shorter name and the maturity date - * Ex: If a company named Green Energy issues bonds that will mature on october 25, 2030, the bond symbol could be `GE30` or `GE2030` or `GE102530` - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function symbol() external view returns(string memory); - - /** - * @notice Returns the number of decimals the bond uses - e.g `10`, means to divide the token amount by `10000000000` - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function decimals() external view returns(uint8); - - /** - * @notice Returns the bond currency. This is the contract address of the token used to pay and return the bond principal - */ - function currency() external view returns(address); - - /** - * @notice Returns the copoun currency. This is the contract address of the token used to pay coupons. It can be same as the the one used for the principal - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function currencyOfCoupon() external view returns(address); - - /** - * @notice Returns the bond denominiation. This is the minimum amount in which the Bonds may be issued. It must be expressend in unnit of the principal currency - * ex: If the denomination is equal to 1,000 and the currency is USDC, then bond denomination is equal to 1,000 USDC - */ - function denomination() external view returns(uint256); - - /** - * @notice Returns the issue volume (total debt amount). It is RECOMMENDED to express the issue volume in the bond currency unit (USDC, DAI, etc...). - * NOTICE: The `issue volume` can also be expressed in `denomination` unit. In that case, the `issue volume` MUST equal the `totalSupply` - * of the bonds, i.e. the total number of bond tokens issued. - * ex: if denomination = $1,000, and the total debt is $5,000,000 - * then, issueVolume() = $5,000, 000 / $1,000 = 5,000 bonds - */ - function issueVolume() external view returns(uint256); - - /** - * @notice Returns the bond interest rate. It is RECOMMENDED to express the interest rate in basis point unit. - * 1 basis point = 0.01% = 0.0001 - * ex: if interest rate = 5%, then coupon() => 500 basis points - */ - function couponRate() external view returns(uint256); - - /** - * @notice Returns the coupon type - * ex: 0: Zero coupon, 1: Fixed Rate, 2: Floating Rate, etc... - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function couponType() external view returns(uint256); - - /** - * @notice Returns the coupon frequency, i.e. the number of times coupons are paid in a year. - * - * * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function couponFrequency() external view returns(uint256); - - /** - * @notice Returns the date when bonds were issued to investors. This is a Unix Timestamp like the one returned by block.timestamp - */ - function issueDate() external view returns(uint256); - - /** - * @notice Returns the bond maturity date, i.e, the date when the principal is repaid. This is a Unix Timestamp like the one returned by block.timestamp - * The` maturity date` MUST be greater than the `issue date` - */ - function maturityDate() external view returns(uint256); - - /** - * @notice Returns the day count basis - * Ex: 0: actual/actual, 1: actual/360, etc... - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function dayCountBasis() external view returns(uint256); - - /** - * @notice Returns the principal of an account. It is RECOMMENDED to express the principal in the bond currency unit (USDC, DAI, etc...). - * NOTICE: The `principal` can also be expressed in `denomination` unit. In that case, the `principal` MUST equal the balance of `_account`. - * Ex: if denomination = $1,000, and the user has invested $5,000 (princiapl in currency unit), then - * principalOf(_account) = 5,000/1,000 = 5 - * @param _account account address - */ - function principalOf(address _account) external view returns(uint256); - - /** - * @notice Returns the amount of tokens the `_spender` account has been authorized by the `_owner`` - * acount to manage their bonds - * @param _owner the bondholder address - * @param _spender the address that has been authorized by the bondholder - */ - function approval(address _owner, address _spender) external view returns(uint256); - - /** - * @notice Authorizes `_spender` account to manage `_amount`of their bonds - * @param _spender the address to be authorized by the bondholder - * @param _amount amount of bond to approve. _amount MUST be a multiple of denomination - */ - function approve(address _spender, uint256 _amount) external returns(bool); - - /** - * @notice Authorizes the `_spender` account to manage all their bonds - * @param _spender the address to be authorized by the bondholder - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function approveAll(address _spender) external returns(bool); - - /** - * @notice Lower the allowance of `_spender` by `_amount` - * @param _spender the address to be authorized by the bondholder - * @param _amount amount of bond to remove approval; _amount MUST be a multiple of denomination - */ - function decreaseAllowance(address _spender, uint256 _amount) external returns(bool); - - /** - * @notice Remove the allowance for `_spender` - * @param _spender the address to remove the authorization by from - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function decreaseAllowanceForAll(address _spender) external returns(bool); - - /** - * @notice Moves `_amount` bonds to address `_to` - * @param _to the address to send the bonds to - * @param _amount amount of bond to transfer. _amount MUST be a multiple of denomination - * @param _data additional information provided by the token holder - */ - function transfer(address _to, uint256 _amount, bytes calldata _data) external returns(bool); - - /** - * @notice Moves all bonds to address `_to` - * @param _to the address to send the bonds to - * @param _data additional information provided by the token holder - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function transferAll(address _to, bytes calldata _data) external returns(bool); - - /** - * @notice Moves `_amount` bonds from an account that has authorized through the approve function - * @param _from the bondholder address - * @param _to the address to transfer bonds to - * @param _amount amount of bond to transfer. _amount MUST be a multiple of denomination - * @param _data additional information provided by the token holder - */ - function transferFrom(address _from, address _to, uint256 _amount, bytes calldata _data) external returns(bool); - - /** - * @notice Moves all bonds from `_from` to `_to`. The caller must have been authorized through the approve function - * @param _from the bondholder address - * @param _to the address to transfer bonds to - * @param _data additional information provided by the token holder - * - * OPTIONAL - interfaces and other contracts MUST NOT expect these values to be present. The method is used to improve usability. - */ - function transferAllFrom(address _from, address _to, bytes calldata _data) external returns(bool); - - /** - * @notice MUST be emitted when bonds are transferred - * @param _from the account that owns bonds - * @param _to the account that receives the bond - * @param _amount the amount of bonds to be transferred - * @param _data additional information provided by the token holder - */ - event Transferred(address _from, address _to, uint256 _amount, bytes _data); - - /** - * @notice MUST be emitted when an account is approved - * @param _owner the bonds owner - * @param _spender the account to be allowed to spend bonds - * @param _amount the amount allowed by _owner to be spent by _spender. - */ - event Approved(address _owner, address _spender, uint256 _amount); - - /** - * @notice MUST be emitted when the `_owner` decreases allowance from `_sepnder` by quantity `_amount` - * @param _owner the bonds owner - * @param _spender the account that has been allowed to spend bonds - * @param _amount the amount of tokens to disapprove - */ - event AllowanceDecreased(address _owner, address _spender, uint256 _amount); -} -``` - -## Rationale - -The financial bond standard is designed to represent fixed income assets, which reprensent a loan made by an investor to a borrower. -The proposed design has been motivated by the necessity to tokenize fixed income assets, and to represent the bond token with same -characteristics as in traditional finance. Keeping the same properties as in tradional finance is necessary for issuers and investors -to move to tokenized bonds without major difficulties. The same structure used in tradional finace, i.e issuer-investment bank-investors -can be used for the bond standard, in that case the investment bank intermediary may be replaced by smart contracts. In the case of -institutional issuance, the smart contracts can be managed by the investment bank. Decentralized exchanges may also use the bond standard -to list bonds, in that case, decentralized exchanges will be in charge of managing the smart contracts. Other entities may also create -tokenized bonds by integrating this financial bond interface. - -The use of terminology such as `issueVolume` and `principalOf` instead of `totalSupply` and `balanceOf` respectively, as used in other standards like -[ERC-20](./eip-20) is motivated by the need to use same the terminology as with traditional bond. Furthermore, by adding the `data` input parameter -in all `transfer` functions to allow the transfer of additional data, the ERC-7092 SHOULD NOT be compatible with the ERC-20 standard, even if both -the `principalOf` and `balanceOf` functions were used. - -Another reason that has motivated not to extend other standards like the ERC-20 is to allow token explorer platforms like etherscan to represent -the ERC-7092 token not as a simple ERC-20 token, but as a new token showing some bonds characteristics like the `principal`, the `coupon`, -the `denomination`, and the `maturity date`. Those information are very important for bondholders to know the return on the capital invested. - -### Total Supply - -We made the choice not to define the `totalSupply` function beacause it can be derived from both the `issue volume` and the `denomination`. -However it is RECOMMENDED to define the `totalSupply` function in any contract that implements this standard. In that case, -the `total supply` MUST be equal to the ratio of the `issue volume` and the `denomination`. - -```javascript - totalSupply = issueVolume / denomination -``` - -### Account Balance - -Because the balance of an account can be derived from both the `principal` and the `denomination`, we made the choice not to define the `balanceOf` function. -However it is RECOMMENDED to define the `balanceOf` function in any contract that implements this standard. In that case, -the `balance` of an account MUST be equal to the ratio of the `principal` of that account and the `denomination`. - -```javascript - balanceOf(account) = principalOf(account) / denomination -``` - -## Backwards Compatibility - -Beacause some functions like `totalSupply` or `balanceOf` are not implemented, the ERC-7092 SHOULD NOT extend existing standards, -like [ERC-20](./eip-20) or [ERC-1155](./eip-1155). The ERC-7092 is a representation of a new token for bonds with all bonds characteristics -and functionalities already attached to it. - -**_For the reasons mentionned above, we recommand a pure implementation of the standard_** to issue tokenized bonds, since any hybrid solution -with other standards mentionned above SHOULD fail. - - -## Reference Implementation - -The reference implementation of the [ERC-7092](./eip-7092.md) can be found [here](../assets/eip-7092/ERC7092.sol). - -Some bonds have embedded options attached to them. As an example we can cite: - -1. Callable bonds that have an option that gives the issuer the right to retire bonds before they mature. -2. Puttable bonds that have an option that gives investors the right to retire bonds before they mature. -3. Convertible bonds that gives investors the right to convert their bonds to equity. - -Bonds with embedded options can be created by inheriting from the basic ERC-7092 that integrates the proposed interface. - -### CALLABLE BONDS: - -```solidity -pragma solidity ^0.8.0; - -import 'ERC7092.sol'; - -contract ERC7092Callable is ERC7092 { - // WRITE THE LOGIC TO ALLOW THE ISSUER TO CALL BONDS - // STATE VARIABLES AND FUNCTIONS NEEDED - - /** - * @notice call bonds owned by `_investor` - * MUST be called by the issuer only - */ - function call(address _investor) public { - require(_principals[_investor] > 0, "ERC7092Callable: ONLY_ISSUER"); - require(block.timestamp < _bond[bondISIN].maturityDate, "ERC7092Callable: BOND_MATURED"); - - uint256 principal = _principals[_investor]; - _principals[_investor] = 0; - - // ADD LOGIC HERE - } -} -``` - -### PUTTABLE BONDS: - -```solidity -pragma solidity ^0.8.0; - -import 'ERC7092.sol'; - -contract ERC7092Puttable is ERC7092 { - // WRITE THE LOGIC TO ALLOW INVESTORS TO PUT BONDS - // STATE VARIABLES AND FUNCTIONS NEEDED - - /** - * @notice put bonds - * MUST be called by investors who own bonds - */ - function put() public { - require(_principals[msg.sender] > 0, "ERC7092Puttable: ONLY_INVESTORS"); - require(block.timestamp < _bond[bondISIN].maturityDate, "ERC7092Puttable: BOND_MATURED"); - - uint256 principal = _principals[msg.sender]; - _principals[msg.sender] = 0; - - // ADD LOGIC - } -} -``` - -### CONVERTIBLE BONDS: - -```solidity -pragma solidity ^0.8.0; - -import 'ERC7092.sol'; - -contract ERC7092Convertible is ERC7092 { - // WRITE THE LOGIC TO ALLOW INVESTOR OR ISSUER TO CONVERT BONDS TO EQUITY - // STATE VARIABLES AND FUNCTIONS NEEDED - - /** - * @notice convert bonds to equity. Here we assumed that the investors must convert their bonds to equity - * Issuer can also convert invetsors bonds to equity. - */ - function convert() public { - require(_principals[msg.sender] > 0, "ERC7092Convertible: ONLY_INVESTORS"); - require(block.timestamp < _bond[bondISIN].maturityDate, "ERC7092Convertible: BOND_MATURED"); - - uint256 principal = _principals[msg.sender]; - _principals[msg.sender] = 0; - - // ADD LOGIC HERE - } -} -``` - -## Security Considerations - -When implementing the ERC-7092, it is important to consider security risk related to functions that give approval to operators to manage owner's bonds, and to functions that allow to transfer bonds. Functions `transferAll` and `transferAllFrom` allow to transfer all the balance of an account. Therefore, it is crucial to ensure that only the bonds owner and accounts that have been approved by the bonds owner can call these functions. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7092.md diff --git a/EIPS/eip-7093.md b/EIPS/eip-7093.md index 6e6ce1190c1bf7..b7f00552cfb9dd 100644 --- a/EIPS/eip-7093.md +++ b/EIPS/eip-7093.md @@ -1,423 +1 @@ ---- -eip: 7093 -title: Social Recovery Interface -description: Interfaces for social recovery account supporting various guardian types and customizable recovery policies. -author: John Zhang (@johnz1019), Davis Xiang (@xcshuan), Kyle Xu (@kylexyxu), George Zhang (@odysseus0) -discussions-to: https://ethereum-magicians.org/t/eip-social-recovery-interface/14494 -status: Draft -type: Standards Track -category: ERC -created: 2023-05-29 -requires: 1271 ---- - -## Abstract - -This ERC proposes a standard interface for social recovery of smart contract accounts. It separates identity and policy verification from the recovery process, allowing more ways to authenticate (known as Guardians) than just on-chain accounts. It also lets users customize recovery policies without changing the account’s smart contract. - -## Motivation - -Vitalik Buterin has long advocated for social recovery as an essential tool for user protection within the crypto space. He posits that the value of this system rests in its ability to offer users, especially those less acquainted with the technicalities of cryptography, a robust safety net when access credentials are lost. By entrusting account recovery to a network of selected individuals or entities, dubbed "Guardians," users gain a safeguard against the risk of losing access to their digital assets. - -In essence, social recovery operates by verifying the identity of the user and the chosen Guardians, and then considering a set of their signatures. Should the validated signatures reach a specified threshold, account access is reestablished. This system is equipped to enforce complex policies, such as necessitating signatures from particular Guardians or reaching signature thresholds from different Guardian categories. - -To overcome these limitations, this Ethereum Improvement Proposal (EIP) introduces a novel, customizable social recovery interface standard. This standard decouples identity and recovery policy verification from the recovery procedure itself, thereby enabling an independent, versatile definition and extension of both. This strategy accommodates a wider range of Guardian types and recovery policies, thereby offering users the following benefits: - -1. Appoint friends or family members, who do not have blockchain accounts, as Guardians for social recovery. -2. Use NFTs/SBTs as Guardians for their accounts. -3. Personalize and implement adaptable recovery policies. -4. Support novel types of Guardians and recovery policies without needing to upgrade their account contracts. -5. Enable multiple recovery mechanism support, thereby eliminating single points of failure. - -This approach enables users to customize recovery policies without the need to change the smart contract of the account itself. - -## 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. - - - -This EIP consists of four key concepts: - -- **Identity**: This denotes the representation of a Guardian's identity on the blockchain. It encapsulates traditional on-chain account types such as Externally Owned Accounts (EOA) and Smart Contract Accounts (SCA). More importantly, it extends to include any identity construct capable of producing construct able to be verified on-chain, like signatures and proofs. This could range from [Webauthn](https://www.w3.org/TR/2021/REC-webauthn-2-20210408/)/Passkey R1 keys to Email DomainKeys Identified Mail (DKIM) signatures [RFC 6376](https://www.rfc-editor.org/rfc/rfc6376), OpenID tokens, Zero-Knowledge Proofs (ZKP), Non-Fungible Tokens (NFTs), SoulBound Tokens (SBTs), and even types yet to be developed. This comprehensive approach ensures a broad, forward-compatible support for various identity types. -- **PermissionVerifier**: This component defines how to verify the signature or proof provided by the Guardian. Regardless of whether the Guardian's account is on-chain or off-chain, the PermissionVerifier is invoked during the recovery process of smart contract accounts that incorporate a social recovery system. Its primary role is to confirm the validity of the Guardian's signature or proof, thereby ensuring the authenticity of the Guardian during the recovery process. -- **RecoveryPolicyVerifier**: This component offers a flexible interface for validating recovery policies. The flexibility stems from allowing account holders or authorized parties to define and store their recovery policies. During the recovery process, the verification logic is implemented by invoking the specific function of the contract instance adopting this interface. Thus, a wide array of customizable social recovery scenarios can be catered to through different contract instances and policy configurations. This contract is optional, because sometimes the contract designer may not need the policy abstraction. -- **RecoveryAccount**: This component encapsulates the core of the social recovery functionality. It is designed to be flexible, composable, and extensible to adapt to various recovery needs. Each RecoveryAccount is defined by an instance contract, crafted by smart contract developers, which embeds the essential logic for the recovery process. -- **RecoveryModule**: In some contract designs, many functions are not directly added to the account contract, but are implemented in the form of Module, which is a contract outside the account contract. This component encapsulates the core of the social recovery functionality. It is designed to be flexible, composable, and extensible to adapt to various recovery needs. - -![social_recovery_flow](../assets/eip-7093/social-recovery-flow.svg) - -### DataTypes - -### `TypesAndDecoders` - -This defines the necessary data types required by this interface standard. - -```solidity -/** - * @dev Structure representing an identity with its signature/proof verification logic. - * Represents an EOA/CA account when signer is empty, use `guardianVerifier`as the actual signer for signature verification. - * OtherWise execute IPermissionVerifier(guardianVerifier).isValidPermission(hash, signer, signature). - */ -struct Identity { - address guardianVerifier; - bytes signer; -} - -/** - * @dev Structure representing a guardian with a property - * The property of Guardian are defined by the associated RecoveryPolicyVerifier contract. - */ -struct GuardianInfo { - Identity guardian; - uint64 property; //eg.,Weight,Percentage,Role with weight,etc. -} - -/** - * @dev Structure representing a threshold configuration - */ -struct ThresholdConfig { - uint64 threshold; // Threshold value - int48 lockPeriod; // Lock period for the threshold -} - -/** - * @dev Structure representing a recovery configuration - * A RecoveryConfig can have multiple threshold configurations for different threshold values and their lock periods, and the policyVerifier is optional. - */ -struct RecoveryConfigArg { - address policyVerifier; - GuardianInfo[] guardianInfos; - ThresholdConfig[] thresholdConfigs; -} - -struct Permission { - Identity guardian; - bytes signature; -} - -``` - -The `Identity` structure represents various types of guardians. The process of identity verification is as follows: - -- When the `signer` value in the declared entity is empty, this implies that the `Identity` entity is of EOA/SCA account type. In this case, `guardianVerifier` address should be the address of EOA/SCA (the actual signer). For permission verification of this `Identity` entity, it is recommended to utilize a secure library or built-in function capable of validating both ECDSA and [ERC-1271](./eip-1271.md) signatures. This helps in preventing potential security vulnerabilities, such as signature malleability attacks. -- When the `signer` value in the declared entity is non-empty, this suggests that the `Identity` entity is of non-account type. In this case, permission verification can be accomplished by calling `guardianVerifier` address contract instance through `IPermissionVerifier` interface. - - - -### Interfaces - -### `IPermissionVerifier` - -The Guardian Permission Verification Interface. Implementations MUST conform to this interface to enable identity verification of non-account type guardians. - -```solidity -/** - * @dev Interface for no-account type identity signature/proof verification - */ -interface IPermissionVerifier { - /** - * @dev Check if the signer key format is correct - */ - function isValidSigners(bytes[] signers) external returns (bool); - - /** - * @dev Validate permission - */ - function isValidPermission( - bytes32 hash, - bytes signer, - bytes signature - ) external returns (bool); - - /** - * @dev Validate permissions - */ - function isValidPermissions( - bytes32 hash, - bytes[] signers, - bytes[] signatures - ) external returns (bool); - - /** - * @dev Return supported signer key information, format, signature format, hash algorithm, etc. - * MAY TODO:using ERC-3668: ccip-read - */ - function getGuardianVerifierInfo() public view returns (bytes memory); -} - -``` - - - -### `IRecoveryPolicyVerifier` - -The Recovery Policy Verification Interface. Implementations MAY conform to this interface to support verification of varying recovery policies. RecoveryPolicyVerifier is optional for SocialRecoveryInterface. - -```solidity -/** - * @dev Interface for recovery policy verification - */ -interface IRecoveryPolicyVerifier { - /** - * @dev Verify recovery policy and return verification success and lock period - * Verification includes checking if guardians exist in the Guardians List - */ - function verifyRecoveryPolicy( Permission[] memory permissions, uint64[] memory properties) - external - view - returns (bool succ, uint64 weight); - - /** - * @dev Returns supported policy settings and accompanying property definitions for Guardian. - */ - function getPolicyVerifierInfo() public view returns (bytes memory); -} - -``` - -The `verifyRecoveryPolicy()` function is designed to validate whether the provided list of `Permissions` abides by the specified recovery properties (`properties`). This function has the following constraints and effects: For each matched `guardian`, calculations are made according to the corresponding `property` in the `properties` list (e.g., accumulating weight, distinguishing role while accumulating, etc.). - -These constraints ensure that the provided `guardians` and `properties` comply with the requirements of the recovery policy, maintaining the security and integrity of the recovery process. - - - -### `IRecoveryAccount` - -The Smart Contract Account MAY implement the `IRecoveryAccount` interface to support social recovery functionality, enabling users to customize configurations of different types of Guardians and recovery policies. In the contract design based on Module, the implementation of `RecoveryModule` is very similar to `RecoveryAccount`, except that different accounts need to be distinguished and isolated. - -```solidity -interface IRecoveryAccount { - modifier onlySelf() { - require(msg.sender == address(this), "onlySelf: NOT_AUTHORIZED"); - _; - } - - modifier InRecovering(address policyVerifyAddress) { - (bool isRecovering, ) = getRecoveryStatus(policyVerifierAddress); - require(isRecovering, "InRecovering: no ongoing recovery"); - _; - } - - /** - * @dev Events for updating guardians, starting for recovery, executing recovery, and canceling recovery - */ - event RecoveryStarted(bytes newOwners, uint256 nonce, uint48 expiryTime); - event RecoveryExecuted(bytes newOwners, uint256 nonce); - event RecoveryCanceled(uint256 nonce); - - /** - * @dev Return the domain separator name and version for signatures - * Also return the domainSeparator for EIP-712 signature - */ - - /// @notice Domain separator name for signatures - function DOMAIN_SEPARATOR_NAME() external view returns (string memory); - - /// @notice Domain separator version for signatures - function DOMAIN_SEPARATOR_VERSION() external view returns (string memory); - - /// @notice returns the domainSeparator for EIP-712 signature - /// @return the bytes32 domainSeparator for EIP-712 signature - function domainSeparatorV4() external view returns (bytes32); - - /** - * @dev Update /replace guardians and recovery policies - * Multiple recovery policies can be set using an array of RecoveryConfigArg - */ - function updateGuardians(RecoveryConfigArg[] recoveryConfigArgs) external onlySelf; - - // Generate EIP-712 message hash, - // Iterate over signatures for verification, - // Verify recovery policy, - // Store temporary state or recover immediately based on the result returned by verifyRecoveryPolicy. - function startRecovery( - uint256 configIndex, - bytes newOwner, - Permission[] permissions - ) external; - - /** - * @dev Execute recovery - * temporary state -> ownerKey rotation - */ - function executeRecovery(uint256 configIndex) external; - - function cancelRecovery(uint256 configIndex) external onlySelf InRecovering(policyVerifier); - - function cancelRecoveryByGuardians(uint256 configIndex, Permission[] permissions) - external - InRecovering(policyVerifier); - - /** - * @dev Get wallet recovery config, check if an identity is a guardian, get the nonce of social recovery, and get the recovery status of the wallet - */ - function isGuardian(uint256 configIndex, identity guardian) public view returns (bool); - - function getRecoveryConfigs() public view returns (RecoveryConfigArg[] recoveryConfigArgs); - - function getRecoveryNonce() public view returns (uint256 nonce); - - function getRecoveryStatus(address policyVerifier) public view returns (bool isRecovering, uint48 expiryTime); -} - -``` - -- For the `Guardian`'s signable message, it SHOULD employ [EIP-712](./eip-712.md) type signature to ensure the content of the signature is readable and can be confirmed accurately during the Guardian signing process. -- `getRecoveryNonce()` SHOULD be separated from nonces associated with account asset operations, as social recovery is a function at the account layer. - - - -### **Recovery Account Workflow** - -Note: This workflow is presented as an illustrative example to clarify the coordinated usage of the associated interface components. It does not imply a mandatory adherence to this exact process. - -1. A user sets up a `recoveryPolicyConfigA` within his `RecoveryAccount`: - - ```json - { - "recoveryConfigA": { - "type": "RecoveryConfig", - "policyVerifier": "0xA", - "guardians": [ - { - "type": "Identity", - "name": "A", - "data": { - "guardianVerifier": "guardianVerifier1", - "signer": "signerA" - }, - "property": 30 - }, - { - "type": "Identity", - "name": "B", - "data": { - "guardianVerifier": "guardianVerifier2", - "signer": "" - }, - "property": 30 - }, - { - "type": "Identity", - "name": "C", - "data": { - "guardianVerifier": "guardianVerifier3", - "signer": "signerC" - }, - "property": 40 - } - ], - "thresholdConfigs": [ - { "threshold": 50, "lockPeriod": "24hours"}, - { "threshold": 100,"lockPeriod": "0"} - ] - } - } - ``` - -2. When GuardianA and GuardianB assist the user in performing account recovery, they are to confirm the [EIP-712](./eip-712.md) structured data for signing, which might look like this: - - ```json - { - "types": { - "EIP712Domain": [ - { "name": "name", "type": "string" }, - { "name": "version", "type": "string" }, - { "name": "chainId", "type": "uint256" }, - { "name": "verifyingContract", "type": "address" } - ], - "StartRecovery": [ - { "name": "configIndex", "type": "uint256" }, - { "name": "newOwners", "type": "bytes" }, - { "name": "nonce", "type": "uint256" } - ] - }, - "primaryType": "StartRecovery", - "domain": { - "name": "Recovery Account Contract", - "version": "1", - "chainId": 1, - "verifyingContract": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" - }, - "message": { - "policyVerifier": "0xA", - "newOwners": "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", - "nonce": 10 - } - } - ``` - - In this step, the guardians need to confirm that the domain separator's `verifyingContract` is the correct `RecoveryAccount` address for the user, the contract name, version, and chainId are correct, and the `policyVerifier` and `newOwners` fields in the `message` part match the user's provided data. - - The `msgHash` is then composed of: - - - `msgHash` = `keccak256("\\x19\\x01" + domainSeparatorV4() + dataHash)` - - Where, - - - `dataHash` = `keccak256(EXECUTE_RECOVERY_TYPEHASH + configIndex + keccak256(bytes(newOwners)) + getRecoveryNonce())` - - `EXECUTE_RECOVERY_TYPEHASH` = `keccak256("StartRecovery(address configIndex, bytes newOwners, uint256 nonce)")` - - The guardians sign this hash to obtain the signature: - - - `signature` = `sign(msgHash)` - - The `permission` is then constructed as: - - - `permission` = `guardian + signature` - - Once each Guardian has generated their unique `permission`, all these individual permissions are collected to form `permissions`: - - `permissions`= [`guardianA+signature`, `guardianB+signature`, ...] - - The `permissions` is an array that consists of all the permissions of the Guardians who are participating in the recovery process. - -3. A bundler or another relayer service calls the `RecoveryAccount.startRecovery(0xA, newOwners, permissions)` function. - -4. `startRecovery()` function's processing logic is as follows: - - - Generate a message hash (`msgHash`) from the input parameters `0xA`, `newOwners` and internally generated [EIP-712](./eip-712.md) signature parameters and `RecoveryNonce`. - - Extract `guardian` and corresponding `signature` from the input parameters `permissions` and process them as follows: - - If `guardianA.signer` is non-empty (Identity A), call `IPermissionVerifier(guardianVerifier1).isValidPermissions(signerA, msgHash, permissionA.signature)` to validate the signature. - - If `guardianA.signer` is empty (Identity B), call the internal function `SignatureChecker.isValidSignatureNow(guardianVerifier2, msgHash, permissionB.signature)` to validate the signature. - -5. After successful verification of all `guardians` signatures, fetch the associated `config` data for policyVerifier address `0xA` and call `IRecoveryPolicyVerifier(0xA).verifyRecoveryPolicy(permissions, properties)`. The function `verifyRecoveryPolicy()` performs the following checks: - - Note that the `guardians` parameter in the function refers to the guardians whose signatures have been successfully verified. - - - Verify that `guardians` (Identity A and B) are present in `config.guardianInfos` list and are unique. - - Accumulate the `property` values of `guardians` (30 + 30 = 60). - - Compare the calculated result (60) with the `config.thresholdConfigs.threshold` ,the result is more than the first element (`threshold: 50, lockPeriod: 24 hours`) but less than the second element (`threshold: 100, lockPeriod: ""`), the validation is successful, and the lock period of 24 hours is returned. - -6. The `RecoveryAccount` saves a temporary state `{newOwners, block.timestamp + 24 hours}` and increments `RecoveryNonce`. A `RecoveryStarted` event is emitted. - -7. After the expiry time, anyone (usually a relayer) can call `RecoveryAccount.executeRecovery()` to replace `newOwners`, remove the temporary state, complete the recovery, and emit a `RecoveryExecuteed` event. - - - -## Rationale - -A primary design rationale for this proposal is to extend a greater diversity of Guardian types and more flexible, customizable recovery policies for a RecoveryAccount. This is achieved by separating the verification logic from the social recovery process, ensuring that the basic logic of the account contract remains unaltered. - -The necessity of incorporating `Verifiers` from external contracts arises from the importance of maintaining the inherent recovery logic of the `RecoveryAccount`. The `Verifiers`'s logic is designed to be simple and clear, and its fixed invocation format means that any security risks posed by integrating external contracts can be effectively managed. - -The `recoveryConfigs` are critical to the `RecoveryAccount` and should be securely and effectively stored. The access and modification permissions associated with these configurations must be carefully managed and isolated to maintain security. The storage and quantity of `recoveryConfigs` are not limited to ensure the maximum flexibility of the `RecoveryAccount`'s implementation. - -The introduction of `recoveryNonce` into the `RecoveryAccount` serves to prevent potential replay attacks arising from the malicious use of Guardian's `permissions`. The `recoveryNonce` ensures each recovery process is unique, reducing the likelihood of past successful recovery attempts being maliciously reused. - -## Backwards Compatibility - -No backward compatibility issues are introduced by this standard. - -## Reference Implementation - -TBD. - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7093.md diff --git a/EIPS/eip-7144.md b/EIPS/eip-7144.md index c6e40eee42ed16..5a05636efcf661 100644 --- a/EIPS/eip-7144.md +++ b/EIPS/eip-7144.md @@ -1,345 +1 @@ ---- -eip: 7144 -title: ERC-20 with transaction validation step. -description: A new validation step for transfer and approve calls, achieving a security step in case of stolen wallet. -author: Eduard López i Fina (@eduardfina) -discussions-to: https://ethereum-magicians.org/t/erc721-with-a-validation-step/14071 -status: Review -type: Standards Track -category: ERC -created: 2023-05-07 -requires: 20 ---- - -## Abstract - -This standard is an extension of [ERC-20](./eip-20.md). It defines new validation functionality to avoid wallet draining: every `transfer` or `approve` will be locked waiting for validation. - -## Motivation - -The power of the blockchain is at the same time its weakness: giving the user full responsibility for their data. - -Many cases of Token theft currently exist, and current Token anti-theft schemes, such as transferring Tokens to cold wallets, make Tokens inconvenient to use. - -Having a validation step before every `transfer` and `approve` would give Smart Contract developers the opportunity to create secure Token anti-theft schemes. - -An implementation example would be a system where a validator address is responsible for validating all Smart Contract transactions. - -This address would be connected to a dApp where the user could see the validation requests of his Tokens and accept the correct ones. - -Giving this address only the power to validate transactions would make a much more secure system where to steal a Token the thief would have to have both the user's address and the validator address simultaneously. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -[ERC-20](./eip-20.md) compliant contracts MAY implement this EIP. - -All the operations that change the ownership of Tokens, like a `transfer`/`transferFrom`, SHALL create a `TransferValidation` pending to be validated and emit a `ValidateTransfer`, and SHALL NOT transfer the Tokens. - -All the operations that enable an approval to manage a Token, like an `approve`, SHALL create an `ApprovalValidation` pending to be validated and emit a `ValidateApproval`, and SHALL NOT enable an approval. - -When the transfer is called by an approved account and not the owner, it MUST be executed directly without the need for validation. This is in order to adapt to all current projects that require approve to directly move your Tokens. - -When validating a `TransferValidation` or `ApprovalValidation` the valid field MUST be set to true and MUST NOT be validated again. - -The operations that validate a `TransferValidation` SHALL change the ownership of the Tokens. - -The operations that validate an `ApprovalValidation` SHALL enable the approval. - -### Contract Interface - -```solidity -interface IERC7144 { - - struct TransferValidation { - // The address of the owner. - address from; - // The address of the receiver. - address to; - // The token amount. - uint256 amount; - // Whether is a valid transfer. - bool valid; - } - - struct ApprovalValidation { - // The address of the owner. - address owner; - // The spender address. - address spender; - // The token amount approved. - uint256 amount; - // Whether is a valid approval. - bool valid; - } - - /** - * @dev Emitted when a new transfer validation has been requested. - */ - event ValidateTransfer(address indexed from, address indexed to, uint256 amount, uint256 indexed transferValidationId); - - /** - * @dev Emitted when a new approval validation has been requested. - */ - event ValidateApproval(address indexed owner, address indexed spender, uint256 amount, uint256 indexed approvalValidationId); - - /** - * @dev Returns true if this contract is a validator ERC20. - */ - function isValidatorContract() external view returns (bool); - - /** - * @dev Returns the transfer validation struct using the transfer ID. - * - */ - function transferValidation(uint256 transferId) external view returns (TransferValidation memory); - - /** - * @dev Returns the approval validation struct using the approval ID. - * - */ - function approvalValidation(uint256 approvalId) external view returns (ApprovalValidation memory); - - /** - * @dev Return the total amount of transfer validations created. - * - */ - function totalTransferValidations() external view returns (uint256); - - /** - * @dev Return the total amount of transfer validations created. - * - */ - function totalApprovalValidations() external view returns (uint256); -} - ``` - -The `isValidatorContract()` function MUST be implemented as `public`. - -The `transferValidation(uint256 transferId)` function MAY be implemented as `public` or `external`. - -The `approvalValidation(uint256 approveId)` function MAY be implemented as `public` or `external`. - -The `totalTransferValidations()` function MAY be implemented as `pure` or `view`. - -The `totalApprovalValidations()` function MAY be implemented as `pure` or `view`. - -## Rationale - -### Universality - -The standard only defines the validation functions, but not how they should be used. It defines the validations as internal and lets the user decide how to manage them. - -An example could be to have an address validator connected to a dApp so that users could manage their validations. - -This validator could be used for all Tokens or only for some users. - -It could also be used as a wrapped Smart Contract for existing ERC-20, allowing 1/1 conversion with existing Tokens. - -### Extensibility - -This standard only defines the validation function, but does not define the system with which it has to be validated. A third-party protocol can define how it wants to call these functions as it wishes. - -## Backwards Compatibility - -This standard is an extension of [ERC-20](./eip-20.md), compatible with all the operations except `transfer`/`transferFrom`/`approve`. - -This operations will be overridden to create a validation petition instead of transfer the Tokens or enable an approval. - -## Reference Implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "./IERC7144.sol"; - -/** - * @dev Implementation of ERC7144 - */ -contract ERC7144 is IERC7144, ERC20 { - - // Mapping from transfer ID to transfer validation - mapping(uint256 => TransferValidation) private _transferValidations; - - // Mapping from approval ID to approval validation - mapping(uint256 => ApprovalValidation) private _approvalValidations; - - // Total number of transfer validations - uint256 private _totalTransferValidations; - - // Total number of approval validations - uint256 private _totalApprovalValidations; - - /** - * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. - */ - constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_){ - } - - /** - * @dev Returns true if this contract is a validator ERC721. - */ - function isValidatorContract() public pure returns (bool) { - return true; - } - - /** - * @dev Returns the transfer validation struct using the transfer ID. - * - */ - function transferValidation(uint256 transferId) public view override returns (TransferValidation memory) { - require(transferId < _totalTransferValidations, "ERC7144: invalid transfer ID"); - TransferValidation memory v = _transferValidation(transferId); - - return v; - } - - /** - * @dev Returns the approval validation struct using the approval ID. - * - */ - function approvalValidation(uint256 approvalId) public view override returns (ApprovalValidation memory) { - require(approvalId < _totalApprovalValidations, "ERC7144: invalid approval ID"); - ApprovalValidation memory v = _approvalValidation(approvalId); - - return v; - } - - /** - * @dev Return the total amount of transfer validations created. - * - */ - function totalTransferValidations() public view override returns (uint256) { - return _totalTransferValidations; - } - - /** - * @dev Return the total amount of approval validations created. - * - */ - function totalApprovalValidations() public view override returns (uint256) { - return _totalApprovalValidations; - } - - /** - * @dev Returns the transfer validation of the `transferId`. Does NOT revert if transfer doesn't exist - */ - function _transferValidation(uint256 transferId) internal view virtual returns (TransferValidation memory) { - return _transferValidations[transferId]; - } - - /** - * @dev Returns the approval validation of the `approvalId`. Does NOT revert if transfer doesn't exist - */ - function _approvalValidation(uint256 approvalId) internal view virtual returns (ApprovalValidation memory) { - return _approvalValidations[approvalId]; - } - - /** - * @dev Validate the transfer using the transfer ID. - * - */ - function _validateTransfer(uint256 transferId) internal virtual { - TransferValidation memory v = transferValidation(transferId); - require(!v.valid, "ERC721V: the transfer is already validated"); - - super._transfer(v.from, v.to, v.amount); - - _transferValidations[transferId].valid = true; - } - - /** - * @dev Validate the approval using the approval ID. - * - */ - function _validateApproval(uint256 approvalId) internal virtual { - ApprovalValidation memory v = approvalValidation(approvalId); - require(!v.valid, "ERC7144: the approval is already validated"); - - super._approve(v.owner, v.spender, v.amount); - - _approvalValidations[approvalId].valid = true; - } - - /** - * @dev Create a transfer petition of `tokenId` from `from` to `to`. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - * Emits a {ValidateTransfer} event. - */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual override { - require(from != address(0), "ERC7144: transfer from the zero address"); - require(to != address(0), "ERC7144: transfer to the zero address"); - - if(_msgSender() == from) { - TransferValidation memory v; - - v.from = from; - v.to = to; - v.amount = amount; - - _transferValidations[_totalTransferValidations] = v; - - emit ValidateTransfer(from, to, amount, _totalTransferValidations); - - _totalTransferValidations++; - } else { - super._transfer(from, to, amount); - } - } - - /** - * @dev Create an approval petition from `owner` to operate the `amount` - * - * Emits an {ValidateApproval} event. - */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual override { - require(owner != address(0), "ERC7144: approve from the zero address"); - require(spender != address(0), "ERC7144: approve to the zero address"); - - ApprovalValidation memory v; - - v.owner = owner; - v.spender = spender; - v.amount = amount; - - _approvalValidations[_totalApprovalValidations] = v; - - emit ValidateApproval(v.owner, spender, amount, _totalApprovalValidations); - - _totalApprovalValidations++; - } -} -``` - -## Security Considerations - -As is defined in the Specification the operations that change the ownership of Tokens or enable an approval to manage the Tokens SHALL create a `TransferValidation` or an `ApprovalValidation` pending to be validated and SHALL NOT transfer the Tokens or enable an approval. - -With this premise in mind, the operations in charge of validating a `TransferValidation` or an `ApprovalValidation` must be protected with the maximum security required by the applied system. - -For example, a valid system would be one where there is a validator address in charge of validating the transactions. - -To give another example, a system where each user could choose his validator address would also be correct. - -In any case, the importance of security resides in the fact that no address can validate a `TransferValidation` or an `ApprovalValidation` without the permission of the chosen system. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7144.md diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md index 8a9c517566fb78..575740516c7d1b 100644 --- a/EIPS/eip-7160.md +++ b/EIPS/eip-7160.md @@ -1,211 +1 @@ ---- -eip: 7160 -title: ERC-721 Multi-Metadata Extension -description: Multiple metadata URIs per token, with the option to pin a primary URI. -author: 0xG (@0xGh), Marco Peyfuss (@mpeyfuss) -discussions-to: https://ethereum-magicians.org/t/erc721-multi-metadata-extension/14629 -status: Review -type: Standards Track -category: ERC -created: 2023-06-09 -requires: 165, 721 ---- - -## Abstract - -This EIP proposes an extension to the [ERC-721](./eip-721.md) standard to support multiple metadata URIs per token. It introduces a new interface, `IERC721MultiMetadata`, which provides methods for accessing the metadata URIs associated with a token, including a pinned URI index and a list of all metadata URIs. The extension is designed to be backward compatible with existing `ERC721Metadata` implementations. - -## Motivation - -The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token with the `ERC721Metadata` implementation. However, there are use cases where multiple metadata URIs are desirable. Some example use cases are listed below: - -- A token represents a collection of (cycling) assets with individual metadata -- An on-chain history of revisions to token metadata -- Appending metadata with different aspect ratios so that it can be displayed properly on all screens -- Dynamic and evolving metadata -- Collaborative and multi-artist tokens - -This extension enables such use cases by introducing the concept of multi-metadata support. - -The primary reason for having a multi-metadata standard in addition to the existing `ERC721Metadata` standard is that dapps and marketplaces don't have a mechanism to infer and display all the token URIs. Giving a standard way for marketplaces to offer collectors a way to pin/unpin one of the metadata choices also enables quick and easy adoption of this functionality. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -**The multi-metadata extension is OPTIONAL for [ERC-721](./eip-721.md) contracts and it is RECOMMENDED to be used in conjunction with the [ERC-4906](./eip-4906.md) standard if implemented**. - -```solidity -/// @title EIP-721 Multi-Metdata Extension -/// @dev The ERC-165 identifier for this interface is 0x06e1bc5b. -interface IERC7160 { - - /// @dev This event emits when a token uri is pinned and is - /// useful for indexing purposes. - event TokenUriPinned(uint256 indexed tokenId, uint256 indexed index); - - /// @dev This event emits when a token uri is unpinned and is - /// useful for indexing purposes. - event TokenUriUnpinned(uint256 indexed tokenId); - - /// @notice Get all token uris associated with a particular token - /// @dev If a token uri is pinned, the index returned SHOULD be the index in the string array - /// @dev This call MUST revert if the token does not exist - /// @param tokenId The identifier for the nft - /// @return index An unisgned integer that specifies which uri is pinned for a token (or the default uri if unpinned) - /// @return uris A string array of all uris associated with a token - /// @return pinned A boolean showing if the token has pinned metadata or not - function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris, bool pinned); - - /// @notice Pin a specific token uri for a particular token - /// @dev This call MUST revert if the token does not exist - /// @dev This call MUST emit a `TokenUriPinned` event - /// @dev This call MAY emit a `MetadataUpdate` event from ERC-4096 - /// @param tokenId The identifier of the nft - /// @param index The index in the string array returned from the `tokenURIs` function that should be pinned for the token - function pinTokenURI(uint256 tokenId, uint256 index) external; - - /// @notice Unpin metadata for a particular token - /// @dev This call MUST revert if the token does not exist - /// @dev This call MUST emit a `TokenUriUnpinned` event - /// @dev This call MAY emit a `MetadataUpdate` event from ERC-4096 - /// @dev It is up to the developer to define what this function does and is intentionally left open-ended - /// @param tokenId The identifier of the nft - function unpinTokenURI(uint256 tokenId) external; - - /// @notice Check on-chain if a token id has a pinned uri or not - /// @dev This call MUST revert if the token does not exist - /// @dev Useful for on-chain mechanics that don't require the tokenURIs themselves - /// @param tokenId The identifier of the nft - /// @return pinned A bool specifying if a token has metadata pinned or not - function hasPinnedTokenURI(uint256 tokenId) external view returns (bool pinned); -} -``` - -The `TokenUriPinned` event MUST be emitted when pinning a token uri with the `pinTokenUri` function. - -The `TokenUriUnpinned` event MUST be emitted when unpinning a token uri with the `unpinTokenUri` function. - -The `tokenURI` function defined in the ERC-721 Metadata extension MUST return the pinned URI when a token has a pinned uri. - -The `tokenURI` function defined in the ERC-721 Metadata extension MUST return a default uri when a token has an unpinned uri. - -The `supportsInterface` method MUST return `true` when called with `0x06e1bc5b`. - -Implementing functionality to add or remove uris to a token MUST be implemented separately from this standard. It is RECOMMENDED that one of the event defined in [ERC-4906](./eip-4906.md) are emitted whenever uris are added or removed. - -See the [Implementation](#reference-implementation) section for an example. - -## Rationale - -Similar terminology to [ERC-721](./eip-721.md) was used in order to keep fetching metadata familiar. The concept of pinning and unpinning metadata is introduced as it is clear that NFT owners might want to choose which piece of metadata to display. At first, we considered leaving the pinning and unpinning actions up to each developer, but realized that a standard interface for pinning and unpinning allows for dApps to easily implement universal support for multi-metadata tokens. - -We first considered whether the `tokenURIs` function should return just a string array, but added the extra information so that you could get all info desired in one call instead of potentially three calls. The pinned URI should be used as the primary URI for the token, while the list of metadata URIs can be used to access individual assets' metadata within the token. dApps could present these as a gallery or media carousels. - -The `TokenUriPinned` and `TokenUriUnpinned` events included in this specification can be used by dApps to index what metadata to show. This can eliminate on-chain calls and event driven architecture can be used instead. - -The reason why this standard recommends the use of [ERC-4906](./eip-4906.md) when adding or removing uris from a token is that there is already wide dApp support for this event and it already is what is needed - an alert to dApps that metadata for a token has been updated. We did not want to potentially cause dApp issues with duplicate events. A third party listening to this event could then call the `tokenURIs` function to get the updated metadata. - -## Backwards Compatibility - -This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The implementation of the `tokenURI` method must either return the pinned token uri (if pinned) or some default uri (if unpinned). - -## Reference Implementation - -An open-source reference implementation of the `IERC721MultiMetadata` interface can be provided, demonstrating how to extend an existing [ERC-721](./eip-721.md) contract to support multi-metadata functionality. This reference implementation can serve as a guide for developers looking to implement the extension in their own contracts. - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.19; - -import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol"; -import {IERC7160} from "./IERC7160.sol"; - -contract MultiMetadata is ERC721, Ownable, IERC7160, IERC4906 { - mapping(uint256 => string[]) private _tokenURIs; - mapping(uint256 => uint256) private _pinnedURIIndices; - mapping(uint256 => bool) private _hasPinnedTokenURI; - - constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) Ownable() { - _mint(msg.sender, 1); - } - - // @notice Returns the pinned URI index or the last token URI index (length - 1). - function _getTokenURIIndex(uint256 tokenId) internal view returns (uint256) { - return _hasPinnedTokenURI[tokenId] ? _pinnedURIIndices[tokenId] : _tokenURIs[tokenId].length - 1; - } - - // @notice Implementation of ERC721.tokenURI for backwards compatibility. - // @inheritdoc ERC721.tokenURI - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - _requireMinted(tokenId); - - uint256 index = _getTokenURIIndex(tokenId); - string[] memory uris = _tokenURIs[tokenId]; - string memory uri = uris[index]; - - // Revert if no URI is found for the token. - require(bytes(uri).length > 0, "ERC721: not URI found"); - return uri; - } - - /// @inheritdoc IERC721MultiMetadata.tokenURIs - function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris, bool pinned) { - _requireMinted(tokenId); - return (_getTokenURIIndex(tokenId), _tokenURIs[tokenId], _hasPinnedTokenURI[tokenId]); - } - - /// @inheritdoc IERC721MultiMetadata.pinTokenURI - function pinTokenURI(uint256 tokenId, uint256 index) external { - require(msg.sender == ownerOf(tokenId), "Unauthorized"); - _pinnedURIIndices[tokenId] = index; - _hasPinnedTokenURI[tokenId] = true; - emit TokenUriPinned(tokenId, index); - } - - /// @inheritdoc IERC721MultiMetadata.unpinTokenURI - function unpinTokenURI(uint256 tokenId) external { - require(msg.sender == ownerOf(tokenId), "Unauthorized"); - _pinnedURIIndices[tokenId] = 0; - _hasPinnedTokenURI[tokenId] = false; - emit TokenUriUnpinned(tokenId); - } - - /// @inheritdoc IERC721MultiMetadata.hasPinnedTokenURI - function hasPinnedTokenURI(uint256 tokenId) external view returns (bool pinned) { - return _hasPinnedTokenURI[tokenId]; - } - - /// @notice Sets a specific metadata URI for a token at the given index. - function setUri(uint256 tokenId, uint256 index, string calldata uri) external onlyOwner { - if (_tokenURIs[tokenId].length > index) { - _tokenURIs[tokenId][index] = uri; - } else { - _tokenURIs[tokenId].push(uri); - } - - emit MetadataUpdate(tokenId); - } - - // Overrides supportsInterface to include IERC721MultiMetadata interface support. - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return ( - interfaceId == type(IERC7160).interfaceId || - super.supportsInterface(interfaceId) - ); - } -} -``` - -## Security Considerations - -Care should be taken when specifying access controls for state changing events, such as those that allow uris to be added to tokens -and those specified in this standard: the `pinTokenUri` and `unpinTokenUri` functions. This is up to the developers to specify -as each application may have different requirements to allow for pinning and unpinning. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7160.md diff --git a/EIPS/eip-7201.md b/EIPS/eip-7201.md index 35562f3255d285..17d6a8be5973f4 100644 --- a/EIPS/eip-7201.md +++ b/EIPS/eip-7201.md @@ -1,100 +1 @@ ---- -eip: 7201 -title: Namespaced Storage Layout -description: Conventions for the storage location of structs in the namespaced storage pattern. -author: Francisco Giordano (@frangio), Hadrien Croubois (@Amxx), Ernesto García (@ernestognw), Eric Lau (@ericglau) -discussions-to: https://ethereum-magicians.org/t/eip-7201-namespaced-storage-layout/14796 -status: Last Call -last-call-deadline: 2023-10-01 -type: Standards Track -category: ERC -created: 2023-06-20 ---- - -## Abstract - -We define the NatSpec annotation `@custom:storage-location` to document storage namespaces and their location in storage in Solidity or Vyper source code. Additionally, we define a formula to derive a location from an arbitrary identifier. The formula is chosen to be safe against collisions with the storage layouts used by Solidity and Vyper. - -## Motivation - -Smart contract languages such as Solidity and Vyper rely on tree-shaped storage layout. This tree starts at slot 0 and is composed of sequential chunks for consecutive variables. Hashes are used to ensure the chunks containing values of mappings and dynamic arrays do not collide. This is sufficient for most contracts. However, it presents a challenge for various design patterns used in smart contract development. One example is a modular design where using `DELEGATECALL` a contract executes code from multiple contracts, all of which share the same storage space, and which have to carefully coordinate on how to use it. Another example is upgradeable contracts, where it can be difficult to add state variables in an upgrade given that they may affect the assigned storage location for the preexisting variables. - -Rather than using this default storage layout, these patterns can benefit from laying out state variables across the storage space, usually at pseudorandom locations obtained by hashing. Each value may be placed in an entirely different location, but more frequently values that are used together are put in a Solidity struct and co-located in storage. These pseudorandom locations can be the root of new storage trees that follow the same rules as the default one. Provided that this pseudorandom root is constructed so that it is not part of the default tree, this should result in the definition of independent spaces that do not collide with one another or with the default one. - -These storage usage patterns are invisible to the Solidity and Vyper compilers because they are not represented as Solidity state variables. Smart contract tools like static analyzers or blockchain explorers often need to know the storage location of contract data. Standardizing the location for storage layouts will allow these tools to correctly interpret contracts where these design patterns are used. - -## Specification - -### Preliminaries - -A _namespace_ consists of a set of ordered variables, some of which may be dynamic arrays or mappings, with its values laid out following the same rules as the default storage layout but rooted in some location that is not necessarily slot 0. A contract using namespaces to organize storage is said to use _namespaced storage_. - -A _namespace id_ is a string that identifies a namespace in a contract. It should not contain any whitespace characters. - -### `@custom:storage-location` - -A namespace in a contract should be implemented as a struct type. These structs should be annotated with the NatSpec tag `@custom:storage-location :`, where `` identifies a formula used to compute the storage location where the namespace is rooted, based on the namespace id. _(Note: The Solidity compiler includes this annotation in the AST since v0.8.20, so this is recommended as the minimum compiler version when using this pattern.)_ Structs with this annotation found outside of contracts are not considered to be namespaces for any contract in the source code. - -### Formula - -The formula identified by `erc7201` is defined as `erc7201(id: string) = keccak256(keccak256(id) - 1) & ~0xff`. In Solidity, this corresponds to the expression `keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff))`. When using this formula the annotation becomes `@custom:storage-location erc7201:`. For example, `@custom:storage-location erc7201:foobar` annotates a namespace with id `"foobar"` rooted at `erc7201("foobar")`. - -Future EIPs may define new formulas with unique formula identifiers. It is recommended to follow the convention set in this EIP and use an identifier of the format `erc1234`. - -## Rationale - -The tree-shaped storage layout used by Solidity and Vyper follows the following grammar (with root=0): - -$L_{root} := \mathit{root} \mid L_{root} + n \mid \texttt{keccak256}(L_{root}) \mid \texttt{keccak256}(H(k) \oplus L_{root}) \mid \texttt{keccak256}(L_{root} \oplus H(k))$ - -A requirement for the root is that it shouldn't overlap with any storage location that would be part of the standard storage tree used by Solidity and Vyper (root = 0), nor should it be part of the storage tree derived from any other namespace (another root). This is so that multiple namespaces may be used alongside each other and alongside the standard storage layout, either deliberately or accidentally, without colliding. The term `keccak256(id) - 1` in the formula is chosen as a location that is unused by Solidity, but this is not used as the final location because namespaces can be larger than 1 slot and would extend into `keccak256(id) + n`, which is potentially used by Solidity. A second hash is added to prevent this and guarantee that namespaces are completely disjoint from standard storage, assuming keccak256 collision resistance and that arrays are not unreasonably large. - -Additionally, namespace locations are aligned to 256 as a potential optimization, in anticipation of gas schedule changes after the Verkle state tree migration, which may cause groups of 256 storage slots to become warm all at once. - -### Naming - -This pattern has sometimes been referred to as "diamond storage". This causes it to be conflated with the "diamond proxy pattern", even though they can be used independently of each other. This EIP has chosen to use a different name to clearly differentiate it from the proxy pattern. - -## Backwards Compatibility - -No backward compatibility issues found. - -## Reference Implementation - -```solidity -pragma solidity ^0.8.20; - -contract Example { - /// @custom:storage-location erc7201:example.main - struct MainStorage { - uint256 x; - uint256 y; - } - - // keccak256(abi.encode(uint256(keccak256("example.main")) - 1)) & ~bytes32(uint256(0xff)); - bytes32 private constant MAIN_STORAGE_LOCATION = - 0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500; - - function _getMainStorage() private pure returns (MainStorage storage $) { - assembly { - $.slot := MAIN_STORAGE_LOCATION - } - } - - function _getXTimesY() internal view returns (uint256) { - MainStorage storage $ = _getMainStorage(); - return $.x * $.y; - } -} -``` - - -## Security Considerations - -Namespaces should avoid collisions with other namespaces or with standard Solidity or Vyper storage layout. The formula defined in this ERC guarantees this property for arbitrary namespace ids under the assumption of keccak256 collision resistance, as discussed in Rationale. - -`@custom:storage-location` is a NatSpec annotation that current compilers don't enforce any rules for or ascribe any meaning to. The contract developer is responsible for implementing the pattern and using the namespace as claimed in the annotation. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7201.md diff --git a/EIPS/eip-721.md b/EIPS/eip-721.md index 2c9c8be488602b..ed5e34d00f5bd0 100644 --- a/EIPS/eip-721.md +++ b/EIPS/eip-721.md @@ -1,447 +1 @@ ---- -eip: 721 -title: Non-Fungible Token Standard -author: William Entriken (@fulldecent), Dieter Shirley , Jacob Evans , Nastassia Sachs -discussions-to: https://github.com/ethereum/eips/issues/721 -type: Standards Track -category: ERC -status: Final -created: 2018-01-24 -requires: 165 ---- - -## Simple Summary - -A standard interface for non-fungible tokens, also known as deeds. - -## Abstract - -The following standard allows for the implementation of a standard API for NFTs within smart contracts. This standard provides basic functionality to track and transfer NFTs. - -We considered use cases of NFTs being owned and transacted by individuals as well as consignment to third party brokers/wallets/auctioneers ("operators"). NFTs can represent ownership over digital or physical assets. We considered a diverse universe of assets, and we know you will dream up many more: - -- Physical property — houses, unique artwork -- Virtual collectibles — unique pictures of kittens, collectible cards -- "Negative value" assets — loans, burdens and other responsibilities - -In general, all houses are distinct and no two kittens are alike. NFTs are *distinguishable* and you must track the ownership of each one separately. - -## Motivation - -A standard interface allows wallet/broker/auction applications to work with any NFT on Ethereum. We provide for simple ERC-721 smart contracts as well as contracts that track an *arbitrarily large* number of NFTs. Additional applications are discussed below. - -This standard is inspired by the ERC-20 token standard and builds on two years of experience since EIP-20 was created. EIP-20 is insufficient for tracking NFTs because each asset is distinct (non-fungible) whereas each of a quantity of tokens is identical (fungible). - -Differences between this standard and EIP-20 are examined below. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -**Every ERC-721 compliant contract must implement the `ERC721` and `ERC165` interfaces** (subject to "caveats" below): - -```solidity -pragma solidity ^0.4.20; - -/// @title ERC-721 Non-Fungible Token Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-721 -/// Note: the ERC-165 identifier for this interface is 0x80ac58cd. -interface ERC721 /* is ERC165 */ { - /// @dev This emits when ownership of any NFT changes by any mechanism. - /// This event emits when NFTs are created (`from` == 0) and destroyed - /// (`to` == 0). Exception: during contract creation, any number of NFTs - /// may be created and assigned without emitting Transfer. At the time of - /// any transfer, the approved address for that NFT (if any) is reset to none. - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - /// @dev This emits when the approved address for an NFT is changed or - /// reaffirmed. The zero address indicates there is no approved address. - /// When a Transfer event emits, this also indicates that the approved - /// address for that NFT (if any) is reset to none. - event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); - - /// @dev This emits when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - - /// @notice Count all NFTs assigned to an owner - /// @dev NFTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param _owner An address for whom to query the balance - /// @return The number of NFTs owned by `_owner`, possibly zero - function balanceOf(address _owner) external view returns (uint256); - - /// @notice Find the owner of an NFT - /// @dev NFTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param _tokenId The identifier for an NFT - /// @return The address of the owner of the NFT - function ownerOf(uint256 _tokenId) external view returns (address); - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. When transfer is complete, this function - /// checks if `_to` is a smart contract (code size > 0). If so, it calls - /// `onERC721Received` on `_to` and throws if the return value is not - /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - /// @param data Additional data with no specified format, sent in call to `_to` - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev This works identically to the other function with an extra data parameter, - /// except this function just sets data to "". - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE - /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE - /// THEY MAY BE PERMANENTLY LOST - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Change or reaffirm the approved address for an NFT - /// @dev The zero address indicates there is no approved address. - /// Throws unless `msg.sender` is the current NFT owner, or an authorized - /// operator of the current owner. - /// @param _approved The new approved NFT controller - /// @param _tokenId The NFT to approve - function approve(address _approved, uint256 _tokenId) external payable; - - /// @notice Enable or disable approval for a third party ("operator") to manage - /// all of `msg.sender`'s assets - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// multiple operators per owner. - /// @param _operator Address to add to the set of authorized operators - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAll(address _operator, bool _approved) external; - - /// @notice Get the approved address for a single NFT - /// @dev Throws if `_tokenId` is not a valid NFT. - /// @param _tokenId The NFT to find the approved address for - /// @return The approved address for this NFT, or the zero address if there is none - function getApproved(uint256 _tokenId) external view returns (address); - - /// @notice Query if an address is an authorized operator for another address - /// @param _owner The address that owns the NFTs - /// @param _operator The address that acts on behalf of the owner - /// @return True if `_operator` is an approved operator for `_owner`, false otherwise - function isApprovedForAll(address _owner, address _operator) external view returns (bool); -} - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} -``` - -A wallet/broker/auction application MUST implement the **wallet interface** if it will accept safe transfers. - -```solidity -/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. -interface ERC721TokenReceiver { - /// @notice Handle the receipt of an NFT - /// @dev The ERC721 smart contract calls this function on the recipient - /// after a `transfer`. This function MAY throw to revert and reject the - /// transfer. Return of other than the magic value MUST result in the - /// transaction being reverted. - /// Note: the contract address is always the message sender. - /// @param _operator The address which called `safeTransferFrom` function - /// @param _from The address which previously owned the token - /// @param _tokenId The NFT identifier which is being transferred - /// @param _data Additional data with no specified format - /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - /// unless throwing - function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4); -} -``` - -The **metadata extension** is OPTIONAL for ERC-721 smart contracts (see "caveats", below). This allows your smart contract to be interrogated for its name and for details about the assets which your NFTs represent. - -```solidity -/// @title ERC-721 Non-Fungible Token Standard, optional metadata extension -/// @dev See https://eips.ethereum.org/EIPS/eip-721 -/// Note: the ERC-165 identifier for this interface is 0x5b5e139f. -interface ERC721Metadata /* is ERC721 */ { - /// @notice A descriptive name for a collection of NFTs in this contract - function name() external view returns (string _name); - - /// @notice An abbreviated name for NFTs in this contract - function symbol() external view returns (string _symbol); - - /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. - /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC - /// 3986. The URI may point to a JSON file that conforms to the "ERC721 - /// Metadata JSON Schema". - function tokenURI(uint256 _tokenId) external view returns (string); -} -``` - -This is the "ERC721 Metadata JSON Schema" referenced above. - -```json -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - } - } -} -``` - -The **enumeration extension** is OPTIONAL for ERC-721 smart contracts (see "caveats", below). This allows your contract to publish its full list of NFTs and make them discoverable. - -```solidity -/// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension -/// @dev See https://eips.ethereum.org/EIPS/eip-721 -/// Note: the ERC-165 identifier for this interface is 0x780e9d63. -interface ERC721Enumerable /* is ERC721 */ { - /// @notice Count NFTs tracked by this contract - /// @return A count of valid NFTs tracked by this contract, where each one of - /// them has an assigned and queryable owner not equal to the zero address - function totalSupply() external view returns (uint256); - - /// @notice Enumerate valid NFTs - /// @dev Throws if `_index` >= `totalSupply()`. - /// @param _index A counter less than `totalSupply()` - /// @return The token identifier for the `_index`th NFT, - /// (sort order not specified) - function tokenByIndex(uint256 _index) external view returns (uint256); - - /// @notice Enumerate NFTs assigned to an owner - /// @dev Throws if `_index` >= `balanceOf(_owner)` or if - /// `_owner` is the zero address, representing invalid NFTs. - /// @param _owner An address where we are interested in NFTs owned by them - /// @param _index A counter less than `balanceOf(_owner)` - /// @return The token identifier for the `_index`th NFT assigned to `_owner`, - /// (sort order not specified) - function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256); -} -``` - -### Caveats - -The 0.4.20 Solidity interface grammar is not expressive enough to document the ERC-721 standard. A contract which complies with ERC-721 MUST also abide by the following: - -- Solidity issue #3412: The above interfaces include explicit mutability guarantees for each function. Mutability guarantees are, in order weak to strong: `payable`, implicit nonpayable, `view`, and `pure`. Your implementation MUST meet the mutability guarantee in this interface and you MAY meet a stronger guarantee. For example, a `payable` function in this interface may be implemented as nonpayable (no state mutability specified) in your contract. We expect a later Solidity release will allow your stricter contract to inherit from this interface, but a workaround for version 0.4.20 is that you can edit this interface to add stricter mutability before inheriting from your contract. -- Solidity issue #3419: A contract that implements `ERC721Metadata` or `ERC721Enumerable` SHALL also implement `ERC721`. ERC-721 implements the requirements of interface ERC-165. -- Solidity issue #2330: If a function is shown in this specification as `external` then a contract will be compliant if it uses `public` visibility. As a workaround for version 0.4.20, you can edit this interface to switch to `public` before inheriting from your contract. -- Solidity issues #3494, #3544: Use of `this.*.selector` is marked as a warning by Solidity, a future version of Solidity will not mark this as an error. - -*If a newer version of Solidity allows the caveats to be expressed in code, then this EIP MAY be updated and the caveats removed, such will be equivalent to the original specification.* - -## Rationale - -There are many proposed uses of Ethereum smart contracts that depend on tracking distinguishable assets. Examples of existing or planned NFTs are LAND in Decentraland, the eponymous punks in CryptoPunks, and in-game items using systems like DMarket or EnjinCoin. Future uses include tracking real-world assets, like real-estate (as envisioned by companies like Ubitquity or Propy). It is critical in each of these cases that these items are not "lumped together" as numbers in a ledger, but instead each asset must have its ownership individually and atomically tracked. Regardless of the nature of these assets, the ecosystem will be stronger if we have a standardized interface that allows for cross-functional asset management and sales platforms. - -**"NFT" Word Choice** - -"NFT" was satisfactory to nearly everyone surveyed and is widely applicable to a broad universe of distinguishable digital assets. We recognize that "deed" is very descriptive for certain applications of this standard (notably, physical property). - -*Alternatives considered: distinguishable asset, title, token, asset, equity, ticket* - -**NFT Identifiers** - -Every NFT is identified by a unique `uint256` ID inside the ERC-721 smart contract. This identifying number SHALL NOT change for the life of the contract. The pair `(contract address, uint256 tokenId)` will then be a globally unique and fully-qualified identifier for a specific asset on an Ethereum chain. While some ERC-721 smart contracts may find it convenient to start with ID 0 and simply increment by one for each new NFT, callers SHALL NOT assume that ID numbers have any specific pattern to them, and MUST treat the ID as a "black box". Also note that NFTs MAY become invalid (be destroyed). Please see the enumeration functions for a supported enumeration interface. - -The choice of `uint256` allows a wide variety of applications because UUIDs and sha3 hashes are directly convertible to `uint256`. - -**Transfer Mechanism** - -ERC-721 standardizes a safe transfer function `safeTransferFrom` (overloaded with and without a `bytes` parameter) and an unsafe function `transferFrom`. Transfers may be initiated by: - -- The owner of an NFT -- The approved address of an NFT -- An authorized operator of the current owner of an NFT - -Additionally, an authorized operator may set the approved address for an NFT. This provides a powerful set of tools for wallet, broker and auction applications to quickly use a *large* number of NFTs. - -The transfer and accept functions' documentation only specify conditions when the transaction MUST throw. Your implementation MAY also throw in other situations. This allows implementations to achieve interesting results: - -- **Disallow transfers if the contract is paused** — prior art, CryptoKitties deployed contract, line 611 -- **Blocklist certain address from receiving NFTs** — prior art, CryptoKitties deployed contract, lines 565, 566 -- **Disallow unsafe transfers** — `transferFrom` throws unless `_to` equals `msg.sender` or `countOf(_to)` is non-zero or was non-zero previously (because such cases are safe) -- **Charge a fee to both parties of a transaction** — require payment when calling `approve` with a non-zero `_approved` if it was previously the zero address, refund payment if calling `approve` with the zero address if it was previously a non-zero address, require payment when calling any transfer function, require transfer parameter `_to` to equal `msg.sender`, require transfer parameter `_to` to be the approved address for the NFT -- **Read only NFT registry** — always throw from `safeTransferFrom`, `transferFrom`, `approve` and `setApprovalForAll` - -Failed transactions will throw, a best practice identified in ERC-223, ERC-677, ERC-827 and OpenZeppelin's implementation of SafeERC20.sol. ERC-20 defined an `allowance` feature, this caused a problem when called and then later modified to a different amount, as on OpenZeppelin issue \#438. In ERC-721, there is no allowance because every NFT is unique, the quantity is none or one. Therefore we receive the benefits of ERC-20's original design without problems that have been later discovered. - -Creation of NFTs ("minting") and destruction of NFTs ("burning") is not included in the specification. Your contract may implement these by other means. Please see the `event` documentation for your responsibilities when creating or destroying NFTs. - -We questioned if the `operator` parameter on `onERC721Received` was necessary. In all cases we could imagine, if the operator was important then that operator could transfer the token to themself and then send it -- then they would be the `from` address. This seems contrived because we consider the operator to be a temporary owner of the token (and transferring to themself is redundant). When the operator sends the token, it is the operator acting on their own accord, NOT the operator acting on behalf of the token holder. This is why the operator and the previous token owner are both significant to the token recipient. - -*Alternatives considered: only allow two-step ERC-20 style transaction, require that transfer functions never throw, require all functions to return a boolean indicating the success of the operation.* - -**ERC-165 Interface** - -We chose Standard Interface Detection (ERC-165) to expose the interfaces that a ERC-721 smart contract supports. - -A future EIP may create a global registry of interfaces for contracts. We strongly support such an EIP and it would allow your ERC-721 implementation to implement `ERC721Enumerable`, `ERC721Metadata`, or other interfaces by delegating to a separate contract. - -**Gas and Complexity** (regarding the enumeration extension) - -This specification contemplates implementations that manage a few and *arbitrarily large* numbers of NFTs. If your application is able to grow then avoid using for/while loops in your code (see CryptoKitties bounty issue \#4). These indicate your contract may be unable to scale and gas costs will rise over time without bound. - -We have deployed a contract, XXXXERC721, to Testnet which instantiates and tracks 340282366920938463463374607431768211456 different deeds (2^128). That's enough to assign every IPV6 address to an Ethereum account owner, or to track ownership of nanobots a few micron in size and in aggregate totalling half the size of Earth. You can query it from the blockchain. And every function takes less gas than querying the ENS. - -This illustration makes clear: the ERC-721 standard scales. - -*Alternatives considered: remove the asset enumeration function if it requires a for-loop, return a Solidity array type from enumeration functions.* - -**Privacy** - -Wallets/brokers/auctioneers identified in the motivation section have a strong need to identify which NFTs an owner owns. - -It may be interesting to consider a use case where NFTs are not enumerable, such as a private registry of property ownership, or a partially-private registry. However, privacy cannot be attained because an attacker can simply (!) call `ownerOf` for every possible `tokenId`. - -**Metadata Choices** (metadata extension) - -We have required `name` and `symbol` functions in the metadata extension. Every token EIP and draft we reviewed (ERC-20, ERC-223, ERC-677, ERC-777, ERC-827) included these functions. - -We remind implementation authors that the empty string is a valid response to `name` and `symbol` if you protest to the usage of this mechanism. We also remind everyone that any smart contract can use the same name and symbol as *your* contract. How a client may determine which ERC-721 smart contracts are well-known (canonical) is outside the scope of this standard. - -A mechanism is provided to associate NFTs with URIs. We expect that many implementations will take advantage of this to provide metadata for each NFT. The image size recommendation is taken from Instagram, they probably know much about image usability. The URI MAY be mutable (i.e. it changes from time to time). We considered an NFT representing ownership of a house, in this case metadata about the house (image, occupants, etc.) can naturally change. - -Metadata is returned as a string value. Currently this is only usable as calling from `web3`, not from other contracts. This is acceptable because we have not considered a use case where an on-blockchain application would query such information. - -*Alternatives considered: put all metadata for each asset on the blockchain (too expensive), use URL templates to query metadata parts (URL templates do not work with all URL schemes, especially P2P URLs), multiaddr network address (not mature enough)* - -**Community Consensus** - -A significant amount of discussion occurred on the original ERC-721 issue, additionally we held a first live meeting on Gitter that had good representation and well advertised (on Reddit, in the Gitter #ERC channel, and the original ERC-721 issue). Thank you to the participants: - -- [@ImAllInNow](https://github.com/imallinnow) Rob from DEC Gaming / Presenting Michigan Ethereum Meetup Feb 7 -- [@Arachnid](https://github.com/arachnid) Nick Johnson -- [@jadhavajay](https://github.com/jadhavajay) Ajay Jadhav from AyanWorks -- [@superphly](https://github.com/superphly) Cody Marx Bailey - XRAM Capital / Sharing at hackathon Jan 20 / UN Future of Finance Hackathon. -- [@fulldecent](https://github.com/fulldecent) William Entriken - -A second event was held at ETHDenver 2018 to discuss distinguishable asset standards (notes to be published). - -We have been very inclusive in this process and invite anyone with questions or contributions into our discussion. However, this standard is written only to support the identified use cases which are listed herein. - -## Backwards Compatibility - -We have adopted `balanceOf`, `totalSupply`, `name` and `symbol` semantics from the ERC-20 specification. An implementation may also include a function `decimals` that returns `uint8(0)` if its goal is to be more compatible with ERC-20 while supporting this standard. However, we find it contrived to require all ERC-721 implementations to support the `decimals` function. - -Example NFT implementations as of February 2018: - -- CryptoKitties -- Compatible with an earlier version of this standard. -- CryptoPunks -- Partially ERC-20 compatible, but not easily generalizable because it includes auction functionality directly in the contract and uses function names that explicitly refer to the assets as "punks". -- Auctionhouse Asset Interface -- The author needed a generic interface for the Auctionhouse ÐApp (currently ice-boxed). His "Asset" contract is very simple, but is missing ERC-20 compatibility, `approve()` functionality, and metadata. This effort is referenced in the discussion for EIP-173. - -Note: "Limited edition, collectible tokens" like Curio Cards and Rare Pepe are *not* distinguishable assets. They're actually a collection of individual fungible tokens, each of which is tracked by its own smart contract with its own total supply (which may be `1` in extreme cases). - -The `onERC721Received` function specifically works around old deployed contracts which may inadvertently return 1 (`true`) in certain circumstances even if they don't implement a function (see Solidity DelegateCallReturnValue bug). By returning and checking for a magic value, we are able to distinguish actual affirmative responses versus these vacuous `true`s. - -## Test Cases - -0xcert ERC-721 Token includes test cases written using Truffle. - -## Implementations - -0xcert ERC721 -- a reference implementation - -- MIT licensed, so you can freely use it for your projects -- Includes test cases -- Active bug bounty, you will be paid if you find errors - -Su Squares -- an advertising platform where you can rent space and place images - -- Complete the Su Squares Bug Bounty Program to seek problems with this standard or its implementation -- Implements the complete standard and all optional interfaces - -ERC721ExampleDeed -- an example implementation - -- Implements using the OpenZeppelin project format - -XXXXERC721, by William Entriken -- a scalable example implementation - -- Deployed on testnet with 1 billion assets and supporting all lookups with the metadata extension. This demonstrates that scaling is NOT a problem. - -## References - -**Standards** - -1. [ERC-20](./eip-20.md) Token Standard. -1. [ERC-165](./eip-165.md) Standard Interface Detection. -1. [ERC-173](./eip-173.md) Owned Standard. -1. [ERC-223](https://github.com/ethereum/EIPs/issues/223) Token Standard. -1. [ERC-677](https://github.com/ethereum/EIPs/issues/677) `transferAndCall` Token Standard. -1. [ERC-827](https://github.com/ethereum/EIPs/issues/827) Token Standard. -1. Ethereum Name Service (ENS). https://ens.domains -1. Instagram -- What's the Image Resolution? https://help.instagram.com/1631821640426723 -1. JSON Schema. https://json-schema.org/ -1. Multiaddr. https://github.com/multiformats/multiaddr -1. RFC 2119 Key words for use in RFCs to Indicate Requirement Levels. https://www.ietf.org/rfc/rfc2119.txt - -**Issues** - -1. The Original ERC-721 Issue. https://github.com/ethereum/eips/issues/721 -1. Solidity Issue \#2330 -- Interface Functions are External. https://github.com/ethereum/solidity/issues/2330 -1. Solidity Issue \#3412 -- Implement Interface: Allow Stricter Mutability. https://github.com/ethereum/solidity/issues/3412 -1. Solidity Issue \#3419 -- Interfaces Can't Inherit. https://github.com/ethereum/solidity/issues/3419 -1. Solidity Issue \#3494 -- Compiler Incorrectly Reasons About the `selector` Function. https://github.com/ethereum/solidity/issues/3494 -1. Solidity Issue \#3544 -- Cannot Calculate Selector of Function Named `transfer`. https://github.com/ethereum/solidity/issues/3544 -1. CryptoKitties Bounty Issue \#4 -- Listing all Kitties Owned by a User is `O(n^2)`. https://github.com/axiomzen/cryptokitties-bounty/issues/4 -1. OpenZeppelin Issue \#438 -- Implementation of `approve` method violates ERC20 standard. https://github.com/OpenZeppelin/zeppelin-solidity/issues/438 -1. Solidity DelegateCallReturnValue Bug. https://solidity.readthedocs.io/en/develop/bugs.html#DelegateCallReturnValue - -**Discussions** - -1. Reddit (announcement of first live discussion). https://www.reddit.com/r/ethereum/comments/7r2ena/friday_119_live_discussion_on_erc_nonfungible/ -1. Gitter #EIPs (announcement of first live discussion). https://gitter.im/ethereum/EIPs?at=5a5f823fb48e8c3566f0a5e7 -1. ERC-721 (announcement of first live discussion). https://github.com/ethereum/eips/issues/721#issuecomment-358369377 -1. ETHDenver 2018. https://ethdenver.com - -**NFT Implementations and Other Projects** - -1. CryptoKitties. https://www.cryptokitties.co -1. 0xcert ERC-721 Token. https://github.com/0xcert/ethereum-erc721 -1. Su Squares. https://tenthousandsu.com -1. Decentraland. https://decentraland.org -1. CryptoPunks. https://www.larvalabs.com/cryptopunks -1. DMarket. https://www.dmarket.io -1. Enjin Coin. https://enjincoin.io -1. Ubitquity. https://www.ubitquity.io -1. Propy. https://tokensale.propy.com -1. CryptoKitties Deployed Contract. https://etherscan.io/address/0x06012c8cf97bead5deae237070f9587f8e7a266d#code -1. Su Squares Bug Bounty Program. https://github.com/fulldecent/su-squares-bounty -1. XXXXERC721. https://github.com/fulldecent/erc721-example -1. ERC721ExampleDeed. https://github.com/nastassiasachs/ERC721ExampleDeed -1. Curio Cards. https://mycuriocards.com -1. Rare Pepe. https://rarepepewallet.com -1. Auctionhouse Asset Interface. https://github.com/dob/auctionhouse/blob/master/contracts/Asset.sol -1. OpenZeppelin SafeERC20.sol Implementation. https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC20/SafeERC20.sol - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-721.md diff --git a/EIPS/eip-7231.md b/EIPS/eip-7231.md index 9d728f999ce022..e2ce35575bae56 100644 --- a/EIPS/eip-7231.md +++ b/EIPS/eip-7231.md @@ -1,175 +1 @@ ---- -eip: 7231 -title: Identity aggregated NFT -description: The aggregation of web2 & web3 identities to NFTs, authorized by individuals, gives attributes of ownerships, relationships, experiences. -author: Chloe Gu , Navid X. (@xuxinlai2002), Victor Yu , Archer H. -discussions-to: https://ethereum-magicians.org/t/erc7231-identity-aggregated-nft/15062 -status: Review -type: Standards Track -category: ERC -created: 2023-06-25 -requires: 165, 721, 1271 ---- - -## Abstract - -This standard extends [ERC-721](./eip-721.md) by binding individuals' Web2 and Web3 identities to non-fungible tokens (NFTs) and soulbound tokens (SBTs). By binding multiple identities, aggregated and composible identity infomation can be verified, resulting in more beneficial onchain scenarios for individuals, such as self-authentication, social overlapping, commercial value generation from user targetting, etc. By adding a custom schema in the metadata, and updating and verifying the schema hash in the contract, the binding of NFT and identity information is completed. - -## Motivation - -One of the most interesting aspects of Web3 is the ability to bring an individual's own identity to different applications. Even more powerful is the fact that individuals truly own their accounts without relying on centralized gatekeepers, disclosing to different apps components necessary for authentication and approved by individuals. -Exisiting solutions such as ENS, although open, decentralized, and more convenient for Ethereum-based applications, suffer from a lack of data standardization and authentication of identity due to inherent anominity. Other solutions such as SBTs rely on centralized attestors, can not prevent data tampering, and do not inscribe data into the ledger itself in a privacy enabling way. -The proposed pushes the boundaries of solving identity problems with Identity Aggregated NFT, i.e., the individual-authenticated aggregation of web2 and web3 identities to NFTs (SBTs included). - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Every compliant contract must implement the Interface - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.15; - -interface IERC7231 { - - /** - * @notice emit the use binding informain - * @param id nft id - * @param identitiesRoot new identity root - */ - event SetIdentitiesRoot( - uint256 id, - bytes32 identitiesRoot - ); - - /** - * @notice - * @dev set the user ID binding information of NFT with identitiesRoot - * @param id nft id - * @param identitiesRoot multi UserID Root data hash - * MUST allow external calls - */ - function setIdentitiesRoot( - uint256 id, - bytes32 identitiesRoot - ) external; - - /** - * @notice - * @dev get the multi-userID root by NFTID - * @param id nft id - * MUST return the bytes32 multiUserIDsRoot - * MUST NOT modify the state - * MUST allow external calls - */ - function getIdentitiesRoot( - uint256 id - ) external returns(bytes32); - - /** - * @notice - * @dev verify the userIDs binding - * @param id nft id - * @param userIDs userIDs for check - * @param identitiesRoot msg hash to veriry - * @param signature ECDSA signature - * MUST If the verification is passed, return true, otherwise return false - * MUST NOT modify the state - * MUST allow external calls - */ - function verifyIdentitiesBinding( - uint256 id,address nftOwnerAddress,string[] memory userIDs,bytes32 identitiesRoot, bytes calldata signature - ) external returns (bool); -} -``` - -This is the “Metadata JSON Schema” referenced above. - -```json -{ - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image" - }, - "MultiIdentities": [ - { - "userID": { - "type": "string", - "description": "User ID of Web2 and web3(DID)" - }, - "verifierUri": { - "type": "string", - "description": "Verifier Uri of the userID" - }, - "memo": { - "type": "string", - "description": "Memo of the userID" - }, - "properties": { - "type": "object", - "description": "properties of the user ID information" - } - } - ] - } -} -``` - -## Rationale - -Designing the proposal, we considered the following problems that are solved by this standard: -![EIP Flow Diagram](../assets/eip-7231/img/Identity-aggregated-NFT-flow.png) - -1. Resolve the issue of multiple ID bindings for web2 and web3. -By incorporating the MultiIdentities schema into the metadata file, an authorized bond is established between user identity information and NFTs. This schema encompasses a userID field that can be sourced from a variety of web2 platforms or a decentralized identity (DID) created on blockchain. By binding the NFT ID with the UserIDInfo array, it becomes possible to aggregate multiple identities seamlessly. -1. Users have full ownership and control of their data -Once the user has set the metadata, they can utilize the setIdentitiesRoot function to establish a secure binding between hashed userIDs objects and NFT ID. As only the user holds the authority to carry out this binding, it can be assured that the data belongs solely to the user. -1. Verify the binding relationship between data on-chain and off-chain data through signature based on [ERC-1271](./eip-1271.md) -Through the signature method based on the [ERC-1271](./eip-1271.md) protocol, the verifyIdentiesBinding function of this EIP realizes the binding of the userID and NFT owner address between on-chain and off-chain. - 1. NFT ownership validation - 2. UserID format validation - 3. IdentitiesRoot Consistency verification - 4. Signature validation from nft owner - -As for how to verify the authenticity of the individuals' identities, wallets, accounts, there are various methods, such as zk-based DID authentication onchain, and offchain authentication algorithms, such as auth2, openID2, etc. - -## Backwards Compatibility - -As mentioned in the specifications section, this standard can be fully [ERC-721](./eip-721.md) compatible by adding an extension function set. -In addition, new functions introduced in this standard have many similarities with the existing functions in [ERC-721](./eip-721.md). This allows developers to easily adopt the standard quickly. - -## Test Cases - -Tests are included in [`erc7231.ts`](../assets/eip-7231/test/erc7231.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-7231 -npm install -npx hardhat test -``` - -## Reference Implementation - -`ERC7231.sol` Implementation: [`ERC7231.sol`](../assets/eip-7231/contracts/ERC7231.sol) - -## Security Considerations - -This EIP standard can comprehensively empower individuals to have ownership and control of their identities, wallets, and relevant data by themselves adding or removing the NFTs and identity bound information. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7231.md diff --git a/EIPS/eip-725.md b/EIPS/eip-725.md index 64d68734b8907f..92d2f346d66f3c 100644 --- a/EIPS/eip-725.md +++ b/EIPS/eip-725.md @@ -1,318 +1 @@ ---- -eip: 725 -title: General data key/value store and execution -description: An interface for a smart contract based account with attachable data key/value store -author: Fabian Vogelsteller (@frozeman), Tyler Yasaka (@tyleryasaka) -discussions-to: https://ethereum-magicians.org/t/discussion-for-eip725/12158 -status: Draft -type: Standards Track -category: ERC -created: 2017-10-02 -requires: 165, 173 ---- - -## Abstract - -The following describes two standards that allow for a generic data storage in a smart contract and a generic execution through a smart contract. These can be used separately or in conjunction and can serve as building blocks for smart contract accounts, upgradable metadata, and other means. - -## Motivation - -The initial motivation came out of the need to create a smart contract account system that's flexible enough to be viable long-term but also defined enough to be standardized. They are a generic set of two standardized building blocks to be used in all forms of smart contracts. - -This standard consists of two sub-standards, a generic data key/value store (`ERC725Y`) and a generic execute function (`ERC725X`). Both of these in combination allow for a very flexible and long-lasting account system. The account version of `ERC725` is standardized under `LSP0-ERC725Account`. - -These standards (`ERC725` X and Y) can also be used separately as `ERC725Y` can be used to enhance NFTs and Token metadata or other types of smart contracts. `ERC725X` allows for a generic execution through a smart contract, functioning as an account or actor. - -## Specification - -### Ownership - -This contract is controlled by a single owner. The owner can be a smart contract or an external account. -This standard requires [ERC-173](./eip-173.md) and SHOULD implement the functions: - -- `owner() view` -- `transferOwnership(address newOwner)` - -And the event: - -- `OwnershipTransferred(address indexed previousOwner, address indexed newOwner)` - ---- - -### `ERC725X` - -**`ERC725X`** interface id according to [ERC-165](./eip-165.md): `0x7545acac`. - -Smart contracts implementing the `ERC725X` standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface(..)` function and MUST support the `ERC165` and `ERC725X` interface ids. - -### `ERC725X` Methods - -Smart contracts implementing the `ERC725X` standard SHOULD implement all of the functions listed below: - -#### execute - -```solidity -function execute(uint256 operationType, address target, uint256 value, bytes memory data) external payable returns(bytes memory) -``` - -Function Selector: `0x44c028fe` - -Executes a call on any other smart contracts or address, transfers the blockchains native token, or deploys a new smart contract. - - -_Parameters:_ - -- `operationType`: the operation type used to execute. -- `target`: the smart contract or address to call. `target` will be unused if a contract is created (operation types 1 and 2). -- `value`: the amount of native tokens to transfer (in Wei). -- `data`: the call data, or the creation bytecode of the contract to deploy. - - -_Requirements:_ - -- MUST only be called by the current owner of the contract. -- MUST revert when the execution or the contract creation fails. -- `target` SHOULD be address(0) in case of contract creation with `CREATE` and `CREATE2` (operation types 1 and 2). -- `value` SHOULD be zero in case of `STATICCALL` or `DELEGATECALL` (operation types 3 and 4). - - -_Returns:_ `bytes` , the returned data of the called function, or the address of the contract deployed (operation types 1 and 2). - -**Triggers Event:** [ContractCreated](#contractcreated), [Executed](#executed) - -The following `operationType` COULD exist: - -- `0` for `CALL` -- `1` for `CREATE` -- `2` for `CREATE2` -- `3` for `STATICCALL` -- `4` for `DELEGATECALL` - **NOTE** This is a potentially dangerous operation type - -Others may be added in the future. - -#### data parameter - -- For operationType, `CALL`, `STATICCALL` and `DELEGATECALL` the data field can be random bytes or an abi-encoded function call. - -- For operationType, `CREATE` the `data` field is the creation bytecode of the contract to deploy appended with the constructor argument(s) abi-encoded. - -- For operationType, `CREATE2` the `data` field is the creation bytecode of the contract to deploy appended with: - 1. the constructor argument(s) abi-encoded - 2. a `bytes32` salt. - -``` -data = + + -``` - -> See [EIP-1014: Skinny CREATE2](./eip-1014.md) for more information. - -#### executeBatch - -```solidity -function executeBatch(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes[] memory datas) external payable returns(bytes[] memory) -``` - -Function Selector: `0x31858452` - -Executes a batch of calls on any other smart contracts, transfers the blockchain native token, or deploys a new smart contract. - -_Parameters:_ - -- `operationsType`: the list of operations type used to execute. -- `targets`: the list of addresses to call. `targets` will be unused if a contract is created (operation types 1 and 2). -- `values`: the list of native token amounts to transfer (in Wei). -- `datas`: the list of call data, or the creation bytecode of the contract to deploy. - -_Requirements:_ - -- Parameters array MUST have the same length. -- MUST only be called by the current owner of the contract. -- MUST revert when the execution or the contract creation fails. -- `target` SHOULD be address(0) in case of contract creation with `CREATE` and `CREATE2` (operation types 1 and 2). -- `value` SHOULD be zero in case of `STATICCALL` or `DELEGATECALL` (operation types 3 and 4). - -_Returns:_ `bytes[]` , array list of returned data of the called function, or the address(es) of the contract deployed (operation types 1 and 2). - -**Triggers Event:** [ContractCreated](#contractcreated), [Executed](#executed) on each call iteration - -### `ERC725X` Events - -#### Executed - -```solidity -event Executed(uint256 indexed operationType, address indexed target, uint256 indexed value, bytes4 data); -``` - -MUST be triggered when `execute` creates a new call using the `operationType` `0`, `3`, `4`. - -#### ContractCreated - -```solidity -event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value, bytes32 salt); -``` - -MUST be triggered when `execute` creates a new contract using the `operationType` `1`, `2`. - ---- - -### `ERC725Y` - -**`ERC725Y`** interface id according to [ERC-165](./eip-165.md): `0x629aa694`. - -Smart contracts implementing the `ERC725Y` standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface(..)` function and MUST support the `ERC165` and `ERC725Y` interface ids. - -### `ERC725Y` Methods - -Smart contracts implementing the `ERC725Y` standard MUST implement all of the functions listed below: - -#### getData - -```solidity -function getData(bytes32 dataKey) external view returns(bytes memory) -``` - -Function Selector: `0x54f6127f` - -Gets the data set for the given data key. - -_Parameters:_ - -- `dataKey`: the data key which value to retrieve. - -_Returns:_ `bytes` , The data for the requested data key. - -#### getDataBatch - -```solidity -function getDataBatch(bytes32[] memory dataKeys) external view returns(bytes[] memory) -``` - -Function Selector: `0xdedff9c6` - -Gets array of data at multiple given data keys. - -_Parameters:_ - -- `dataKeys`: the data keys which values to retrieve. - -_Returns:_ `bytes[]` , array of data values for the requested data keys. - -#### setData - -```solidity -function setData(bytes32 dataKey, bytes memory dataValue) external -``` - -Function Selector: `0x7f23690c` - -Sets data as bytes in the storage for a single data key. - -_Parameters:_ - -- `dataKey`: the data key which value to set. -- `dataValue`: the data to store. - -_Requirements:_ - -- MUST only be called by the current owner of the contract. - -**Triggers Event:** [DataChanged](#datachanged) - -#### setDataBatch - -```solidity -function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) external -``` - -Function Selector: `0x97902421` - -Sets array of data at multiple data keys. MUST only be called by the current owner of the contract. - -_Parameters:_ - -- `dataKeys`: the data keys which values to set. -- `dataValues`: the array of bytes to set. - -_Requirements:_ - -- Array parameters MUST have the same length. -- MUST only be called by the current owner of the contract. - -**Triggers Event:** [DataChanged](#datachanged) - -### `ERC725Y` Events - -#### DataChanged - -```solidity -event DataChanged(bytes32 indexed dataKey, bytes dataValue) -``` - -MUST be triggered when a data key was successfully set. - -### `ERC725Y` Data keys - -Data keys, are the way to retrieve values via `getData()`. These `bytes32` values can be freely chosen, or defined by a standard. -A common way to define data keys is the hash of a word, e.g. `keccak256('ERCXXXMyNewKeyType')` which results in: `0x6935a24ea384927f250ee0b954ed498cd9203fc5d2bf95c735e52e6ca675e047` - -The `LSP2-ERC725JSONSchema` standard is a more explicit `ERC725Y` data key standard, that defines key types and value types, and their encoding and decoding. - -## Rationale - -The generic way of storing data keys with values was chosen to allow upgradability over time. Stored data values can be changed over time. Other smart contract protocols can then interpret this data in new ways and react to interactions from a `ERC725` smart contract differently. - -The data stored in an `ERC725Y` smart contract is not only readable/writable by off-chain applications, but also by other smart contracts. Function overloading was used to allow for the retrievable of single and multiple keys, to keep gas costs minimal for both use cases. - -## Backwards Compatibility - -All contracts since `ERC725v2` from 2018/19 should be compatible with the current version of the standard. Mainly interface ID and Event parameters have changed, while `getData(bytes32[])` and `setData(bytes32[], bytes[])` was added as an efficient way to set/get multiple keys at once. The same applies to execution, as `execute(..[])` was added as an efficient way to batch calls. - -From 2023 onward, overloading was removed from `ERC-725` (including `ERC725-X` and `ERC725-Y`). This is because, while overloading is accommodated in Solidity, it isn't broadly supported across most blockchain languages. In order to make the standard language-independent, it was decided to shift from overloading to simply attach the term "Batch" to the functions that accept an array as parameters. - -## Reference Implementation - -Reference implementations can be found in [`ERC725.sol`](../assets/eip-725/ERC725.sol). - -## Security Considerations - -This contract allows generic executions, therefore special care needs to be taken to prevent re-entrancy attacks and other forms of call chain attacks. - -When using the operation type `4` for `delegatecall`, it is important to consider that the called contracts can alter the state of the calling contract and also change owner variables and `ERC725Y` data storage entries at will. Additionally calls to `selfdestruct` are possible and other harmful state-changing operations. - -### Solidity Interfaces - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.5.0 <0.7.0; - -// ERC165 identifier: `0x7545acac` -interface IERC725X /* is ERC165, ERC173 */ { - - event Executed(uint256 indexed operationType, address indexed target, uint256 indexed value, bytes4 data); - event ContractCreated(uint256 indexed operationType, address indexed contractAddress, uint256 indexed value, bytes32 salt); - - - function execute(uint256 operationType, address target, uint256 value, bytes memory data) external payable returns(bytes memory); - - function executeBatch(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes memory datas) external payable returns(bytes[] memory); -} - -// ERC165 identifier: `0x629aa694` -interface IERC725Y /* is ERC165, ERC173 */ { - - event DataChanged(bytes32 indexed dataKey, bytes dataValue); - - function getData(bytes32 dataKey) external view returns(bytes memory); - function getDataBatch(bytes32[] memory dataKeys) external view returns(bytes[] memory); - - function setData(bytes32 dataKey, bytes memory dataValue) external; - function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) external; -} -interface IERC725 /* is IERC725X, IERC725Y */ { -} -``` - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-725.md diff --git a/EIPS/eip-7303.md b/EIPS/eip-7303.md index 48590de9350113..45c0ba1e2e6460 100644 --- a/EIPS/eip-7303.md +++ b/EIPS/eip-7303.md @@ -1,177 +1 @@ ---- -eip: 7303 -title: Token-Controlled Token Circulation -description: Access control scheme based on token ownership. -author: Ko Fujimura (@kofujimura) -discussions-to: https://ethereum-magicians.org/t/erc-7303-token-controlled-token-circulation/15020 -status: Draft -type: Standards Track -category: ERC -created: 2023-07-09 -requires: 721, 1155, 5679 ---- -## Abstract - -This ERC introduces an access control scheme termed Token-Controlled Token Circulation (TCTC). By representing the privileges associated with a role as an [ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md) token (referred to as a `control token`), the processes of granting or revoking a role can be facilitated through the minting or burning of the corresponding `control token`. - -## Motivation - -There are numerous methods to implement access control for privileged actions. A commonly utilized pattern is "role-based" access control as specified in [ERC-5982](./eip-5982.md). This method, however, necessitates the use of an off-chain management tool to grant or revoke required roles through its interface. Additionally, as many wallets lack a user interface that displays the privileges granted by a role, users are often unable to comprehend the status of their privileges through the wallet. - -### Use Cases - -This ERC is applicable in many scenarios where role-based access control as described in [ERC-5982](./eip-5982.md) is used. Specific use cases include: - -**Mint/Burn Permission:** -In applications that circulate items such as tickets, coupons, membership cards, and site access rights as tokens, it is necessary to provide the system administrator with the authority to mint or burn these tokens. These permissions can be realized as `control tokens` in this scheme. - -**Transfer Permission:** -In some situations within these applications, it may be desirable to limit the ability to transfer tokens to specific agencies. In these cases, an agency certificate is issued as a `control token`. The ownership of this `control token` then provides the means to regulate token transfers. - -**Address Verification:** -Many applications require address verification to prevent errors in the recipient's address when minting or transferring target tokens. A `control token` is issued as proof of address verification to users, which is required by the recipient when a mint or transfer transaction is executed, thus preventing misdeliveries. In some instances, this `control token` for address verification may be issued by a government agency or specific company after an identity verification process. - -## 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. Smart contracts implementing the [ERC-7303](./eip-7303.md) standard MUST represent the privilege required by the role as an ERC-721 token or ERC-1155 token. The tokens that represent privileges are called `control tokens` in this ERC. The `control token` can be any type of token, and its transactions may be recursively controlled by another `control token`. -2. To associate the required `control token` with the role, the address of the previously deployed contract for the `control token` MUST be used. -3. To ascertain whether an account possesses the necessary role, it SHOULD be confirmed that the balance of the `control token` exceeds 0, utilizing the `balanceOf` method defined in ERC-721 or ERC-1155. Note that the `typeId` must be specified if an ERC-1155 token is used for the `balanceOf` method. -4. To grant a role to an account, a `control token` representing the privilege SHOULD be minted to the account using `safeMint` method defined in [ERC-5679](./eip-5679.md). -5. To revoke a role from an account, the `control token` representing the privilege SHOULD be burned using the `burn` method defined in ERC-5679. -6. A role in a compliant smart contract is represented in the format of `bytes32`. It's RECOMMENDED the value of such role is computed as a `keccak256` hash of a string of the role name, in this format: `bytes32 role = keccak256("")` such as `bytes32 role = keccak256("MINTER")`. - -## Rationale - -The choice to utilize ERC-721 or ERC-1155 token as the control token for privileges enhances visibility of such privileges within wallets, thus simplifying privilege management for users. - -Generally, when realizing privileges as tokens, specifications like Soulbound Token (e.g., [ERC-5192](./eip-5192.md)) are used. Given that ERC-5192 inherits from ERC-721, this ERC has choiced ERC-721 as the requirement for the control token. - -Employing a transferable control token can cater to scenarios where role delegation is necessary. For example, when an authority within an organization is replaced or on vacation, the ability to transfer their privileges to another member becomes possible. The decision to designate the control token as transferable will depend on the specific needs of the application. - -## Backwards Compatibility - -This ERC is designed to be compatible for [ERC-721](./eip-721), [ERC-1155](./eip-1155), and [ERC-5679](./eip-5679) respectively. - -## Reference Implementation - -ERC-7303 provides a modifier to facilitate the implementation of TCTC access control in applications. This modifier checks if an account possesses the necessary role. ERC-7303 also includes a function that grants a specific role to a designated account. - -```solidity -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; - -abstract contract ERC7303 { - struct ERC721Token { - address contractId; - } - - struct ERC1155Token { - address contractId; - uint256 typeId; - } - - mapping (bytes32 => ERC721Token[]) private _ERC721_Contracts; - mapping (bytes32 => ERC1155Token[]) private _ERC1155_Contracts; - - modifier onlyHasToken(bytes32 role, address account) { - require(_checkHasToken(role, account), "ERC7303: not has a required token"); - _; - } - - /** - * @notice Grant a role to user who owns a control token specified by the ERC-721 contractId. - * Multiple calls are allowed, in this case the user must own at least one of the specified token. - * @param role byte32 The role which you want to grant. - * @param contractId address The address of contractId of which token the user required to own. - */ - function _grantRoleByERC721(bytes32 role, address contractId) internal { - require( - IERC165(contractId).supportsInterface(type(IERC721).interfaceId), - "ERC7303: provided contract does not support ERC721 interface" - ); - _ERC721_Contracts[role].push(ERC721Token(contractId)); - } - - /** - * @notice Grant a role to user who owns a control token specified by the ERC-1155 contractId. - * Multiple calls are allowed, in this case the user must own at least one of the specified token. - * @param role byte32 The role which you want to grant. - * @param contractId address The address of contractId of which token the user required to own. - * @param typeId uint256 The token type id that the user required to own. - */ - function _grantRoleByERC1155(bytes32 role, address contractId, uint256 typeId) internal { - require( - IERC165(contractId).supportsInterface(type(IERC1155).interfaceId), - "ERC7303: provided contract does not support ERC1155 interface" - ); - _ERC1155_Contracts[role].push(ERC1155Token(contractId, typeId)); - } - - function _checkHasToken(bytes32 role, address account) internal view returns (bool) { - ERC721Token[] memory ERC721Tokens = _ERC721_Contracts[role]; - for (uint i = 0; i < ERC721Tokens.length; i++) { - if (IERC721(ERC721Tokens[i].contractId).balanceOf(account) > 0) return true; - } - - ERC1155Token[] memory ERC1155Tokens = _ERC1155_Contracts[role]; - for (uint i = 0; i < ERC1155Tokens.length; i++) { - if (IERC1155(ERC1155Tokens[i].contractId).balanceOf(account, ERC1155Tokens[i].typeId) > 0) return true; - } - - return false; - } -} -``` - -The following is a simple example of utilizing `ERC7303` within an ERC-721 token to define "minter" and "burner" roles. Accounts possessing these roles are allowed to create new tokens and destroy existing tokens, facilitated by specifying ERC-721 or ERC-1155 control tokens: - -```solidity -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "./ERC7303.sol"; - -contract MyToken is ERC721, ERC7303 { - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); - - constructor() ERC721("MyToken", "MTK") { - // Specifies the deployed contractId of ERC721 control token. - _grantRoleByERC721(MINTER_ROLE, 0x...); - _grantRoleByERC721(BURNER_ROLE, 0x...); - - // Specifies the deployed contractId and typeId of ERC1155 control token. - _grantRoleByERC1155(MINTER_ROLE, 0x..., ...); - _grantRoleByERC1155(BURNER_ROLE, 0x..., ...); - } - - function safeMint(address to, uint256 tokenId, string memory uri) - public onlyHasToken(MINTER_ROLE, msg.sender) - { - _safeMint(to, tokenId); - } - - function burn(uint256 tokenId) - public onlyHasToken(BURNER_ROLE, msg.sender) - { - _burn(tokenId); - } -} -``` - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7303.md diff --git a/EIPS/eip-7401.md b/EIPS/eip-7401.md index d3d937d62fbba6..c055add0468ea8 100644 --- a/EIPS/eip-7401.md +++ b/EIPS/eip-7401.md @@ -1,496 +1 @@ ---- -eip: 7401 -title: Parent-Governed Non-Fungible Tokens Nesting -description: An interface for Non-Fungible Tokens Nesting with emphasis on parent token's control over the relationship. -author: Bruno Škvorc (@Swader), Cicada (@CicadaNCR), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/eip-6059-parent-governed-nestable-non-fungible-tokens/11914 -status: Final -type: Standards Track -category: ERC -created: 2023-07-26 -requires: 165, 721 ---- - -## Abstract - -❗️ **[ERC-7401](./eip-7401.md) supersedes [ERC-6059](./eip-6059.md).** ❗️ - -The Parent-Governed NFT Nesting standard extends [ERC-721](./eip-721.md) by allowing for a new inter-NFT relationship and interaction. - -At its core, the idea behind the proposal is simple: the owner of an NFT does not have to be an Externally Owned Account (EOA) or a smart contract, it can also be an NFT. - -The process of nesting an NFT into another is functionally identical to sending it to another user. The process of sending a token out of another one involves issuing a transaction from the account owning the parent token. - -An NFT can be owned by a single other NFT, but can in turn have a number of NFTs that it owns. This proposal establishes the framework for the parent-child relationships of NFTs. A parent token is the one that owns another token. A child token is a token that is owned by another token. A token can be both a parent and child at the same time. Child tokens of a given token can be fully managed by the parent token's owner, but can be proposed by anyone. - -![Nestable tokens](../assets/eip-7401/img/eip-7401-nestable-tokens.png) - -The graph illustrates how a child token can also be a parent token, but both are still administered by the root parent token's owner. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability for tokens to own other tokens allows for greater utility, usability and forward compatibility. - -In the four years since [ERC-721](./eip-721.md) was published, the need for additional functionality has resulted in countless extensions. This ERC improves upon ERC-721 in the following areas: - -- [Bundling](#bundling) -- [Collecting](#collecting) -- [Membership](#membership) -- [Delegation](#delegation) - -This proposal fixes the inconsistency in the [ERC-6059](./eip-6059.md) interface specification, where interface ID doesn't match the interface specified as the interface evolved during the proposal's lifecycle, but one of the parameters was not added to it. The missing parameter is, however, present in the interface ID. Apart from this fix, this proposal is functionally equivalent to [ERC-6059](./eip-6059.md). - -### Bundling - -One of the most frequent uses of [ERC-721](./eip-721.md) is to disseminate the multimedia content that is tied to the tokens. In the event that someone wants to offer a bundle of NFTs from various collections, there is currently no easy way of bundling all of these together and handle their sale as a single transaction. This proposal introduces a standardized way of doing so. Nesting all of the tokens into a simple bundle and selling that bundle would transfer the control of all of the tokens to the buyer in a single transaction. - -### Collecting - -A lot of NFT consumers collect them based on countless criteria. Some aim for utility of the tokens, some for the uniqueness, some for the visual appeal, etc. There is no standardized way to group the NFTs tied to a specific account. By nesting NFTs based on their owner's preference, this proposal introduces the ability to do it. The root parent token could represent a certain group of tokens and all of the children nested into it would belong to it. - -The rise of soulbound, non-transferable, tokens, introduces another need for this proposal. Having a token with multiple soulbound traits (child tokens), allows for numerous use cases. One concrete example of this can be drawn from supply chains use case. A shipping container, represented by an NFT with its own traits, could have multiple child tokens denoting each leg of its journey. - -### Membership - -A common utility attached to NFTs is a membership to a Decentralised Autonomous Organization (DAO) or to some other closed-access group. Some of these organizations and groups occasionally mint NFTs to the current holders of the membership NFTs. With the ability to nest mint a token into a token, such minting could be simplified, by simply minting the bonus NFT directly into the membership one. - -### Delegation - -One of the core features of DAOs is voting and there are various approaches to it. One such mechanic is using fungible voting tokens where members can delegate their votes by sending these tokens to another member. Using this proposal, delegated voting could be handled by nesting your voting NFT into the one you are delegating your votes to and transferring it when the member no longer wishes to delegate their votes. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title EIP-7401 Parent-Governed Nestable Non-Fungible Tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-7401 -/// @dev Note: the ERC-165 identifier for this interface is 0x42b0e56f. - -pragma solidity ^0.8.16; - -interface IERC7059 /* is ERC165 */ { - /** - * @notice The core struct of ownership. - * @dev The `DirectOwner` struct is used to store information of the next immediate owner, be it the parent token, - * an `ERC721Receiver` contract or an externally owned account. - * @dev If the token is not owned by an NFT, the `tokenId` MUST equal `0`. - * @param tokenId ID of the parent token - * @param ownerAddress Address of the owner of the token. If the owner is another token, then the address MUST be - * the one of the parent token's collection smart contract. If the owner is externally owned account, the address - * MUST be the address of this account - */ - struct DirectOwner { - uint256 tokenId; - address ownerAddress; - } - - /** - * @notice The core child token struct, holding the information about the child tokens. - * @return tokenId ID of the child token in the child token's collection smart contract - * @return contractAddress Address of the child token's smart contract - */ - struct Child { - uint256 tokenId; - address contractAddress; - } - - /** - * @notice Used to notify listeners that the token is being transferred. - * @dev Emitted when `tokenId` token is transferred from `from` to `to`. - * @param from Address of the previous immediate owner, which is a smart contract if the token was nested. - * @param to Address of the new immediate owner, which is a smart contract if the token is being nested. - * @param fromTokenId ID of the previous parent token. If the token was not nested before, the value MUST be `0` - * @param toTokenId ID of the new parent token. If the token is not being nested, the value MUST be `0` - * @param tokenId ID of the token being transferred - */ - event NestTransfer( - address indexed from, - address indexed to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 indexed tokenId - ); - - /** - * @notice Used to notify listeners that a new token has been added to a given token's pending children array. - * @dev Emitted when a child NFT is added to a token's pending array. - * @param tokenId ID of the token that received a new pending child token - * @param childIndex Index of the proposed child token in the parent token's pending children array - * @param childAddress Address of the proposed child token's collection smart contract - * @param childId ID of the child token in the child token's collection smart contract - */ - event ChildProposed( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - /** - * @notice Used to notify listeners that a new child token was accepted by the parent token. - * @dev Emitted when a parent token accepts a token from its pending array, migrating it to the active array. - * @param tokenId ID of the token that accepted a new child token - * @param childIndex Index of the newly accepted child token in the parent token's active children array - * @param childAddress Address of the child token's collection smart contract - * @param childId ID of the child token in the child token's collection smart contract - */ - event ChildAccepted( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - /** - * @notice Used to notify listeners that all pending child tokens of a given token have been rejected. - * @dev Emitted when a token removes all child tokens from its pending array. - * @param tokenId ID of the token that rejected all of the pending children - */ - event AllChildrenRejected(uint256 indexed tokenId); - - /** - * @notice Used to notify listeners a child token has been transferred from parent token. - * @dev Emitted when a token transfers a child from itself, transferring ownership. - * @param tokenId ID of the token that transferred a child token - * @param childIndex Index of a child in the array from which it is being transferred - * @param childAddress Address of the child token's collection smart contract - * @param childId ID of the child token in the child token's collection smart contract - * @param fromPending A boolean value signifying whether the token was in the pending child tokens array (`true`) or - * in the active child tokens array (`false`) - * @param toZero A boolean value signifying whether the token is being transferred to the `0x0` address (`true`) or - * not (`false`) - */ - event ChildTransferred( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId, - bool fromPending, - bool toZero - ); - - /** - * @notice Used to retrieve the *root* owner of a given token. - * @dev The *root* owner of the token is the top-level owner in the hierarchy which is not an NFT. - * @dev If the token is owned by another NFT, it MUST recursively look up the parent's root owner. - * @param tokenId ID of the token for which the *root* owner has been retrieved - * @return owner The *root* owner of the token - */ - function ownerOf(uint256 tokenId) external view returns (address owner); - - /** - * @notice Used to retrieve the immediate owner of the given token. - * @dev If the immediate owner is another token, the address returned, MUST be the one of the parent token's - * collection smart contract. - * @param tokenId ID of the token for which the direct owner is being retrieved - * @return address Address of the given token's owner - * @return uint256 The ID of the parent token. MUST be `0` if the owner is not an NFT - * @return bool The boolean value signifying whether the owner is an NFT or not - */ - function directOwnerOf(uint256 tokenId) - external - view - returns ( - address, - uint256, - bool - ); - - /** - * @notice Used to burn a given token. - * @dev When a token is burned, all of its child tokens are recursively burned as well. - * @dev When specifying the maximum recursive burns, the execution MUST be reverted if there are more children to be - * burned. - * @dev Setting the `maxRecursiveBurn` value to 0 SHOULD only attempt to burn the specified token and MUST revert if - * there are any child tokens present. - * @param tokenId ID of the token to burn - * @param maxRecursiveBurns Maximum number of tokens to recursively burn - * @return uint256 Number of recursively burned children - */ - function burn(uint256 tokenId, uint256 maxRecursiveBurns) - external - returns (uint256); - - /** - * @notice Used to add a child token to a given parent token. - * @dev This adds the child token into the given parent token's pending child tokens array. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @dev This method MUST NOT be called directly. It MUST only be called from an instance of `IERC7059` as part of a - `nestTransfer` or `transferChild` to an NFT. - * @dev Requirements: - * - * - `directOwnerOf` on the child contract MUST resolve to the called contract. - * - the pending array of the parent contract MUST not be full. - * @param parentId ID of the parent token to receive the new child token - * @param childId ID of the new proposed child token - */ - function addChild(uint256 parentId, uint256 childId) external; - - /** - * @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of the child token to accept in the pending children array of a given token - * @param childAddress Address of the collection smart contract of the child token expected to be at the specified - * index - * @param childId ID of the child token expected to be located at the specified index - */ - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) external; - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev The children's ownership structures are not updated. - * @dev Requirements: - * - * - `parentId` MUST exist - * @param parentId ID of the parent token for which to reject all of the pending tokens - * @param maxRejections Maximum number of expected children to reject, used to prevent from - * rejecting children which arrive just before this operation. - */ - function rejectAllChildren(uint256 parentId, uint256 maxRejections) external; - - /** - * @notice Used to transfer a child token from a given parent token. - * @dev MUST remove the child from the parent's active or pending children. - * @dev When transferring a child token, the owner of the token MUST be set to `to`, or not updated in the event of `to` - * being the `0x0` address. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract - * @param childId ID of the child token in its own collection smart contract - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `to` - */ - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes data - ) external; - - /** - * @notice Used to retrieve the active child tokens of a given parent token. - * @dev Returns array of Child structs existing for parent token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the active child tokens - * @return struct[] An array of Child structs containing the parent token's active child tokens - */ - function childrenOf(uint256 parentId) - external - view - returns (Child[] memory); - - /** - * @notice Used to retrieve the pending child tokens of a given parent token. - * @dev Returns array of pending Child structs existing for given parent. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the pending child tokens - * @return struct[] An array of Child structs containing the parent token's pending child tokens - */ - function pendingChildrenOf(uint256 parentId) - external - view - returns (Child[] memory); - - /** - * @notice Used to retrieve a specific active child token for a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the child is being retrieved - * @param index Index of the child token in the parent token's active child tokens array - * @return struct A Child struct containing data about the specified child - */ - function childOf(uint256 parentId, uint256 index) - external - view - returns (Child memory); - - /** - * @notice Used to retrieve a specific pending child token from a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the pending child token is being retrieved - * @param index Index of the child token in the parent token's pending child tokens array - * @return struct A Child struct containing data about the specified child - */ - function pendingChildOf(uint256 parentId, uint256 index) - external - view - returns (Child memory); - - /** - * @notice Used to transfer the token into another token. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @param from Address of the direct owner of the token to be transferred - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token being transferred - * @param destinationId ID of the token to receive the token being transferred - * @param data Additional data with no specified format - */ - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) external; -} -``` - -ID MUST never be a `0` value, as this proposal uses `0` values do signify that the token/destination is not an NFT. - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **How to name the proposal?**\ -In an effort to provide as much information about the proposal we identified the most important aspect of the proposal; the parent centered control over nesting. The child token's role is only to be able to be `Nestable` and support a token owning it. This is how we landed on the `Parent-Centered` part of the title. -2. **Why is automatically accepting a child using [EIP-712](./eip-712.md) permit-style signatures not a part of this proposal?**\ -For consistency. This proposal extends ERC-721 which already uses 1 transaction for approving operations with tokens. It would be inconsistent to have this and also support signing messages for operations with assets. -3. **Why use indexes?**\ -To reduce the gas consumption. If the token ID was used to find which token to accept or reject, iteration over arrays would be required and the cost of the operation would depend on the size of the active or pending children arrays. With the index, the cost is fixed. Lists of active and pending children per token need to be maintained, since methods to get them are part of the proposed interface.\ -To avoid race conditions in which the index of a token changes, the expected token ID as well as the expected token's collection smart contract is included in operations requiring token index, to verify that the token being accessed using the index is the expected one.\ -Implementation that would internally keep track of indices using mapping was attempted. The minimum cost of accepting a child token was increased by over 20% and the cost of minting has increased by over 15%. We concluded that it is not necessary for this proposal and can be implemented as an extension for use cases willing to accept the increased transaction cost this incurs. In the sample implementation provided, there are several hooks which make this possible. -4. **Why is the pending children array limited instead of supporting pagination?**\ -The pending child tokens array is not meant to be a buffer to collect the tokens that the root owner of the parent token wants to keep, but not enough to promote them to active children. It is meant to be an easily traversable list of child token candidates and should be regularly maintained; by either accepting or rejecting proposed child tokens. There is also no need for the pending child tokens array to be unbounded, because active child tokens array is.\ -Another benefit of having bounded child tokens array is to guard against spam and griefing. As minting malicious or spam tokens could be relatively easy and low-cost, the bounded pending array assures that all of the tokens in it are easy to identify and that legitimate tokens are not lost in a flood of spam tokens, if one occurs.\ -A consideration tied to this issue was also how to make sure, that a legitimate token is not accidentally rejected when clearing the pending child tokens array. We added the maximum pending children to reject argument to the clear pending child tokens array call. This assures that only the intended number of pending child tokens is rejected and if a new token is added to the pending child tokens array during the course of preparing such call and executing it, the clearing of this array SHOULD result in a reverted transaction. -5. **Should we allow tokens to be nested into one of its children?**\ -The proposal enforces that a parent token can't be nested into one of its child token, or downstream child tokens for that matter. A parent token and its children are all managed by the parent token's root owner. This means that if a token would be nested into one of its children, this would create the ownership loop and none of the tokens within the loop could be managed anymore. -6. **Why is there not a "safe" nest transfer method?**\ -`nestTransfer` is always "safe" since it MUST check for `IERC7059` compatibility on the destination. -7. **How does this proposal differ from the other proposals trying to address a similar problem?**\ -This interface allows for tokens to both be sent to and receive other tokens. The propose-accept and parent governed patterns allow for a more secure use. The backward compatibility is only added for ERC-721, allowing for a simpler interface. The proposal also allows for different collections to inter-operate, meaning that nesting is not locked to a single smart contract, but can be executed between completely separate NFT collections.\ -Additionally this proposal addresses the inconsistencies between `interfaceId`, interface specification and example implementation of [ERC-6059](./eip-6059.md). - -### Propose-Commit pattern for child token management - -Adding child tokens to a parent token MUST be done in the form of propose-commit pattern to allow for limited mutability by a 3rd party. When adding a child token to a parent token, it is first placed in a *"Pending"* array, and MUST be migrated to the *"Active"* array by the parent token's root owner. The *"Pending"* child tokens array SHOULD be limited to 128 slots to prevent spam and griefing. - -The limitation that only the root owner can accept the child tokens also introduces a trust inherent to the proposal. This ensures that the root owner of the token has full control over the token. No one can force the user to accept a child if they don't want to. - -### Parent Governed pattern - -The parent NFT of a nested token and the parent's root owner are in all aspects the true owners of it. Once you send a token to another one you give up ownership. - -We continue to use ERC-721's `ownerOf` functionality which will now recursively look up through parents until it finds an address which is not an NFT, this is referred to as the *root owner*. Additionally we provide the `directOwnerOf` which returns the most immediate owner of a token using 3 values: the owner address, the tokenId which MUST be 0 if the direct owner is not an NFT, and a flag indicating whether or not the parent is an NFT. - -The root owner or an approved party MUST be able to do the following operations on children: `acceptChild`, `rejectAllChildren` and `transferChild`. - -The root owner or an approved party MUST also be allowed to do these operations only when token is not owned by an NFT: `transferFrom`, `safeTransferFrom`, `nestTransferFrom`, `burn`. - -If the token is owned by an NFT, only the parent NFT itself MUST be allowed to execute the operations listed above. Transfers MUST be done from the parent token, using `transferChild`, this method in turn SHOULD call `nestTransferFrom` or `safeTransferFrom` in the child token's smart contract, according to whether the destination is an NFT or not. For burning, tokens must first be transferred to an EOA and then burned. - -We add this restriction to prevent inconsistencies on parent contracts, since only the `transferChild` method takes care of removing the child from the parent when it is being transferred out of it. - -### Child token management - -This proposal introduces a number of child token management functions. In addition to the permissioned migration from *"Pending"* to *"Active"* child tokens array, the main token management function from this proposal is the `transferChild` function. The following state transitions of a child token are available with it: - -1. Reject child token -2. Abandon child token -3. Unnest child token -4. Transfer the child token to an EOA or an `ERC721Receiver` -5. Transfer the child token into a new parent token - -To better understand how these state transitions are achieved, we have to look at the available parameters passed to `transferChild`: - -```solidity - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes data - ) external; -``` - -Based on the desired state transitions, the values of these parameters have to be set accordingly (any parameters not set in the following examples depend on the child token being managed): - -1. **Reject child token**\ -![Reject child token](../assets/eip-7401/img/eip-7401-reject-child.png) -2. **Abandon child token**\ -![Abandon child token](../assets/eip-7401/img/eip-7401-abandon-child.png) -3. **Unnest child token**\ -![Unnest child token](../assets/eip-7401/img/eip-7401-unnest-child.png) -4. **Transfer the child token to an EOA or an `ERC721Receiver`**\ -![Transfer child token to EOA](../assets/eip-7401/img/eip-7401-transfer-child-to-eoa.png) -5. **Transfer the child token into a new parent token**\ -![Transfer child token to parent token](../assets/eip-7401/img/eip-7401-transfer-child-to-token.png)\ -This state change places the token in the pending array of the new parent token. The child token still needs to be accepted by the new parent token's root owner in order to be placed into the active array of that token. - -## Backwards Compatibility - -The Nestable token standard has been made compatible with [ERC-721](./eip-721.md) in order to take advantage of the robust tooling available for implementations of ERC-721 and to ensure compatibility with existing ERC-721 infrastructure. - -The only incompatibility with ERC-721 is that Nestable tokens cannot use a token ID of 0. - -There is some differentiation of how the `ownerOf` method behaves compared to ERC-721. The `ownerOf` method will now recursively look up through parent tokens until it finds an address that is not an NFT; this is referred to as the *root owner*. Additionally, we provide the `directOwnerOf`, which returns the most immediate owner of a token using 3 values: the owner address, the `tokenId`, which MUST be 0 if the direct owner is not an NFT, and a flag indicating whether or not the parent is an NFT. In case the token is owned by an EoA or an ERC-721 Receiver, the `ownerOf` method will behave the same as in ERC-721. - -## Test Cases - -Tests are included in [`nestable.ts`](../assets/eip-7401/test/nestable.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-7401 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`NestableToken.sol`](../assets/eip-7401/contracts/NestableToken.sol). - - -## Security Considerations - -The same security considerations as with [ERC-721](./eip-721.md) apply: hidden logic may be present in any of the functions, including burn, add child, accept child, and more. - -Since the current owner of the token is allowed to manage the token, there is a possibility that after the parent token is listed for sale, the seller might remove a child token just before before the sale and thus the buyer would not receive the expected child token. This is a risk that is inherent to the design of this standard. Marketplaces should take this into account and provide a way to verify the expected child tokens are present when the parent token is being sold or to guard against such a malicious behaviour in another way. - -It is worth noting that `balanceOf` method only accounts for immediate tokens owned by the address; the tokens that are nested into a token owned by this address will not be reflected in this value as the recursive lookup needed in order to calculate this value is potentially too deep and might break the method. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7401.md diff --git a/EIPS/eip-7405.md b/EIPS/eip-7405.md index 243028700dae94..33b43e28e8db9c 100644 --- a/EIPS/eip-7405.md +++ b/EIPS/eip-7405.md @@ -1,230 +1 @@ ---- -eip: 7405 -title: Portable Smart Contract Accounts -description: Migrating smart contract accounts at the Proxy (ERC-1967) layer. -author: Aaron Yee (@aaronyee-eth) -discussions-to: https://ethereum-magicians.org/t/erc-7405-portable-smart-contract-accounts/15236 -status: Draft -type: Standards Track -category: ERC -created: 2023-07-26 -requires: 191, 1967 ---- - -## Abstract - -Portable Smart Contract Accounts (PSCA) address the lack of portability and compatibility faced by Smart Contract Accounts (SCA) across different wallet providers. Based on [ERC-1967](./eip-1967.md), the PSCA system allows users to easily migrate their SCAs between different wallets using new, randomly generated migration keys. This provides a similar experience to exporting an externally owned account (EOA) with a private key or mnemonic. The system ensures security by employing signatures and time locks, allowing users to verify and cancel migration operations during the lock period, thereby preventing potential malicious actions. PSCA offers a non-intrusive and cost-effective approach, enhancing the interoperability and composability within the Account Abstraction (AA) ecosystem. - -## Motivation - -With the introduction of the [ERC-4337](./eip-4337.md) standard, AA related infrastructure and SCAs have been widely adopted in the community. However, unlike EOAs, SCAs have a more diverse code space, leading to varying contract implementations across different wallet providers. Consequently, the lack of portability for SCAs has become a significant issue, making it challenging for users to migrate their accounts between different wallet providers. While some proposed a modular approach for SCA accounts, it comes with higher implementation costs and specific prerequisites for wallet implementations. - -Considering that different wallet providers tend to prefer their own implementations or may expect their contract systems to be concise and robust, a modular system may not be universally applicable. The community currently lacks a more general SCA migration standard. - -This proposal describes a solution working at the Proxy (ERC-1967) layer, providing a user experience similar to exporting an EOA account (using private keys or mnemonics). A universal SCA migration mechanism is shown in the following diagram: - -![Overview Diagram](../assets/eip-7405/overview-diagram.jpg) - -Considering that different wallet providers may have their own implementations, this solution imposes almost no requirements on the SCA implementation, making it more universally applicable and less intrusive with lower operational costs. Unlike a modular system operating at the "implementation" layer, both approaches can complement each other to further improve the interoperability and composability of the AA ecosystem. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Terms - -- Wallet Provider: A service provider that offers wallet services. SCA implementations among wallet providers are typically different, lacking compatibility with each other. -- Random Operator: A new, randomly generated migration mnemonic or private key used for each migration. The corresponding address of its public key is the random operator's address. - - If using a mnemonic, the derived migration private key follows the [BIP 44](https://github.com/bitcoin/bips/blob/55566a73f9ddf77b4512aca8e628650c913067bf/bip-0044.mediawiki) specification with the path **`m/44'/60'/0'/0/0'`**. - -### Interfaces - -A Portable Smart Contract Account **MUST** implement the **`IERC7405`** interface: - -```solidity -interface IERC7405 { - /** - * @dev emitted when the account finishes the migration - * @param oldImplementation old implementation address - * @param newImplementation new implementation address - */ - event AccountMigrated( - address oldImplementation, - address newImplementation - ); - - /** - * @dev prepare the account for migration - * @param randomOperator public key (in address format) of the random operator - * @param signature signature signed by the random operator - * - * **MUST** check the authenticity of the account - */ - function prepareAccountMigration( - address randomOperator, - bytes calldata signature - ) external; - - /** - * @dev cancel the account migration - * - * **MUST** check the authenticity of the account - */ - function cancelAccountMigration() external; - - /** - * @dev handle the account migration - * @param newImplementation new implementation address - * @param initData init data for the new implementation - * @param signature signature signed by the random operator - * - * **MUST NOT** check the authenticity to make it accessible by the new implementation - */ - function handleAccountMigration( - address newImplementation, - bytes calldata initData, - bytes calldata signature - ) external; -} -``` - -### Signatures - -The execution of migration operations **MUST** use the migration private key to sign the `MigrationOp`. - -```solidity -struct MigrationOp { - uint256 chainID; - bytes4 selector; - bytes data; -} -``` - -When the **`selector`** corresponds to **`prepareAccountMigration(address,bytes)`** (i.e., **`0x50fe70bd`**), the **`data`** is **`abi.encode(randomOperator)`**. When the **`selector`** corresponds to **`handleAccountMigration(address,bytes,bytes)`** (i.e., **`0xae2828ba`**), the **`data`** is **`abi.encode(randomOperator, setupCalldata)`**. - -The signature is created using **[ERC-191](./eip-191.md)**, signing the **`MigrateOpHash`** (calculated as **`abi.encode(chainID, selector, data)`**). - -### Registry - -To simplify migration credentials and enable direct addressing of the SCA account with only the migration mnemonic or private key, this proposal requires a shared registry deployed at the protocol layer. - -```solidity -interface IERC7405Registry { - struct MigrationData { - address account; - uint48 createTime; - uint48 lockUntil; - } - - /** - * @dev check if the migration data for the random operator exists - * @param randomOperator public key (in address format) of the random operator - */ - function migrationDataExists( - address randomOperator - ) external returns (bool); - - /** - * @dev get the migration data for the random operator - * @param randomOperator public key (in address format) of the random operator - */ - function getMigrationData( - address randomOperator - ) external returns (MigrationData memory); - - /** - * @dev set the migration data for the random operator - * @param randomOperator public key (in address format) of the random operator - * @param lockUntil the timestamp until which the account is locked for migration - * - * **MUST** validate `migrationDataMap[randomOperator]` is empty - */ - function setMigrationData( - address randomOperator, - uint48 lockUntil - ) external; - - /** - * @dev delete the migration data for the random operator - * @param randomOperator public key (in address format) of the random operator - * - * **MUST** validate `migrationDataMap[randomOperator].account` is `msg.sender` - */ - function deleteMigrationData(address randomOperator) external; -} -``` - -### Expected behavior - -When performing account migration (i.e., migrating an SCA from Wallet A to Wallet B), the following steps **MUST** be followed: - -1. Wallet A generates a new migration mnemonic or private key (**MUST** be new and random) and provides it to the user. The address corresponding to its public key is used as the **`randomOperator`**. -2. Wallet A signs the **`MigrateOpHash`** using the migration private key and calls the **`prepareAccountMigration`** method, which **MUST** performs the following operations: - - Calls the internal method **`_requireAccountAuth()`** to verify the authenticity of the SCA account. For example, in ERC-4337 account implementation, it may require **`msg.sender == address(entryPoint)`**. - - Performs signature checks to confirm the validity of the **`randomOperator`**. - - Calls **`IERC7405Registry.migrationDataExists(randomOperator)`** to ensure that the **`randomOperator`** does not already exist. - - Sets the SCA account's lock status to true and adds a record by calling **`IERC7405Registry.setMigrationData(randomOperator, lockUntil)`**. - - After calling **`prepareAccountMigration`**, the account remains locked until a successful call to either **`cancelAccountMigration`** or **`handleAccountMigration`**. -3. To continue the migration, Wallet B initializes authentication data and imports the migration mnemonic or private key. Wallet B then signs the **`MigrateOpHash`** using the migration private key and calls the **`handleWalletMigration`** method, which **MUST** performs the following operations: - - **MUST NOT** perform SCA account authentication checks to ensure public accessibility. - - Performs signature checks to confirm the validity of the **`randomOperator`**. - - Calls **`IERC7405Registry.getMigrationData(randomOperator)`** to retrieve **`migrationData`**, and requires **`require(migrationData.account == address(this) && block.timestamp > migrationData.lockUntil)`**. - - Calls the internal method **`_beforeWalletMigration()`** to execute pre-migration logic from Wallet A (e.g., data cleanup). - - Modifies the Proxy (ERC-1967) implementation to the implementation contract of Wallet B. - - Calls **`address(this).call(initData)`** to initialize the Wallet B contract. - - Calls **`IERC7405Registry.deleteMigrationData(randomOperator)`** to remove the record. - - Emits the **`AccountMigrated`** event. -4. If the migration needs to be canceled, Wallet A can call the **`cancelAccountMigration`** method, which **MUST** performs the following operations: - - Calls the internal method **`_requireAccountAuth()`** to verify the authenticity of the SCA account. - - Sets the SCA account's lock status to false and deletes the record by calling **`IERC7405Registry.deleteMigrationData(randomOperator)`**. - -### Storage Layout - -To prevent conflicts in storage layout during migration across different wallet implementations, a Portable Smart Contract Account implementation contract: - -- **MUST NOT** directly define state variables in the contract header. -- **MUST** encapsulate all state variables within a struct and store that struct in a specific slot. The slot index **SHOULD** be unique across different wallet implementations. - -For slot index, we recommend calculating it based on the namespace and slot ID: - -- The namespace **MUST** contain only [A-Za-z0-9_]. -- Wallet providers' namespaces are **RECOMMENDED** to use snake_case, incorporating the wallet name and major version number, such as **`foo_wallet_v1`**. -- The slot ID for slot index **SHOULD** follow the format **`{namespace}.{customDomain}`**, for example, **`foo_wallet_v1.config`**. -- The calculation of the slot index is performed as **`bytes32(uint256(keccak256(slotID) - 1))`**. - -## Rationale - -The main challenge addressed by this EIP is the lack of portability in Smart Contract Accounts (SCAs). Currently, due to variations in SCA implementations across wallet providers, moving between wallets is a hassle. Proposing a modular approach, though beneficial in some respects, comes with its own costs and compatibility concerns. - -The PSCA system, rooted in ERC-1967, introduces a migration mechanism reminiscent of exporting an EOA with a private key or mnemonic. This approach is chosen for its familiarity to users, ensuring a smoother user experience. - -Employing random, migration-specific keys further fortifies security. By mimicking the EOA exportation process, we aim to keep the process recognizable, while addressing the unique challenges of SCA portability. - -The decision to integrate with a shared registry at the protocol layer simplifies migration credentials. This system enables direct addressing of the SCA account using only the migration key, enhancing efficiency. - -Storage layout considerations were paramount to avoid conflicts during migrations. Encapsulating state variables within a struct, stored in a unique slot, ensures that migrations don't lead to storage overlaps or overwrites. - -## Backwards Compatibility - -This proposal is backward compatible with all SCA based on ERC-1967 Proxy, including non-ERC-4337 SCAs. Furthermore, this proposal does not have specific prerequisites for SCA implementation contracts, making it broadly applicable to various SCAs. - - - -## Security Considerations - -- Each migration must generate a new, randomly generated migration mnemonic or private key and its corresponding random operator address to prevent replay attacks or malicious signing. -- Different wallet implementations must consider the independence of storage layout to avoid conflicts in storage after migration. -- To prevent immediate loss of access for the account owner due to malicious migration, we introduce a "time lock" to make migrations detectable and reversible. When a malicious operation attempts an immediate migration of an SCA, the account enters a lock state and waits for a lock period. During this time, users can use the original account authentication to cancel the migration and prevent asset loss. Accounts in the lock state **SHOULD NOT** allow the following operations: - - Any form of asset transfer operations - - Any form of external contract call operations - - Any attempts to modify account authentication factors - - Any operations that could potentially impact the above three -- When performing migration operations, the wallet provider **SHOULD** attempt to notify the account owner of the migration details through all available messaging channels. - -## Copyright - -Copyright and related rights waived via **[CC0](../LICENSE.md)**. +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7405.md diff --git a/EIPS/eip-7406.md b/EIPS/eip-7406.md index 777f1bc80d60c3..6245088318b7ee 100644 --- a/EIPS/eip-7406.md +++ b/EIPS/eip-7406.md @@ -1,156 +1 @@ ---- -eip: 7406 -title: Multi-Namespace Onchain Registry -description: An universally accepted multi-namespace registry with mapping structures on the Ethereum -author: Mengshi Zhang (@MengshiZhang), Zihao Chen (@zihaoccc) -discussions-to: https://ethereum-magicians.org/t/eip-7406-multi-namespace-onchain-registry/15216 -status: Draft -type: Standards Track -category: ERC -created: 2023-07-23 -requires: 137 ---- - -## Abstract - -This EIP proposes a universally accepted description for onchain registry entries with support for multi-namespaces, where each entry is structured as a mapping type. The multi-namespace registry enables the storage of a collection of key-value mappings within the blockchain, serving as a definitive source of information with a traceable history of changes. These mapping records act as pointers combined with onchain assets, offering enhanced versatility in various use cases by encapsulating extensive details. The proposed solution introduces a general mapping data structure that is flexible enough to support and be compatible with different situations, providing a more scalable and powerful alternative to current ENS-like registries. - -## Motivation - -Blockchain-based registries are fundamental components for decentralized applications, enabling the storage and retrieval of essential information. Existing solutions, like the ENS registry, serve specific use cases but may lack the necessary flexibility to accommodate more complex scenarios. The need for a more general mapping data structure with multi-namespace support arises to empower developers with a single registry capable of handling diverse use cases efficiently. - -The proposed multi-namespace registry offers several key advantages: - -- **Versatility**: Developers can define and manage multiple namespaces, each with its distinct set of keys, allowing for more granular control and organization of data. For instance, single same key can derive as different pointers to various values based on difference namespaces, which a namespace can be specified as a session type, if this registry stores sessions, or short URL -> full URL mapping is registry stores such type of data. -- **Traceable History**: By leveraging multi-namespace capabilities, the registry can support entry versioning by using multi-namespace distinct as version number, enabling tracking of data change history, reverting data, or data tombstoning. This facilitates data management and governance within a single contract. -- **Enhanced Compatibility**: The proposed structure is designed to be compatible with various use cases beyond the scope of traditional ENS-like registries, promoting its adoption in diverse decentralized applications. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. - -### **Registry specification** - -The multi namespace registry contract exposes the following functions: - -```solidity -function owner(bytes32 namespace, bytes32 key) external view returns (address); -``` - -- Returns the owner of the specified **key** under the given **namespace**. - -```solidity -function resolver(bytes32 namespace, bytes32 key) external view returns (address); -``` - -- Returns the resolver address for the specified **key** under the given **namespace**. - -```solidity -function setOwner(bytes32 namespace, bytes32 key, address newOwner) external; -``` - -- Transfers ownership of the **key** under the specified **namespace** to another owner. This function may only be called by the current owner of the **key** under a specific **namespace**. The same **key** under different **namespaces** may have different owners. A successful call to this function logs the event **Transfer(bytes32 namespace, bytes32 key, address newOwner)**. - -```solidity -function createNamespace(bytes32 namespace) external; -``` - -- Create a new **namespace** such as a new version or a new type of protocol in current registry. A successful call to this function logs the event **NewNamespace(bytes32 namespace)**. - -```solidity -function setResolver(bytes32 namespace, bytes32 key, address newResolver) external; -``` - -- Sets the resolver address for the **key** under the given **namespace**. This function may only be called by the owner of the key under a specific **namespace**. The same key under different namespaces may have different resolvers. A successful call to this function logs the event **NewResolver(bytes32 namespace, bytes32 key, address newResolver)**. - -### **Resolver specification** - -The multi-namespace resolver contract can utilize the same specification as defined in [ERC-137](./eip-137.md). - -## Rationale - -By supporting multiple namespaces, the registry caters to various use cases, including but not limited to identity management, session management, record tracking, and decentralized content publishing. This flexibility enables developers to design and implement more complex decentralized applications with ease. - -## Backwards Compatibility - -As this EIP introduces a new feature and does not modify any existing behaviors, there are no backwards compatibility issues. - -## Reference Implementation - -### *Appendix A: Registry Implementation* - -```solidity -pragma solidity ^0.8.12; - -import "./IERC7406Interface.sol"; - -contract ERC7406 { - struct Record { - address owner; - address resolver; - } - - - // A map is used to record namespace existence - mapping(byte32=>uint) namespaces; - mapping(bytes32=>mapping(bytes32=>Record)) records; - - event NewOwner(bytes32 indexed namespace, bytes32 indexed key, address owner); - event Transfer(bytes32 indexed namespace, bytes32 indexed key, address owner); - event NewResolver(bytes32 indexed namespace, bytes32 indexed key, address resolver); - event NewNamespace(bytes32 namespace) - - modifier only_owner(bytes32 namespace, bytes32 key) { - if(records[namespace][key].owner != msg.sender) throw; - _ - } - - modifier only_approver() { - if(records[0][0].owner != msg.sender) throw; - _ - } - - function ERC7406(address approver) { - records[0][0].owner = approver; - } - - function owner(bytes32 namespace, bytes32 key) constant returns (address) { - return records[namespace][key].owner; - } - - function createNamespace(bytes32 namespace) only_approver() { - if (status == 0) throw; - NewNamespace(namespace); - if (namespaces[namespace] != 0) { - return; - } - namespaces[namespace] = 1; - } - - function resolver(bytes32 namespace, bytes32 key) constant returns (address) { - if (namespaces[namespace] == 0) throw; - return records[namespace][key].resolver; - } - - function setOwner(bytes32 namespace, bytes32 key, address owner) only_owner(namespace, key) { - Transfer(key, namespace, owner); - records[namespace][key].owner = owner; - } - - function setResolver(bytes32 namespace, bytes32 key, address resolver) only_approver() { - if (namespaces[namespace] == 0) { - this.createNamespace(namespace, 1); - } - NewResolver(key, namespace, resolver); - records[namespace][key].resolver = resolver; - } -} -``` - -## Security Considerations - -The proposed multi-namespace registry introduces several security considerations due to its ability to manage various namespaces and access controls. Thorough testing, auditing, and peer reviews will be conducted to identify and mitigate potential attack vectors and vulnerabilities. Security-conscious developers are encouraged to contribute to the audit process. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7406.md diff --git a/EIPS/eip-7409.md b/EIPS/eip-7409.md index 4ed52dec430938..ee67ef3928b355 100644 --- a/EIPS/eip-7409.md +++ b/EIPS/eip-7409.md @@ -1,378 +1 @@ ---- -eip: 7409 -title: Public Non-Fungible Tokens Emote Repository -description: React to any Non-Fungible Tokens using Unicode emojis. -author: Bruno Škvorc (@Swader), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/eip-6381-emotable-extension-for-non-fungible-tokens/12710 -status: Last Call -last-call-deadline: 2023-10-18 -type: Standards Track -category: ERC -created: 2023-07-26 -requires: 165 ---- - - -## Abstract - -❗️ **[ERC-7409](./eip-7409.md) supersedes [ERC-6381](./eip-6381.md).** ❗️ - -The Public Non-Fungible Tokens Emote Repository standard provides an enhanced interactive utility for [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) by allowing NFTs to be emoted at. - -This proposal introduces the ability to react to NFTs using Unicode standardized emoji in a public non-gated repository smart contract that is accessible at the same address in all of the networks. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability for anyone to interact with an NFT introduces an interactive aspect to owning an NFT and unlocks feedback-based NFT mechanics. - -This ERC introduces new utilities for [ERC-721](./eip-721.md) based tokens in the following areas: - -- [Interactivity](#interactivity) -- [Feedback based evolution](#feedback-based-evolution) -- [Valuation](#valuation) - -This proposal fixes the compatibility issue in the [ERC-6381](./eip-6381.md) interface specification, where emojis are represented using `bytes4` values. The introduction of variation flags and emoji skin tones has rendered the `bytes4` namespace insufficient for representing all possible emojis, so the new standard used `string` instead. Apart from this fix, this proposal is functionally equivalent to [ERC-6381](./eip-6381.md). - -### Interactivity - -The ability to emote on an NFT introduces the aspect of interactivity to owning an NFT. This can either reflect the admiration for the emoter (person emoting to an NFT) or can be a result of a certain action performed by the token's owner. Accumulating emotes on a token can increase its uniqueness and/or value. - -### Feedback based evolution - -Standardized on-chain reactions to NFTs allow for feedback based evolution. - -Current solutions are either proprietary or off-chain and therefore subject to manipulation and distrust. Having the ability to track the interaction on-chain allows for trust and objective evaluation of a given token. Designing the tokens to evolve when certain emote thresholds are met incentivizes interaction with the token collection. - -### Valuation - -Current NFT market heavily relies on previous values the token has been sold for, the lowest price of the listed token and the scarcity data provided by the marketplace. There is no real time indication of admiration or desirability of a specific token. Having the ability for users to emote to the tokens adds the possibility of potential buyers and sellers gauging the value of the token based on the impressions the token has collected. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title ERC-7409 Emotable Extension for Non-Fungible Tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-7409 -/// @dev Note: the ERC-165 identifier for this interface is 0x1b3327ab. - -pragma solidity ^0.8.16; - -interface IERC7409 /*is IERC165*/ { - /** - * @notice Used to notify listeners that the token with the specified ID has been emoted to or that the reaction has been revoked. - * @dev The event MUST only be emitted if the state of the emote is changed. - * @param emoter Address of the account that emoted or revoked the reaction to the token - * @param collection Address of the collection smart contract containing the token being emoted to or having the reaction revoked - * @param tokenId ID of the token - * @param emoji Unicode identifier of the emoji - * @param on Boolean value signifying whether the token was emoted to (`true`) or if the reaction has been revoked (`false`) - */ - event Emoted( - address indexed emoter, - address indexed collection, - uint256 indexed tokenId, - string emoji, - bool on - ); - - /** - * @notice Used to get the number of emotes for a specific emoji on a token. - * @param collection Address of the collection containing the token being checked for emoji count - * @param tokenId ID of the token to check for emoji count - * @param emoji Unicode identifier of the emoji - * @return Number of emotes with the emoji on the token - */ - function emoteCountOf( - address collection, - uint256 tokenId, - string memory emoji - ) external view returns (uint256); - - /** - * @notice Used to get the number of emotes for a specific emoji on a set of tokens. - * @param collections An array of addresses of the collections containing the tokens being checked for emoji count - * @param tokenIds An array of IDs of the tokens to check for emoji count - * @param emojis An array of unicode identifiers of the emojis - * @return An array of numbers of emotes with the emoji on the tokens - */ - function bulkEmoteCountOf( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis - ) external view returns (uint256[] memory); - - /** - * @notice Used to get the information on whether the specified address has used a specific emoji on a specific - * token. - * @param emoter Address of the account we are checking for a reaction to a token - * @param collection Address of the collection smart contract containing the token being checked for emoji reaction - * @param tokenId ID of the token being checked for emoji reaction - * @param emoji The ASCII emoji code being checked for reaction - * @return A boolean value indicating whether the `emoter` has used the `emoji` on the token (`true`) or not - * (`false`) - */ - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - string memory emoji - ) external view returns (bool); - - /** - * @notice Used to get the information on whether the specified addresses have used specific emojis on specific - * tokens. - * @param emoters An array of addresses of the accounts we are checking for reactions to tokens - * @param collections An array of addresses of the collection smart contracts containing the tokens being checked - * for emoji reactions - * @param tokenIds An array of IDs of the tokens being checked for emoji reactions - * @param emojis An array of the ASCII emoji codes being checked for reactions - * @return An array of boolean values indicating whether the `emoter`s has used the `emoji`s on the tokens (`true`) - * or not (`false`) - */ - function haveEmotersUsedEmotes( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis - ) external view returns (bool[] memory); - - /** - * @notice Used to get the message to be signed by the `emoter` in order for the reaction to be submitted by someone - * else. - * @param collection The address of the collection smart contract containing the token being emoted at - * @param tokenId ID of the token being emoted - * @param emoji Unicode identifier of the emoji - * @param state Boolean value signifying whether to emote (`true`) or undo (`false`) emote - * @param deadline UNIX timestamp of the deadline for the signature to be submitted - * @return The message to be signed by the `emoter` in order for the reaction to be submitted by someone else - */ - function prepareMessageToPresignEmote( - address collection, - uint256 tokenId, - string memory emoji, - bool state, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to get multiple messages to be signed by the `emoter` in order for the reaction to be submitted by someone - * else. - * @param collections An array of addresses of the collection smart contracts containing the tokens being emoted at - * @param tokenIds An array of IDs of the tokens being emoted - * @param emojis An array of unicode identifiers of the emojis - * @param states An array of boolean values signifying whether to emote (`true`) or undo (`false`) emote - * @param deadlines An array of UNIX timestamps of the deadlines for the signatures to be submitted - * @return The array of messages to be signed by the `emoter` in order for the reaction to be submitted by someone else - */ - function bulkPrepareMessagesToPresignEmote( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states, - uint256[] memory deadlines - ) external view returns (bytes32[] memory); - - /** - * @notice Used to emote or undo an emote on a token. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @param collection Address of the collection containing the token being emoted at - * @param tokenId ID of the token being emoted - * @param emoji Unicode identifier of the emoji - * @param state Boolean value signifying whether to emote (`true`) or undo (`false`) emote - */ - function emote( - address collection, - uint256 tokenId, - string memory emoji, - bool state - ) external; - - /** - * @notice Used to emote or undo an emote on multiple tokens. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @dev MUST revert if the lengths of the `collections`, `tokenIds`, `emojis` and `states` arrays are not equal. - * @param collections An array of addresses of the collections containing the tokens being emoted at - * @param tokenIds An array of IDs of the tokens being emoted - * @param emojis An array of unicode identifiers of the emojis - * @param states An array of boolean values signifying whether to emote (`true`) or undo (`false`) emote - */ - function bulkEmote( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states - ) external; - - /** - * @notice Used to emote or undo an emote on someone else's behalf. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @dev MUST revert if the lengths of the `collections`, `tokenIds`, `emojis` and `states` arrays are not equal. - * @dev MUST revert if the `deadline` has passed. - * @dev MUST revert if the recovered address is the zero address. - * @param emoter The address that presigned the emote - * @param collection The address of the collection smart contract containing the token being emoted at - * @param tokenId IDs of the token being emoted - * @param emoji Unicode identifier of the emoji - * @param state Boolean value signifying whether to emote (`true`) or undo (`false`) emote - * @param deadline UNIX timestamp of the deadline for the signature to be submitted - * @param v `v` value of an ECDSA signature of the message obtained via `prepareMessageToPresignEmote` - * @param r `r` value of an ECDSA signature of the message obtained via `prepareMessageToPresignEmote` - * @param s `s` value of an ECDSA signature of the message obtained via `prepareMessageToPresignEmote` - */ - function presignedEmote( - address emoter, - address collection, - uint256 tokenId, - string memory emoji, - bool state, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to bulk emote or undo an emote on someone else's behalf. - * @dev Does nothing if attempting to set a pre-existent state. - * @dev MUST emit the `Emoted` event is the state of the emote is changed. - * @dev MUST revert if the lengths of the `collections`, `tokenIds`, `emojis` and `states` arrays are not equal. - * @dev MUST revert if the `deadline` has passed. - * @dev MUST revert if the recovered address is the zero address. - * @param emoters An array of addresses of the accounts that presigned the emotes - * @param collections An array of addresses of the collections containing the tokens being emoted at - * @param tokenIds An array of IDs of the tokens being emoted - * @param emojis An array of unicode identifiers of the emojis - * @param states An array of boolean values signifying whether to emote (`true`) or undo (`false`) emote - * @param deadlines UNIX timestamp of the deadline for the signature to be submitted - * @param v An array of `v` values of an ECDSA signatures of the messages obtained via `prepareMessageToPresignEmote` - * @param r An array of `r` values of an ECDSA signatures of the messages obtained via `prepareMessageToPresignEmote` - * @param s An array of `s` values of an ECDSA signatures of the messages obtained via `prepareMessageToPresignEmote` - */ - function bulkPresignedEmote( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states, - uint256[] memory deadlines, - uint8[] memory v, - bytes32[] memory r, - bytes32[] memory s - ) external; -} -``` - -### Message format for presigned emotes - -The message to be signed by the `emoter` in order for the reaction to be submitted by someone else is formatted as follows: - -```solidity -keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collection, - tokenId, - emoji, - state, - deadline - ) - ); -``` - -The values passed when generating the message to be signed are: - -- `DOMAIN_SEPARATOR` - The domain separator of the Emotable repository smart contract -- `collection` - Address of the collection containing the token being emoted at -- `tokenId` - ID of the token being emoted -- `emoji` - Unicode identifier of the emoji -- `state` - Boolean value signifying whether to emote (`true`) or undo (`false`) emote -- `deadline` - UNIX timestamp of the deadline for the signature to be submitted - -The `DOMAIN_SEPARATOR` is generated as follows: - -```solidity -keccak256( - abi.encode( - "ERC-7409: Public Non-Fungible Token Emote Repository", - "1", - block.chainid, - address(this) - ) - ); -``` - -Each chain, that the Emotable repository smart contract is deployed on, will have a different `DOMAIN_SEPARATOR` value due to chain IDs being different. - -### Pre-determined address of the Emotable repository - -The address of the Emotable repository smart contract is designed to resemble the function it serves. It starts with `0x3110735` which is the abstract representation of `EMOTES`. The address is: - -``` -0x3110735F0b8e71455bAe1356a33e428843bCb9A1 -``` - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **Does the proposal support custom emotes or only the Unicode specified ones?**\ -The proposal only accepts the Unicode identifier which is a `string` value. This means that while we encourage implementers to add the reactions using standardized emojis, the values not covered by the Unicode standard can be used for custom emotes. The only drawback being that the interface displaying the reactions will have to know what kind of image to render and such additions will probably be limited to the interface or marketplace in which they were made. -2. **Should the proposal use emojis to relay the impressions of NFTs or some other method?**\ -The impressions could have been done using user-supplied strings or numeric values, yet we decided to use emojis since they are a well established mean of relaying impressions and emotions. -3. **Should the proposal establish an emotable extension or a common-good repository?**\ -Initially we set out to create an emotable extension to be used with any ERC-721 compliant tokens. However, we realized that the proposal would be more useful if it was a common-good repository of emotable tokens. This way, the tokens that can be reacted to are not only the new ones but also the old ones that have been around since before the proposal.\ -In line with this decision, we decided to calculate a deterministic address for the repository smart contract. This way, the repository can be used by any NFT collection without the need to search for the address on the given chain. -4. **Should we include only single-action operations, only multi-action operations, or both?**\ -We've considered including only single-action operations, where the user is only able to react with a single emoji to a single token, but we decided to include both single-action and multi-action operations. This way, the users can choose whether they want to emote or undo emote on a single token or on multiple tokens at once.\ -This decision was made for the long-term viability of the proposal. Based on the gas cost of the network and the number of tokens in the collection, the user can choose the most cost-effective way of emoting. -5. **Should we add the ability to emote on someone else's behalf?**\ -While we did not intend to add this as part of the proposal when drafting it, we realized that it would be a useful feature for it. This way, the users can emote on behalf of someone else, for example, if they are not able to do it themselves or if the emote is earned through an off-chain activity. -6. **How do we ensure that emoting on someone else's behalf is legitimate?**\ -We could add delegates to the proposal; when a user delegates their right to emote to someone else, the delegate can emote on their behalf. However, this would add a lot of complexity and additional logic to the proposal.\ -Using ECDSA signatures, we can ensure that the user has given their consent to emote on their behalf. This way, the user can sign a message with the parameters of the emote and the signature can be submitted by someone else. -7. **Should we add chain ID as a parameter when reacting to a token?**\ -During the course of discussion of the proposal, a suggestion arose that we could add chain ID as a parameter when reacting to a token. This would allow the users to emote on the token of one chain on another chain.\ -We decided against this as we feel that additional parameter would rarely be used and would add additional cost to the reaction transactions. If the collection smart contract wants to utilize on-chain emotes to tokens they contain, they require the reactions to be recorded on the same chain. Marketplaces and wallets integrating this proposal will rely on reactions to reside in the same chain as well, because if chain ID parameter was supported this would mean that they would need to query the repository smart contract on all of the chains the repository is deployed in order to get the reactions for a given token.\ -Additionally, if the collection creator wants users to record their reactions on a different chain, they can still direct the users to do just that. The repository does not validate the existence of the token being reacted to, which in theory means that you can react to non-existent token or to a token that does not exist yet. The likelihood of a different collection existing at the same address on another chain is significantly low, so the users can react using the collection's address on another chain and it is very unlikely that they will unintentionally react to another collection's token. -8. **Should we use `bytes4` or `strings` to represent emotes?**\ -Initially the proposal used `bytes4`. This was due to the assumption that all of the emojis use UTF-4 encoding, which is not the case.\ -The emojis usually use up to 8 bytes, but the personalized emojis with skin tones use much more. This is why we decided to use `strings` to represent the emotes. This allows the repository to be forward compatible with any future emojis that might be added to the Unicode standard.\ -This is how this proposal fixes the forward compatibility issue of the [ERC-6381](./eip-6381.md). - -## Backwards Compatibility - -The Emote repository standard is fully compatible with [ERC-721](./eip-721.md) and with the robust tooling available for implementations of ERC-721 as well as with the existing ERC-721 infrastructure. - -## Test Cases - -Tests are included in [`emotableRepository.ts`](../assets/eip-7409/test/emotableRepository.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-7409 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`EmotableRepository.sol`](../assets/eip-7409/contracts/EmotableRepository.sol). - -## Security Considerations - -The proposal does not envision handling any form of assets from the user, so the assets should not be at risk when interacting with an Emote repository. - -The ability to use ECDSA signatures to emote on someone else's behalf introduces the risk of a replay attack, which the format of the message to be signed guards against. The `DOMAIN_SEPARATOR` used in the message to be signed is unique to the repository smart contract of the chain it is deployed on. This means that the signature is invalid on any other chain and the Emote repositories deployed on them should revert the operation if a replay attack is attempted. - -Another thing to consider is the ability of presigned message reuse. Since the message includes the signature validity deadline, the message can be reused any number of times before the deadline is reached. The proposal only allows for a single reaction with a given emoji to a specific token to be active, so the presigned message can not be abused to increase the reaction count on the token. However, if the service using the repository relies on the ability to revoke the reaction after certain actions, a valid presigned message can be used to re-react to the token. We suggest that the services using the repository in conjunction with presigned messages use deadlines that invalidate presigned messages after a reasonably short period of time. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7409.md diff --git a/EIPS/eip-7412.md b/EIPS/eip-7412.md index 43e651e70de2d9..66c50b1b1f9b10 100644 --- a/EIPS/eip-7412.md +++ b/EIPS/eip-7412.md @@ -1,169 +1 @@ ---- -eip: 7412 -title: On-Demand Off-Chain Data Retrieval -description: A method to construct multicalls with prepended verifiable off-chain data -author: Noah Litvin (@noahlitvin), db (@dbeal-eth) -discussions-to: https://ethereum-magicians.org/t/erc-7412-on-demand-off-chain-data-retrieval/15346 -status: Draft -type: Standards Track -category: ERC -created: 2023-07-26 ---- - -## Abstract - -Contracts may require off-chain data during execution. A smart contract function could implement the standard proposed here by reverting with `error OracleDataRequired(address oracleContract, bytes oracleQuery)`. Clients supporting this standard would recognize this error message during a simulation of the request, query the specified decentralized oracle network for signed data, and instead stage a transaction with a multicall that prepends the verification of the required off-chain data. The data would be written on-chain during verification to a smart contract for the subsequent call to read, avoiding the error. - -## Motivation - -Ethereum's scaling roadmap involves a series of separate execution contexts for smart contract code (including layer two and layer three scaling solutions). This makes the ability to read data across multiple chains crucial to the construction of scalable applications. Also, for decentralized finance protocols that rely on price data, it is not reasonable to expect oracle networks will be able to continuously push fresh data to every layer two and layer three network for an arbitrary number of price feeds. - -Cross-chain bridges are being developed where smart contract functions can write data to other chains. There is a need for a similar standard that enables reading data from other chains. This standard can be generalized for reading any off-chain data from a decentralized oracle network, including price feeds. - -With standards for both writing and reading cross-chain data, protocol developers will be able to create abstractions for asynchronicity (a topic thoroughly explored in other software engineering contexts). This will enable the development of highly sophisticated protocols that do not suffer from scaling constraints. - -[ERC-3668](./eip-3668.md) introduced the use of reverts for requiring off-chain data, but there are various challenges introduced by the specifics of that standard which are outlined in the _Rationale_ section below. By leveraging multicalls rather than callback functions, the standard proposed here is able to overcome some of these constraints. - -## Specification - -A contract implementing this standard MUST revert with the following error whenever off-chain data is required: - -```solidity -error OracleDataRequired(address oracleContract, bytes oracleQuery) -``` - -`oracleQuery` specifies the off-chain data that is being required. Valid data formats for this parameter are specific to the oracle ID specified by the oracle contract. This might include chain id, contract address, function signature, payload, and timestamp/"latest" for cross-chain reads. For price feeds, it could include a ticker symbol and timestamp/"latest". - -`oracleContract` is the address of the contract which can verify the off-chain data and provide it to the contract to avoid the `OracleDataRequired` error. This contract MUST implement the following interface: - -```solidity -interface IERC7412 { - function oracleId() view external returns (bytes32 oracleId); - function fulfillOracleQuery(bytes signedOffchainData) payable external; -} -``` - -`oracleId` is a unique identifier that references the decentralized oracle network that generates the desired signed off-chain data. Oracle IDs would be analogous to Chain IDs in the Ethereum ecosystem. Clients are expected to resolve a gateway that corresponds to an Oracle ID, similar to how clients are expected to resolve an RPC endpoint based on a Chain ID. - -It should be possible to derive the `oracleQuery` from the `signedOffchainData`, such that the oracle contract is able to provide the verified offchain data based on the `oracleQuery`. - -The contract implementing the `IERC7412` interface MUST revert with the following error message if it requires payment to fulfill the oracle data query: - -```solidity -error FeeRequired(uint amount) -``` - -`amount` specifies the amount of native gas tokens required to execute the `fulfillOracleQuery` function, denominated in wei. This error MUST be resolved if the caller provides sufficient `msg.value` such that the fee amount can be collected by the oracle contract. The contract MAY NOT return gas tokens if they are provided in excess of the `amount`. In practice, we would expect the fee amount to remain relatively stable, if not constant. - -It is the responsibility of the client to decide how to construct the multicall, where necessary the `fulfillOracleQuery` functions are being called before the intended function call in an atomic transaction. Wallets that support account abstraction (per [ERC-4337](./eip-4337.md)) should already have the ability to generate atomic multi-operations. For EOA support, protocols could implement [ERC-2771](./eip-2771.md). A standard multicall contract can only be used to construct multicalls including functions which do not reference `msg.sender` or `msg.data`. - -To prevent data becoming too stale for a request between the simulation and a call's execution, ideally a contract could also emit the following event: `event OracleDataUsed(address oracleContract, bytes oracleQuery, uint expirationTime)` Here, `expirationTime` is the time after which the `OracleDataRequired` error would be thrown by the contract. (This would typically be a calculation involving a staleness tolerance and `block.timestamp`). Client applications that implement this standard would be able to recognize this event during simulation and estimate if an additional update will still be necessary, taking into account the speed of the chain. For example, the oracle query may request the latest quote available for a particular price feed and the expiration time may signal that the price cannot be older than three seconds prior to the current timestamp recognized by the blockchain. This has been omitted from the standard because there isn't a practical way to retrieve event data during transaction simulations on most JSON-RPC APIs at this time. - -Note that `URI` could be used as the `oracleId` with a URI specified as the `oracleQuery`. This would allow this standard to be compliant with arbitrary on-chain URIs without requiring updates to a client library, similar to [ERC-3668](./eip-3668.md). - -## Rationale - -This proposal is essentially an alternative to [ERC-3668](./eip-3668.md) with a few important distinctions: - -- ERC-3668 requires URIs to be encoded on-chain. While this can work well for static assets (such as IPFS hashes for assets related to NFTs and merkle trees), it is not ideal for retrieving data that must be fresh like cross-chain data retrieval or price feeds. Although dynamic data can be referenced with an HTTP URL, this increases centralization and maintenance-related risks. -- By relying on a multicall rather than callbacks, it is much simpler to handle situations in which nested calls require different off-chain data. By the standard proposed here, end users (including those using clients that implement account abstraction) always need to simply sign a transaction, regardless of the complexity of the internal structure of the call being executed. The client can automatically prepend any necessary off-chain data to the transaction for the call to succeed. -- The error is very simple to construct. Developers implementing this standard only need to have awareness of the oracle network they choose to rely on, the form of the query accepted by this network, and the contract from which they expect to retrieve the data. - -With this standard, not only can oracle providers scalably support an unlimited number of networks but they can also be compatible with local/forked networks for protocol development. - -Another major advantage of this standard is that oracles can charge fees in the form of native gas tokens during the on-chain verification of the data. This creates an economic incentive where fees can be collected from data consumers and provided to node operators in the decentralized oracle network. - -## Reference Implementation - -The following pseudocode illustrates an oversimplified version of the client SDK. Ideally, this could be implemented in wallets, but it could also be built into the application layer. This function takes a desired transaction and converts it into a multicall with the required data verification transactions prepended such that the `OracleDataRequired` errors would be avoided: - -```javascript -function prepareTransaction(originalTx) { - let multicallTx = [originalTx]; - while (true) { - try { - const simulationResult = simulateTx(multicallTx); - return multicallTx; - } catch (error) { - if (error instanceof OracleDataRequired) { - const signedRequiredData = fetchOffchainData( - error.oracleContract, - error.oracleQuery - ); - const dataVerificationTx = generateDataVerificationTx( - error.oracleContract, - signedRequiredData - ); - multicallTx.unshift(dataVerificationTx); - } - } - } -} -``` - -An oracle provider could create a contract (that might also perform some pre-processing) that would automatically trigger a request for off-chain data as follows: - -```solidity -contract OracleContract is IERC7412 { - address public constant VERIFIER_CONTRACT = 0x0000; - uint public constant STALENESS_TOLERANCE = 86400; // One day - mapping(bytes32 => bytes) public latestVerifiedData; - - function oracleId() external pure returns (bytes32){ - return bytes32(abi.encodePacked("MY_ORACLE_ID")); - } - - function fulfillOracleQuery(bytes calldata signedOffchainData) payable external { - bytes memory oracleQuery = _verify(signedOffchainData); - latestVerifiedData[keccak256(oracleQuery)] = signedOffchainData; - } - - function retrieveCrossChainData(uint chainId, address contractAddress, bytes payload) internal returns (bytes) { - bytes memory oracleQuery = abi.encode(chainId, contractAddress, payload); - (uint timestamp, bytes response) = abi.decode(latestVerifiedData[oracleQuery], (uint, bytes)); - - if(timestamp < block.timestamp - STALENESS_TOLERANCE){ - revert OracleDataRequired(address(this), oracleQuery); - } - - return response; - } - - function _verify(bytes memory signedOffchainData) payable internal returns (bytes oracleQuery) { - // Insert verification code here - // This may revert with error FeeRequired(uint amount) - } - -} -``` - -Now a top-level protocol smart contract could implement a cross-chain function like so: - -```solidity -interface ICrosschainContract { - function functionA(uint x) external returns (uint y); - function functionB(uint x) external returns (uint y); -} - -contract CrosschainAdder { - IERC7412 oracleContract = 0x0000; - - function add(uint chainIdA, address contractAddressA, uint chainIdB, address contractAddressB) external returns (uint sum){ - sum = abi.decode(oracleContract.retrieveCrossChainData(chainIdA, contractAddressA, abi.encodeWithSelector(ICrosschainContract.functionA.selector,1)), (uint)) + abi.decode(oracleContract.retrieveCrossChainData(chainIdB, contractAddressB, abi.encodeWithSelector(ICrosschainContract.functionB.selector,2)),(uint)); - } -} -``` - -Note that the developer of the `CrosschainAdder` function does not need to be concerned with the implementation of this standard. The `add` function can simply call the function on the oracle contract as if it were retrieving on-chain data normally. - -Cross-chain functions like this could also be leveraged to avoid O(n) (and greater) loops on-chain. For example, `chainIdA` and `chainIdB` could reference the same chain that the `CrosschainAdder` contract is deployed on with `functionA` and `functionB` as view functions with computationally intensive loops. - -## Security Considerations - -One potential risk introduced by this standard is that its reliance on multicalls could obfuscate transaction data in wallet applications that do not have more sophisticated transaction decoding functionality. This is an existing challenge being addressed by wallet application developers, as multicalls are increasingly common in protocol development outside of this standard. - -Note that it is the responsibility of the verifier contract to confirm the validity of the data provided from the oracle network. This standard does not create any new opportunities for invalid data to be provided to a smart contract. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7412.md diff --git a/EIPS/eip-7417.md b/EIPS/eip-7417.md index 7a7150a92b523d..7ecaea95a87eb0 100644 --- a/EIPS/eip-7417.md +++ b/EIPS/eip-7417.md @@ -1,239 +1 @@ ---- -eip: 7417 -title: Token Converter -description: Smart-contract service that converts token of one ERC version to another -author: Dexaran (@Dexaran) -discussions-to: https://ethereum-magicians.org/t/token-standard-converter/15252 -status: Draft -type: Standards Track -category: ERC -created: 2023-07-27 -requires: 20, 165, 223 ---- - -## Abstract - -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. - -## 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. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -The Token Converter system comprises two main parts: - -- Converter contract - -- [ERC-223](./eip-223.md) wrapper contract for each [ERC-20](./eip-20.md) token - -Converter contract can deploy new [ERC-223](./eip-223.md) wrapper contracts for any [ERC-20](./eip-20.md) token that does not have a [ERC-223](./eip-223.md) wrapper currently. There MUST be exactly one [ERC-223](./eip-223.md) wrapper for each [ERC-20](./eip-20.md) token. - -Converter contract MUST accept deposits of [ERC-20](./eip-20.md) tokens and send [ERC-223](./eip-223.md) tokens to the depositor at 1:1 ratio. Upon depositing 1234 units of `ERC-20 token_A` the depositor MUST receive exactly 1234 units of `ERC-223 token_A`. This is done by issuing new [ERC-223](./eip-223.md) tokens at the moment of [ERC-20](./eip-20.md) deposit. The original [ERC-20](./eip-20.md) tokens MUST be frozen in the Converter contract and available for claiming back. - -Converter contract MUST accept deposits of [ERC-223](./eip-223.md) tokens and send [ERC-20](./eip-20.md) tokens to the depositor at 1:1 ratio. This is done by releasing the original [ERC-20](./eip-20.md) tokens at the moment of [ERC-223](./eip-223.md) deposit. The deposited [ERC-223](./eip-223.md) tokens must be burned. - -### Token Converter - -#### Conver contract methods - -##### `getWrapperFor` - -```solidity -function getWrapperFor(address _erc20Token) 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. - -##### `getOriginFor` - -```solidity -function getOriginFor(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. - -##### `createERC223Wrapper` - -```solidity -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. - -##### `convertERC20toERC223` - -```solidity -function convertERC20toERC223(address _erc20token, uint256 _amount) public returns (bool) -``` - -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. - -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. - -##### `tokenReceived` - -```solidity -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. - -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. - -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. - -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. - -Returns `0x8943ec02`. - -##### `rescueERC20` - -```solidity -function rescueERC20(address _token) external -``` - -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. - -### Converting [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) - -In order to convert [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) the token holder should: - -1. Call the `approve` function of the [ERC-20](./eip-20.md) token and allow Token Converter to withdraw tokens from the token holders address via `transferFrom` function. -2. Wait for the transaction with `approve` to be submitted to the blockchain. -3. Call the `convertERC20toERC223` function of the Token Converter contract. - -### Converting [ERC-223](./eip-223.md) tokens back to [ERC-20](./eip-20.md) - -In order to convert [ERC-20](./eip-20.md) tokens to [ERC-223](./eip-223.md) the token holder should: - -1. Send [ERC-223](./eip-223.md) tokens to the address of the Token Converter contract via `transfer` function of the [ERC-223](./eip-223.md) token contract. - -## Rationale - - - -TBD - -## Backwards Compatibility - -This proposal is supposed to eliminate the backwards compatibility concerns for different token standards making them interchangeable and interoperable. - -This service is the first of its kind and therefore does not have any backwards compatibility issues as it does not have any predecessors. - -## Reference Implementation - -```solidity - - address public ownerMultisig; - - mapping (address => ERC223WrapperToken) public erc223Wrappers; // A list of token wrappers. - // First one is [ERC-20](./eip-20.md) origin, - // second one is [ERC-223](./eip-223.md) version. - mapping (address => address) public erc20Origins; - mapping (address => uint256) public erc20Supply; // Token => how much was deposited. - - function getWrapperFor(address _erc20Token) public view returns (address) - { - return address(erc223Wrappers[_erc20Token]); - } - - function getOriginFor(address _erc223WrappedToken) public view returns (address) - { - return erc20Origins[_erc223WrappedToken]; - } - - function tokenReceived(address _from, uint _value, bytes memory _data) public override returns (bytes4) - { - require(erc20Origins[msg.sender] != address(0), "ERROR: The received token is not a [ERC-223](./eip-223.md) Wrapper for any [ERC-20](./eip-20.md) token."); - safeTransfer(erc20Origins[msg.sender], _from, _value); - - erc20Supply[erc20Origins[msg.sender]] -= _value; - erc223Wrappers[msg.sender].burn(_value); - - return 0x8943ec02; - } - - function createERC223Wrapper(address _erc20Token) public returns (address) - { - require(address(erc223Wrappers[_erc20Token]) == address(0), "ERROR: Wrapper already exists."); - require(getOriginFor(_erc20Token) == address(0), "ERROR: Cannot convert [ERC-223](./eip-223.md) to [ERC-223](./eip-223.md)."); - - ERC223WrapperToken _newERC223Wrapper = new ERC223WrapperToken(_erc20Token); - erc223Wrappers[_erc20Token] = _newERC223Wrapper; - erc20Origins[address(_newERC223Wrapper)] = _erc20Token; - - return address(_newERC223Wrapper); - } - - function convertERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool) - { - // If there is no active wrapper for a token that user wants to wrap - // then create it. - if(address(erc223Wrappers[_ERC20token]) == address(0)) - { - createERC223Wrapper(_ERC20token); - } - 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."); - - erc223Wrappers[_ERC20token].mint(msg.sender, _amount); - - return true; - } - - 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 transferOwnership(address _newOwner) public - { - require(msg.sender == ownerMultisig, "ERROR: Only owner can do this."); - ownerMultisig = _newOwner; - } - - 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 safeTransferFrom(address token, address from, address to, uint value) internal { - // bytes4(keccak256(bytes('transferFrom(address,address,uint256)'))); - (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. -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. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7417.md diff --git a/EIPS/eip-7425.md b/EIPS/eip-7425.md index 1b407a37bcc647..17ccf23c9f5c04 100644 --- a/EIPS/eip-7425.md +++ b/EIPS/eip-7425.md @@ -1,179 +1 @@ ---- -eip: 7425 -title: Tokenized Reserve -description: Transparent reserve fund on-chain with stakeholder participation. -author: Jimmy Debe (@jimstir) -discussions-to: https://ethereum-magicians.org/t/eip-7425-tokenized-reserve/15297 -status: Draft -type: Standards Track -category: ERC -created: 2023-06-30 -requires: 20, 4626 ---- - -## Abstract - -A proposal for a tokenized reserve mechanism. The reserve allows an audit of on-chain actions of the owner. Using [ERC-4626](../EIPS/eip-4626.md), stakeholders can create shares to show support for actions in the reserve. - -## Motivation - -Tokenized reserves are an extension of tokenized vaults. The goal is to create a reserve similar to a real world reserve an entity has as a backup in case regular funds run low. In the real world, an entity will have to meet certain criteria before accessing reserve funds. In a decentralized environment, an entity can incorporate stakeholders into their criteria. This will help entities who participate in decentralized environments to be transparent with stakeholders. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Definitions: - - - owner: The creator of the reserve - - user: Stakeholders of specific proposals - - reserve: The tokenized reserve contract - - proposal: Occurs when the owner wants a withdrawal from contract - -### Constructor: - - - name: ERC-20 token name - - ticker: ERC-20 ticker - - asset: ERC-4626 underlying ERC-20 address - - rAuth: Primary authorized user - - rOwner: Owner of the Reserve - -### Interface - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; -import "./ERC4626.sol"; - -interface TokenReserve is ERC4626{ - /** - * @dev Event emitted after a new proposal is created - */ - event proposals( - address indexed token, - uint256 indexed proposalNum, - uint256 indexed amount, - address recipient - ); - /** - * @dev Event emitted after a new deposit is made by the owner - */ - event depositR( - address indexed token, - uint256 indexed amount, - uint256 indexed time, - uint256 count - ); - /** - * @dev Get time of a deposit made to reserve with depositReserve() - * @param count Number matching deposit - * @return block.timestamp format - */ - function depositTime(uint256 count) external view returns (uint256); - /** - * @dev Get amount deposited to reserve with depositReserve() - * @param count Number of deposit - * @return uint256 number of any asset that were deposited - */ - function ownerDeposit(uint256 count) external view returns(uint256); - /** - * @dev Token type deposited to reserve with depositReserve() - * - MUST be an address of ERC20 token - * @param count Number of deposit - */ - function tokenDeposit(uint256 count) external view returns(address); - /** - * @dev Amount deposited for shares of proposal by the user - * - MUST be an ERC20 address - * @param user address of user - * @param proposal number of the proposal the user deposited - */ - function userDeposit(address user, uint256 proposal) external view returns(uint256); - /** - * @dev Token used for given proposal - * - MUST be ERC20 address - * @param proposal number for requested token - * @return token address - */ - function proposalToken(uint256 proposal) external view returns(address); - /** - * @dev Amount withdrawn for given opened proposal - */ - function proposalWithdrew(uint256 proposal) external view returns(uint256); - /** - * @dev Amount received for given closed proposal - */ - function proposalDeposited(uint256 proposal) external view returns(uint256); - /** - * @dev Make a deposit to a proposal creating new shares with deposit function from ERC-4626 - * - MUST be opened proposal - * - MUST NOT be opened proposal that was closed - * NOTE: using the deposit() will cause assets to not be accounted for in a proposal - * @param assets Amount being deposited - * @param receiver Address of depositor - * @param proposal Number associated proposal - */ - function proposalDeposit(uint256 assets, address receiver, uint256 proposal) external virtual returns(uint256); - /** - * @dev Burn shares, receive 1 to 1 value of shares - * - MUST have closed proposalNumber - * - MUST have userDeposit greater than or equal to userWithdrawal - * @param assets Amount being deposited - * @param receiver Address of receiver - * @param owner Address of token owner - * @param proposal Number associated proposal - */ - function proposalWithdraw(uint256 assets, address receiver, address owner, uint256 proposal)external virtual returns(uint256); - /** - * @dev Issue new proposal - * - MUST create new proposal number - * - MUST account for amount withdrawn - * - MUST emit proposals event - * @param token Address of ERC-20 token - * @param amount Token amount being withdrawn - * @param receiver Address of token recipient - */ - function proposalOpen(address token, uint256 amount, address receiver) external virtual returns (uint256); - /** - * @dev Make deposit and/or choose to close an opened proposal - * - MUST account for amount received - * - MUST be a proposal that is less than or equal to current proposal - * - MUST emit proposals event - * @param token Address of ERC-20 token - * @param proposal Number of desired proposal - * @param amount Token amount being deposited to the reserve - * @param close Choose to close the proposal - */ - function proposalClose(address token, uint256 proposal, uint256 amount, bool close) external virtual returns (bool); - /** - * @dev Optional accounting for tokens deposited by owner - * - MUST be reserve owner - * - MUST emit depositR event - * NOTE: No shares are issued, funds can not be redeemed. Only withdrawn from proposalOpen - * @param token Address of ERC-20 token - * @param sender Address of where tokens from - * @param amount Token amount being deposited - */ - function depositReserve(address token, address sender, uint256 amount) external virtual; -} - -``` - -## Rationale - -This proposal is designed to be a basic implementation of a reserve fund interface. Other non specified conditions should be addressed on a case by case basis. Each reserve uses [ERC-20](../EIPS/eip-20.md) standard for shares, and [ERC-4626](../EIPS/eip-4626.md) for the creation of shares. The reserve token can be the underlying token in [ERC-4626](../EIPS/eip-4626.md) or the shares that are created when the underlying token is deposited in the vault. -[ERC-4626](../EIPS/eip-4626.md) is implemented to the reserve to account for user participation. There needs to be a representation for users participating in a proposal within a reserve. With vaults, the implementor could decide how to treat participation based on users entering the vault. For example, a user could be forced not to use the same tokens in multiple proposals to allow shares to be created fairly. Once the underlying token is deposited into the vault for an open proposal those tokens could not be accessible until the proposal is closed. -It is not explicitly enforced that deposited tokens that create shares cannot be withdrawn by the owner of the reserve. On a case by case basis there can be implementions to ensure those tokens are accounted for if needed. - -## Backwards Compatibility - -Tokenized reserves are made compatible with [ERC-20](../EIPS/eip-20.md) and [ERC-4626](../EIPS/eip-4626.md). - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7425.md diff --git a/EIPS/eip-7432.md b/EIPS/eip-7432.md index cd55e89d0e1248..68c52ade6a0532 100644 --- a/EIPS/eip-7432.md +++ b/EIPS/eip-7432.md @@ -1,433 +1 @@ ---- -eip: 7432 -title: Non-Fungible Token Roles -description: Role Management for NFTs. Enables accounts to share the utility of NFTs via expirable role assignments. -author: Ernani São Thiago (@ernanirst), Daniel Lima (@karacurt) -discussions-to: https://ethereum-magicians.org/t/eip-7432-non-fungible-token-roles/15298 -status: Review -type: Standards Track -category: ERC -created: 2023-07-14 -requires: 165 ---- - -## Abstract - -This standard introduces role management for NFTs. Each role assignment is associated with a single NFT and expires -automatically at a given timestamp. Roles are defined as `bytes32` and feature a custom `_data` field of arbitrary size -to allow customization. - -## Motivation - -The NFT Roles interface aims to establish a standard for role management in NFTs. Tracking on-chain roles enables -decentralized applications (dApps) to implement access control for privileged actions, e.g., minting tokens with a role -(airdrop claim rights). - -NFT roles can be deeply integrated with dApps to create a utility-sharing mechanism. A good example is in digital real -estate. A user can create a digital property NFT and grant a `keccak256("PROPERTY_MANAGER")` role to another user, -allowing them to delegate specific utility without compromising ownership. The same user could also grant multiple -`keccak256("PROPERTY_TENANT")` roles, allowing the grantees to access and interact with the digital property. - -There are also interesting use cases in decentralized finance (DeFi). Insurance policies could be issued as NFTs, and -the beneficiaries, insured, and insurer could all be on-chain roles tracked using this standard. - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", -"NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC-2119 and RFC-8174. - -Compliant contracts MUST implement the following interface: - -```solidity -/// @title ERC-7432 Non-Fungible Token Roles -/// @dev See https://eips.ethereum.org/EIPS/eip-7432 -/// Note: the ERC-165 identifier for this interface is 0x04984ac8. -interface IERC7432 /* is ERC165 */ { - struct RoleData { - uint64 expirationDate; - bool revocable; - bytes data; - } - - struct RoleAssignment { - bytes32 role; - address tokenAddress; - uint256 tokenId; - address grantor; - address grantee; - uint64 expirationDate; - bytes data; - } - - /** Events **/ - - /// @notice Emitted when a role is granted. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user assigning the role. - /// @param _grantee The user receiving the role. - /// @param _expirationDate The expiration date of the role. - /// @param _revocable Whether the role is revocable or not. - /// @param _data Any additional data about the role. - event RoleGranted( - bytes32 indexed _role, - address indexed _tokenAddress, - uint256 indexed _tokenId, - address _grantor, - address _grantee, - uint64 _expirationDate, - bool _revocable, - bytes _data - ); - - /// @notice Emitted when a role is revoked. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _revoker The user revoking the role. - /// @param _grantee The user that receives the role revocation. - event RoleRevoked( - bytes32 indexed _role, - address indexed _tokenAddress, - uint256 indexed _tokenId, - address _revoker, - address _grantee - ); - - /// @notice Emitted when a user is approved to manage any role on behalf of another user. - /// @param _tokenAddress The token address. - /// @param _operator The user approved to grant and revoke roles. - /// @param _isApproved The approval status. - event RoleApprovalForAll( - address indexed _tokenAddress, - address indexed _operator, - bool _isApproved - ); - - /** External Functions **/ - - /// @notice Grants a role on behalf of a user. - /// @param _roleAssignment The role assignment data. - function grantRoleFrom(RoleAssignment calldata _roleAssignment) external; - - /// @notice Grants a role on behalf of a user. - /// @param _roleAssignment The role assignment data. - function grantRevocableRoleFrom(RoleAssignment calldata _roleAssignment) external; - - /// @notice Revokes a role on behalf of a user. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _revoker The user revoking the role. - /// @param _grantee The user that receives the role revocation. - function revokeRoleFrom( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _revoker, - address _grantee - ) external; - - /// @notice Approves operator to grant and revoke any roles on behalf of another user. - /// @param _tokenAddress The token address. - /// @param _operator The user approved to grant and revoke roles. - /// @param _approved The approval status. - function setRoleApprovalForAll( - address _tokenAddress, - address _operator, - bool _approved - ) external; - - /** View Functions **/ - - /// @notice Checks if a user has a role. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function hasNonUniqueRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (bool); - - /// @notice Checks if a user has a unique role. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function hasRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (bool); - - /// @notice Returns the custom data of a role assignment. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function roleData( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (RoleData memory data_); - - /// @notice Returns the expiration date of a role assignment. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function roleExpirationDate( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (uint64 expirationDate_); - - /// @notice Checks if the grantor approved the operator for all NFTs. - /// @param _tokenAddress The token address. - /// @param _grantor The user that approved the operator. - /// @param _operator The user that can grant and revoke roles. - function isRoleApprovedForAll( - address _tokenAddress, - address _grantor, - address _operator - ) external view returns (bool); - - /// @notice Returns the last user to receive a role. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that granted the role. - function lastGrantee( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor - ) external view returns (address); -} -``` - -### Metadata Extension - -The Roles Metadata extension extends the traditional JSON-based metadata schema of NFTs. Therefore, DApps supporting -this feature MUST also implement the metadata extension of [ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md). This -extension is **optional** and allows developers to provide additional information for roles. - -Updated Metadata Schema: - -```js -{ - - /** Existing NFT Metadata **/ - - "title": "Asset Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive" - } - }, - - /** Additional fields for Roles **/ - - "roles": [ - { - "id": { - "type": "bytes32", - "description": "Identifies the role" - }, - "name": { - "type": "string", - "description": "Human-readable name of the role" - }, - "description": { - "type": "string", - "description": "Describes the role" - }, - "isUniqueRole": { - "type": "boolean", - "description": "Whether the role supports simultaneous assignments or not" - }, - "inputs": [ - { - "name": { - "type": "string", - "description": "Human-readable name of the argument" - }, - "type": { - "type": "string", - "description": "Solidity type, e.g., uint256 or address" - } - } - ] - } - ] - -} -``` - -The following JSON is an example of [ERC-7432](./eip-7432.md) Metadata: - -```js -{ - // ... Existing NFT Metadata - - "roles": [ - { - // keccak256("PROPERTY_MANAGER") - "id": "0x5cefc88e2d50f91b66109b6bb76803f11168ca3d1cee10cbafe864e4749970c7", - "name": "Property Manager", - "description": "The manager of the property is responsible for furnishing it and ensuring its good condition.", - "isUniqueRole": false, - "inputs": [] - }, - { - // keccak256("PROPERTY_TENANT") - "id": "0x06a3b33b0a800805559ee9c64f55afd8a43a05f8472feb6f6b77484ff5ac9c26", - "name": "Property Tenant", - "description": "The tenant of the property is responsible for paying the rent and keeping the property in good condition.", - "isUniqueRole": true, - "inputs": [ - { - "name": "rent", - "type": "uint256" - } - ] - } - ] - -} -``` - -The `roles` array properties are SUGGESTED, and developers should add any other relevant information as necessary (e.g., -an image for the role). However, it's highly RECOMMENDED to include the `isUniqueRole` property, as this field is -used to determine if the `hasRole` or `hasNonUniqueRole` function should be called (refer to -[Unique and Non-Unique Roles](#unique-and-non-unique-roles)). - -It's also important to highlight the importance of the `inputs` property. This field describes the parameters that -should be encoded and passed to the `grantRole` function. It's RECOMMENDED to use the properties `type` and `components` -defined on the Solidity ABI Specification, where `type` is the canonical type of the parameter, and `components` is used -for complex tuple types. - -### Caveats - -* Compliant contracts MUST implement the `IERC7432` interface. -* A role is represented by a `bytes32`, and it's RECOMMENDED to use the `keccak256` of the role's name for this purpose: - `bytes32 role = keccak256("ROLE_NAME")`. -* The `grantRoleFrom` and `grantRevocableRoleFrom` functions MUST revert if the `_expirationDate` is in the past or if - the `msg.sender` is not approved to grant roles on behalf of the `_grantor`. It MAY be implemented as `public` or - `external`. -* The `revokeRole` and `revokeRoleFrom` functions SHOULD revert if `_revocable` is `false`. They MAY be implemented as `public` or `external`. -* The `revokeRoleFrom` function MUST revert if the `msg.sender` is not approved to revoke roles on behalf of the `_revoker` or the `_grantee`. - It MAY be implemented as `public` or `external`. -* The `setRoleApprovalForAll` function MAY be implemented as `public` or `external`. -* The `hasNonUniqueRole` function MAY be implemented as `pure` or `view` and SHOULD only confirm if the role assignment - exists and is not expired. -* The `hasRole` function MAY be implemented as `pure` or `view` and SHOULD check if the assignment exists, is not - expired and is the last one granted (does not support simultaneous role assignments). -* The `roleData` function MAY be implemented as `pure` or `view`, and SHOULD return an empty struct if the role - assignment does not exist. -* The `roleExpirationDate` function MAY be implemented as `pure` or `view`, and SHOULD return zero if the role - assignment does not exist. -* The `isRoleApprovedForAll` function MAY be implemented as `pure` or `view` and SHOULD only return `true` if the - `_operator` is approved to grant and revoke roles for all NFTs on behalf of the `_grantor`. -* Compliant contracts SHOULD support [ERC-165](./eip-165.md). - -## Rationale - -[ERC-7432](./eip-7432.md) IS NOT an extension of [ERC-721](./eip-721.md). The main reason behind this decision is to -keep the standard agnostic of any NFT implementation. This approach also enables the standard to be implemented -externally or on the same contract as the NFT, and allow dApps to use roles with immutable NFTs. - -### Automatic Expiration - -Automatic expiration is implemented via the `grantRole` and `hasRole` functions. `grantRole` is responsible for setting -the expiration date, and `hasRole` checks if the role is expired by comparing with the current block timestamp -(`block.timestamp`). Since `uint256` is not natively supported by most programming languages, dates are represented as -`uint64` on this standard. The maximum UNIX timestamp represented by a `uint64` is about the year `584,942,417,355`, -which should be enough to be considered "permanent". For this reason, it's RECOMMENDED using `type(uint64).max` when -calling the `grantRole` function to support use cases that require an assignment never to expire. - -### Revocable Roles - -In certain scenarios, the grantor may need to revoke a role before its expiration date, while in others, the grantee -requires assurance that the grantor cannot revoke the role. The `_revocable` parameter was introduced to the `grantRole` -function to support both use cases and specify whether the `grantor` can revoke assigned roles. - -Regardless of the value of `_revocable`, it's RECOMMENDED always to enable the `grantee` to revoke received roles, -allowing recipients to eliminate undesirable assignments. - -### Unique and Non-Unique Roles - -The standard supports both unique and non-unique roles. Unique roles can be assigned to only one account at a time, -while non-unique roles can be granted to multiple accounts simultaneously. The roles extension metadata can be used to -specify if roles are unique or not, and if the contract does not implement it, all roles should be considered unique. - -To verify the validity of a unique role, dApps SHOULD use the `hasRole` function, which also checks if no other -assignment was granted afterward. In other words, for unique roles, each new assignment invalidates the previous one, -and only the last one can be valid. - -### Custom Data - -DApps can customize roles using the `_data` parameter of the `grantRole` function. `_data` is implemented using the -generic type `bytes` to enable dApps to encode any role-specific information when creating a role assignment. The custom -data is retrievable using the `roleData` function and is emitted with the `RoleGranted` event. With this approach, -developers can integrate this information into their applications, both on-chain and off-chain. - -### Role Approval - -Similar to [ERC-721](./eip-721.md), this standard enables users to approve other accounts to grant and revoke roles on -its behalf. This functionality was introduced to allow third-parties to interact with ERC-7432 without requiring NFT -ownership. Compliant contracts MUST implement the functions `setRoleApprovalForAll` and `isRoleApprovedForAll` to -deliver this feature. - -## Backwards Compatibility - -On all functions and events, the standard requires both the `tokenAddress` and `tokenId` to be provided. This -requirement enables dApps to use a standalone [ERC-7432](./eip-7432.md) contract as the authoritative source for the -roles of immutable NFTs. It also helps with backward compatibility as NFT-specific functions such as `ownerOf` are no -longer required. Consequently, this design ensures a more straightforward integration with different implementations of -NFTs. - -## Reference Implementation - -See [ERC-7432.sol](../assets/eip-7432/ERC7432.sol). - -## Security Considerations - -Developers integrating the Non-Fungible Token Roles interface should consider the following on their implementations: - -* Ensure proper access controls are in place to prevent unauthorized role assignments or revocations. -* Take into account potential attack vectors such as reentrancy and ensure appropriate safeguards are in place. -* Since this standard does not check NFT ownership, it's the responsibility of the dApp to query for the NFT Owner and - pass the correct `_grantor` to the `hasRole` function. -* It's the responsibility of the dApp to confirm if the role is unique or non-unique. When the role is unique, the dApp - SHOULD use the `hasRole` function to check for the validity of the assignment. `hasNonUniqueRole` SHOULD be used when - the role can be granted to multiple accounts simultaneously (hence, non-unique). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7432.md diff --git a/EIPS/eip-7444.md b/EIPS/eip-7444.md index 1ce0eed37bba92..8e52a37d08f177 100644 --- a/EIPS/eip-7444.md +++ b/EIPS/eip-7444.md @@ -1,174 +1 @@ ---- -eip: 7444 -title: Time Locks Maturity -description: Interface for conveying the date upon which a time-locked system becomes unlocked -author: Thanh Trinh (@thanhtrinh2003) , Joshua Weintraub (@jhweintraub) , Rob Montgomery (@RobAnon) -discussions-to: https://ethereum-magicians.org/t/eip-idea-timelock-maturity/15321 -status: Draft -type: Standards Track -category: ERC -created: 2023-06-05 -requires: 165 ---- - -## Abstract - -This EIP defines a standardized method to communicate the date on which a time-locked system will become unlocked. This allows for the determination of maturities for a wide variety of asset classes and increases the ease with which these assets may be valued. - -## Motivation - -Time-locks are ubiquitous, yet no standard on how to determine the date upon which they unlock exists. Time-locked assets experience theta-decay, where the time remaining until they become unlocked dictates their value. Providing a universal standard to view what date they mature on allows for improved on-chain valuations of the rights to these illiquid assets, particularly useful in cases where the rights to these illiquid assets may be passed between owners through semi-liquid assets such as [ERC-721](./eip-721.md) or [ERC-1155](./eip-1155.md). - -## 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. - -**Every [ERC-7444](./eip-7444.md) compliant contract must implement [ERC-165](./eip-165.md) interface detection** - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface ERC-7444 { - / - * @notice This function returns the timestamp that the time lock specified by `id` unlocks at - * @param id The identifier which describes a specific time lock - * @return maturity The timestamp of the time lock when it unlocks - */ - function getMaturity(bytes32 id) - external - view - returns (uint256 maturity); - -} -``` - -The maturity return parameter should be implemented in the Unix timestamp standard, which has been used widely in solidity. For example, `block.timestamp` represents the Unix timestamp when a block is mined in 256-bit value. - -For singleton implementations on fungible assets, values passed to `id` SHOULD be ignored, and queries to such implementations should pass in `0x0` - -## Rationale - -### Universal Maturities on Locked Assets - -Locked Assets have become increasingly popular and used in different parts of defi, such as yield farming and vested escrow concept. This has increased the need to formalize and define an universal interface for all these timelocked assets. - -### Valuation of Locked Assets via the Black-Scholes Model - - Locked Assets cannot be valued normally since the value of these assets can be varied through time and many other different factors throughout the locking time. For instance, The Black-Scholes Model or Black-Scholes-Merton model is an example of a suitable model to estimate the theoretical value of asset with the consideration of impact of time and other potential risks. - -![Black-Sholes Model](../assets/eip-7444/equation.png) - -- $C=\text{call option price}$ -- $N=\text{CDF of the normal distribution}$ -- $S_t=\text{spot price of an asset}$ -- $K=\text{strike price}$ -- $r=\text{risk-free interest rate}$ -- $t=\text{time to maturity}$ -- $\sigma=\text{volatility of the asset}$ - -Time to maturity plays an important role in evaluating the price of timelocked assets, thus the demand to have a common interface for retrieving the data is inevitable. - -## Backwards Compatibility - -This standard can be implemented as an extension to [ERC-721](./eip-721.md) and/or [ERC-1155](./eip-1155.md) tokens with time-locked functionality, many of which can be retrofitted with a designated contract to determine the point at which their time locks release. - -## Reference Implementation - -### Locked [ERC-20](./eip-20.md) implementation - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract LockedERC20ExampleContract implements ERC-7444{ - ERC20 public immutable token; - uint256 public totalLocked; - - //Timelock struct - struct TimeLock { - address owner; - uint256 amount; - uint256 maturity; - bytes32 lockId; - } - - //maps lockId to balance of the lock - mapping(bytes32 => TimeLock) public idToLock; - - function constructor( - address _token, - ) public { - token = ERC20(_token); - } - - //Maturity is not appropriate - error LockPeriodOngoing(); - error InvalidReceiver(); - error TransferFailed(); - - /// @dev Deposit tokens to be locked in the requested locking period - /// @param amount The amount of tokens to deposit - /// @param lockingPeriod length of locking period for the tokens to be locked - function deposit(uint256 amount, uint256 lockingPeriod) external returns (bytes32 lockId) { - uint256 maturity = block.timestamp + lockingPeriod; - lockId = keccack256(abi.encode(msg.sender, amount, maturity)); - - require(idToLock[lockId].maturity == 0, "lock already exists"); - - if (!token.transferFrom(msg.sender, address(this), amount)) { - revert TransferFailed(); - } - - TimeLock memory newLock = TimeLock(msg.sender, amount, maturity, lockedId); - - totalLocked += amount; - - idToLock[lockId] = newLock; - - } - - /// @dev Withdraw tokens in the lock after the end of the locking period - /// @param lockId id of the lock that user have deposited in - function withdraw(bytes32 lockId) external { - TimeLock memory lock = idToLock[lockId]; - - if (msg.sender != lock.owner) { - revert InvalidReceiver(); - } - - if (block.timestamp > lock.maturity) { - revert LockPeriodOngoing(); - } - - totalLocked -= lock.amount; - - //State cleanup - delete idToLock[lockId]; - - if (!token.transfer(msg.sender, lock.amount)) { - revert TransferFailed(); - } - - } - - function getMaturity(bytes32 id) external view returns (uint256 maturity) { - return idToLock[id].maturity; - } -} - -``` - -## Security Considerations - -### Extendable Time Locks - -Users or developers should be aware of potential extendable timelocks, where the returned timestamp can be modified through protocols. Users or protocols should check the timestamp carefully before trading or lending with others. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7444.md diff --git a/EIPS/eip-7484.md b/EIPS/eip-7484.md index 4f855ee90ced40..71b332a67a3c75 100644 --- a/EIPS/eip-7484.md +++ b/EIPS/eip-7484.md @@ -1,272 +1 @@ ---- -eip: 7484 -title: Registries and Adapters for Smart Accounts -description: Adapters that allow modular smart contract accounts to verify the security of modules using a Module Registry -author: Konrad Kopp (@kopy-kat), zeroknots (@zeroknots) -discussions-to: https://ethereum-magicians.org/t/erc-7484-registry-adapters-for-smart-accounts/15434 -status: Draft -type: Standards Track -category: ERC -created: 2023-08-14 -requires: 4337 ---- - -## Abstract - -This proposal standardises the interface and functionality of Module Registries, allowing modular smart contract accounts to verify the security of modules using a Registry Adapter. It also provides a reference implementation of a Singleton Module Registry. - -## Motivation - -[ERC-4337](./eip-4337.md) standardises the execution flow of contract accounts and other efforts aim to standardise the modular implementation of these accounts, allowing any developer to build modules for these modular accounts (hereafter Smart Accounts). However, adding third-party modules into Smart Accounts unchecked opens up a wide range of attack vectors. - -One solution to this security issue is to create a Module Registry that stores security attestations on Modules and allows Smart Accounts to query these attestations before using a module. This standard aims to achieve two things: - -1. Standardise the interface and required functionality of Module Registries. -2. Standardise the functionality of Adapters that allow Smart Accounts to query Module Registries. - -This ensures that Smart Accounts can securely query Module Registries and handle the Registry behavior correctly, irrespective of their architecture, execution flows and security assumptions. This standard also provides a reference implementation of a Singleton Module Registry that is ownerless and can be used by any Smart Account. While we see many benefits of the entire ecosystem using this single Module Registry (see `Rationale`), we acknowledge that there are tradeoffs to using a singleton and thus this standard does not require Smart Accounts to use the reference implementation. Hence, this standard ensures that Smart Accounts can query any Module Registry that implements the required interface and functionality, reducing integration overhead and ensuring interoperability for Smart Accounts. - -## Specification - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Definitions - -- **Smart account** - An ERC-4337 compliant smart contract account that has a modular architecture. -- **Module** - Self-contained smart account functionality. -- **Attestation** - Onchain assertions made about the security of a module. -- **Attester** - The entity that makes an attestation about a module. -- **(Module) Registry** - A contract that stores an onchain list of attestations about modules. -- **Adapter** - Smart account functionality that handles the fetching and validation of attestations from the Registry. - -### Required Registry functionality - -There are 2 separate required Registry methods: `check` and `checkN` - -- `check` is used to check the attestation on a module by a single attester. -- `checkN` is used to check the attestation on a module by multiple attesters. - -The core interface for a Registry is as follows: - -```solidity -interface IRegistry { - function check(address module, address attester) public view returns (uint256); - - function checkN(address module, address[] memory attesters, uint256 threshold) external view returns (uint256[] memory); -} -``` - -The Registry MUST implement the following functionality: - -- Verify that an attester is the creator of an attestation, for example by checking `msg.sender` or by using signatures, before storing it. -- Allow attesters to revoke attestations that they have made. -- Store either the attestation data or a reference to the attestation data. -- Implement `check` and `checkN` as specified below. - -The Registry SHOULD implement the following additional functionality: - -- Allow attesters to specify an expiry date for their attestations and revert during `check` or `checkN` if an attestation is expired. -- Implement a view function that allows an adapter or offchain client to read the data for a specific attestation. - -#### `check` - -Takes two arguments: `module` and `attester`. - -- The Registry MUST revert if the `attester` has not made an attestation on the `module`. -- The Registry MUST revert if the `attester` has revoked their attestation on the `module`. - -Returns a `uint256` of the timestamp at which the attestation was created. - -#### `checkN` - -Takes three arguments: `module`, `attesters` and `threshold`. - -Note: `threshold` may be 0. - -- The Registry MUST revert if the number of `attesters` that have made an attestation on the `module` is smaller than the `threshold`. -- The Registry MUST revert if any `attester` has revoked their attestation on the `module`. - -Returns an array of `uint256` of the timestamps at which the attestation by each queried attester was created. - -Note: The return values of `check` and `checkN` might change in the future, based on community feedback and further exploration of Registries and Adapters. - -### Adapter behavior - -A Smart Account MUST implement the following Adapter functionality either natively in the account or as a module. This Adapter functionality MUST ensure that: - -- The Registry is queried about module `A` at least once before or during the transaction in which `A` is called for the first time. -- The Registry reverting is treated as a security risk. - -Additionally, the Adapter SHOULD implement the following functionality: - -- Revert the transaction flow when the Registry reverts. -- Query the Registry about module `A` on installation of `A`. -- Query the Registry about module `A` on execution of `A`. - -Example: Adapter flow using `check` -![Adapter flow using check()](../assets/eip-7484/check-sequence.jpg) - -## Rationale - -### Attestations - -Attestations are onchain assertions made about a module. These assertions could pertain to the security of a module (similar to a regular smart contract audit), whether a module adheres to a certain standard or any other kinds of statements about these modules. While some of these assertions can feasibly be verified onchain, the majority of them cannot be. - -One example of this would be determining what storage slots a specific module can write to, which might be useful if a smart account uses DELEGATECALL to invoke the module. This assertion is practically infeasible to verify onchain, but can easily be verified off-chain. Thus, an attester could perform this check off-chain and publish an attestation onchain that attests to the fact that a given module can only write to its designated storage slots. - -While attestations are always certain kinds of assertions made about a module, this proposal purposefully allows the attestation data to be any kind of data or pointer to data. This ensures that any kind of data can be used as an assertion, from a simple boolean flag specifying that a module is secure to a complex proof of runtime module behaviour. - -### Singleton Registry - -In order for attestations to be queryable onchain, they need to be stored in some sort of list in a smart contract. This proposal includes the reference implementation of an ownerless Singleton Registry that functions as the source of truth for attestations. - -The reasons for proposing a Singleton Registry are the following: - -**Security**: A Singleton Registry creates greater security by focusing account integrations into a single source of truth where a maximum number of security entities are attesting. This has a number of benefits: a) it increases the maximum potential quantity and type of attestations per module and b) removes the need for accounts to verify the authenticity and security of different registries, focusing trust delegation to the onchain entities making attestations. The result is that accounts are able to query multiple attesters with lower gas overhead in order to increase security guarantees and there is no additional work required by accounts to verify the security of different registries. - -**Interoperability**: A Singleton Registry not only creates a greater level of “attestation liquidity”, but it also increases module liquidity and ensures a greater level of module interoperability. Developers need only deploy their module to one place to receive attestations and maximise module distribution to all integrated accounts. Attesters can also benefit from previous auditing work by chaining attestations and deriving ongoing security from these chains of dependencies. This allows for benefits such as traversing through the history of attestations or version control by the developer. - -However, there are obviously tradeoffs for using a singleton. A Singleton Registry creates a single point of failure that, if exploited, could lead to serious consequences for smart accounts. The most serious attack vector of these would be the ability for an attacker to attest to a malicious module on behalf of a trusted attester. One tradeoff here is that using multiple registries, changes in security attestations (for example a vulnerability is found and an attestation is revoked) are slower to propagate across the ecosystem, giving attackers an opportunity to exploit vulnerabilities for longer or even find and exploit them after seeing an issue pointed out in a specific Registry but not in others. - -Due to being a singleton, the Registry needs to be very flexible and thus likely less computationally efficient in comparison to a narrow, optimised Registry. This means that querying a Singleton Registry is likely to be more computationally (and by extension gas) intensive than querying a more narrow Registry. The tradeoff here is that a singleton makes it cheaper to query attestations from multiple parties simultaneously. So, depending on the Registry architectures, there is an amount of attestations to query (N) after which using a flexible singleton is actually computationally cheaper than querying N narrow registries. However, the reference implementation has also been designed with gas usage in mind and it is unlikely that specialised registries will be able to significantly decrease gas beyond the reference implementations benchmarks. - -### Related work - -The reference implementation of the Registry is heavily inspired by the Ethereum Attestation Service. The specific use-case of this proposal, however, required some custom modifications and additions to EAS, meaning that using the existing EAS contracts as the Module Registry was sub-optimal. However, it would be possible to use EAS as a Module Registry with some modifications. - -## Backwards Compatibility - -No backward compatibility issues found. - -## Reference Implementation - -### Adapter.sol - -```solidity -contract Adapter { - IRegistry registry; - - function checkModule(address module, address trustedAttester) internal { - // Check module attestation on Registry - registry.check(module, trustedAttester); - } - - function checkNModule(address module, address[] memory attesters, uint256 threshold) internal { - // Check list of module attestations on Registry - registry.checkN(module, attesters, threshold); - } -} -``` - -### Account.sol - -**Note**: This is a specific example that complies to the `Specification` above, but this implementation is not binding. - -```solidity -contract Account is Adapter { - ... - - // installs a module - function installModule(address module, address trustedAttester) public { - checkModule(module, trustedAttester); - ... - } - - // executes a module - function executeTransactionFromModule(address module, address[] memory attesters, uint256 threshold) public { - checkNModule(module, attesters, threshold); - ... - } - - ... -} -``` - -### Registry - -```solidity -contract Registry { - ... - - function check( - address module, - address attester - ) - public - view - returns (uint256) - { - AttestationRecord storage attestation = _getAttestation(module, attester); - - uint48 expirationTime = attestation.expirationTime; - uint48 attestedAt = - expirationTime != 0 && expirationTime < block.timestamp ? 0 : attestation.time; - if (attestedAt == 0) revert AttestationNotFound(); - - uint48 revokedAt = attestation.revocationTime; - if (revokedAt != 0) revert RevokedAttestation(attestation.attester); - - return uint256(attestedAt); - } - - function checkN( - address module, - address[] calldata attesters, - uint256 threshold - ) - external - view - returns (uint256[] memory) - { - uint256 attestersLength = attesters.length; - if (attestersLength < threshold || threshold == 0) { - threshold = attestersLength; - } - - uint256 timeNow = block.timestamp; - uint256[] memory attestedAtArray = new uint256[](attestersLength); - - for (uint256 i; i < attestersLength; i = uncheckedInc(i)) { - AttestationRecord storage attestation = _getAttestation(module, attesters[i]); - if (attestation.revocationTime != 0) { - revert RevokedAttestation(attestation.attester); - } - - uint48 expirationTime = attestation.expirationTime; - if (expirationTime != 0 && expirationTime < timeNow) { - revert AttestationNotFound(); - } - - attestedAtArray[i] = uint256(attestation.time); - - if (attestation.time == 0) continue; - - if (threshold != 0) --threshold; - } - if (threshold == 0) return attestedAtArray; - revert InsufficientAttestations(); - } - - function _getAttestation( - address module, - address attester - ) - internal - view - virtual - returns (AttestationRecord storage) - { - return _moduleToAttesterToAttestations[module][attester]; - } - - ... -} -``` - -## Security Considerations - -Needs discussion. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7484.md diff --git a/EIPS/eip-7507.md b/EIPS/eip-7507.md index 07b189c1098cbc..08c1ebc62e39cd 100644 --- a/EIPS/eip-7507.md +++ b/EIPS/eip-7507.md @@ -1,179 +1 @@ ---- -eip: 7507 -title: Multi-User NFT Extension -description: An extension of ERC-721 to allow multiple users to a token with restricted permissions. -author: Ming Jiang (@minkyn), Zheng Han (@hanbsd), Fan Yang (@fayang) -discussions-to: https://ethereum-magicians.org/t/eip-7507-multi-user-nft-extension/15660 -status: Draft -type: Standards Track -category: ERC -created: 2023-08-24 -requires: 721 ---- - -## Abstract - -This standard is an extension of [ERC-721](./eip-721.md). It proposes a new role `user` in addition to `owner` for a token. A token can have multiple users under separate expiration time. It allows the subscription model where an NFT can be subscribed non-exclusively by different users. - -## Motivation - -Some NFTs represent IP assets, and IP assets have the need to be licensed for access without transferring ownership. The subscription model is a very common practice for IP licensing where multiple users can subscribe to an NFT to obtain access. Each subscription is usually time limited and will thus be recorded with an expiration time. - -Existing [ERC-4907](./eip-4907.md) introduces a similar feature, but does not allow for more than one user. It is more suitable in the rental scenario where a user gains an exclusive right of use to an NFT before the next user. This rental model is common for NFTs representing physical assets like in games, but not very useful for shareable IP assets. - -## Specification - -Solidity interface available at [`IERC7507.sol`](../assets/eip-7507/contracts/IERC7507.sol): - -```solidity -interface IERC7507 { - - /// @notice Emitted when the expires of a user for an NFT is changed - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires); - - /// @notice Get the user expires of an NFT - /// @param tokenId The NFT to get the user expires for - /// @param user The user to get the expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId, address user) external view returns(uint256); - - /// @notice Set the user expires of an NFT - /// @param tokenId The NFT to set the user expires for - /// @param user The user to set the expires for - /// @param expires The user could use the NFT before expires in UNIX timestamp - function setUser(uint256 tokenId, address user, uint64 expires) external; - -} -``` - -## Rationale - -This standard complements [ERC-4907](./eip-4907.md) to support multi-user feature. Therefore the proposed interface tries to keep consistent using the same naming for functions and parameters. - -However, we didn't include the corresponding `usersOf(uint256 tokenId)` function as that would imply the implemention has to support enumerability over multiple users. It is not always necessary, for example, in the case of open subscription. So we decide not to add it to the interface and leave the choice up to the implementers. - -## Backwards Compatibility - -No backwards compatibility issues found. - -## Test Cases - -Test cases available available at: [`ERC7507.test.ts`](../assets/eip-7507/test/ERC7507.test.ts): - -```typescript -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -const NAME = "NAME"; -const SYMBOL = "SYMBOL"; -const TOKEN_ID = 1234; -const EXPIRATION = 2000000000; -const YEAR = 31536000; - -describe("ERC7507", function () { - - async function deployContractFixture() { - const [deployer, owner, user1, user2] = await ethers.getSigners(); - - const contract = await ethers.deployContract("ERC7507", [NAME, SYMBOL], deployer); - await contract.mint(owner, TOKEN_ID); - - return { contract, owner, user1, user2 }; - } - - describe("Functions", function () { - it("Should not set user if not owner or approved", async function () { - const { contract, user1 } = await loadFixture(deployContractFixture); - - await expect(contract.setUser(TOKEN_ID, user1, EXPIRATION)) - .to.be.revertedWith("ERC7507: caller is not owner or approved"); - }); - - it("Should return zero expiration for nonexistent user", async function () { - const { contract, user1 } = await loadFixture(deployContractFixture); - - expect(await contract.userExpires(TOKEN_ID, user1)).to.equal(0); - }); - - it("Should set users and then update", async function () { - const { contract, owner, user1, user2 } = await loadFixture(deployContractFixture); - - await contract.connect(owner).setUser(TOKEN_ID, user1, EXPIRATION); - await contract.connect(owner).setUser(TOKEN_ID, user2, EXPIRATION); - - expect(await contract.userExpires(TOKEN_ID, user1)).to.equal(EXPIRATION); - expect(await contract.userExpires(TOKEN_ID, user2)).to.equal(EXPIRATION); - - await contract.connect(owner).setUser(TOKEN_ID, user1, EXPIRATION + YEAR); - await contract.connect(owner).setUser(TOKEN_ID, user2, 0); - - expect(await contract.userExpires(TOKEN_ID, user1)).to.equal(EXPIRATION + YEAR); - expect(await contract.userExpires(TOKEN_ID, user2)).to.equal(0); - }); - }); - - describe("Events", function () { - it("Should emit event when set user", async function () { - const { contract, owner, user1 } = await loadFixture(deployContractFixture); - - await expect(contract.connect(owner).setUser(TOKEN_ID, user1, EXPIRATION)) - .to.emit(contract, "UpdateUser").withArgs(TOKEN_ID, user1.address, EXPIRATION); - }); - }); - -}); -``` - -## Reference Implementation - -Reference implementation available at: [`ERC7507.sol`](../assets/eip-7507/contracts/ERC7507.sol): - -```solidity -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -import "./IERC7507.sol"; - -contract ERC7507 is ERC721, IERC7507 { - - mapping(uint256 => mapping(address => uint64)) private _expires; - - constructor( - string memory name, string memory symbol - ) ERC721(name, symbol) {} - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override returns (bool) { - return interfaceId == type(IERC7507).interfaceId || super.supportsInterface(interfaceId); - } - - function userExpires( - uint256 tokenId, address user - ) public view virtual override returns(uint256) { - require(_exists(tokenId), "ERC7507: query for nonexistent token"); - return _expires[tokenId][user]; - } - - function setUser( - uint256 tokenId, address user, uint64 expires - ) public virtual override { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7507: caller is not owner or approved"); - _expires[tokenId][user] = expires; - emit UpdateUser(tokenId, user, expires); - } - -} -``` - -## Security Considerations - -No security considerations found. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7507.md diff --git a/EIPS/eip-7508.md b/EIPS/eip-7508.md index d1df447d846348..dbe33d2cc67937 100644 --- a/EIPS/eip-7508.md +++ b/EIPS/eip-7508.md @@ -1,1075 +1 @@ ---- -eip: 7508 -title: Dynamic On-Chain Token Attributes Repository -description: Dynamic on-chain storage of token attributes in a public-good repository. -author: Steven Pineda (@steven2308), Jan Turk (@ThunderDeliverer) -discussions-to: https://ethereum-magicians.org/t/dynamic-on-chain-token-attributes-repository/15667 -status: Draft -type: Standards Track -category: ERC -created: 2023-08-15 -requires: 165 ---- - -## Abstract - -The Public On-Chain Non-Fungible Token Attributes Repository standard provides the ability for [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) compatible tokens to store their attributes on-chain available to any external smart contract interacting with them. - -This proposal introduces the ability to assign attributes to NFTs in a public non-gated repository smart contract that is accessible at the same address in all of the networks. The repository smart contract is designed to be a common-good repository, meaning that it can be used by any ERC-721 or ERC-1155 compatible token. - -## Motivation - -With NFTs being a widespread form of tokens in the Ethereum ecosystem and being used for a variety of use cases, it is time to standardize additional utility for them. Having the ability to store token's attributes on chain allows for greater utility of tokens as it fosters cross-collection interactivity and provides perpetual store of attributes. - -This ERC introduces new utilities for [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) based tokens in the following areas: - -- [Cross-Collection interactivity](#cross-collection-interactivity) -- [Perpetual Store of Attributes](#perpetual-store-of-attributes) -- [Token Evolution](#token-evolution) -- [Dynamic State Tracking](#dynamic-state-tracking) - -### Cross-Collection Interactivity - -Storing attributes on-chain in a predictable format allows for cross-collection interactivity. This means that the attributes of a token can be used by any external smart contract without the need for the token to be aware of the external smart contract. - -For example, a token can represent a game character with its set of attributes and can be used in an unrelated game with the same stats without the need for retrieving these attributes from an off-chain source. This ensures that the data the game is using is legitimate and not tampered with in order to gain an advantage. - -### Perpetual Store of Attributes - -Standardized on-chain token attributes allow for their perpetual storage. - -With off-chain attributes storage, the attributes are only available as long as the off-chain storage is available. If the storage is taken down, the attributes are lost. With on-chain attributes storage, the attributes are available as long as the blockchain is available. This increases the value of the token as it ensures that the attributes are available for as long as the token exists. - -### Token Evolution - -On-Chain storage of token attributes allows for the token to evolve over time. Owner's actions can impact the attributes of the token. Since the attributes are stored on chain, the smart contract has the ability to modify the attribute once certain thresholds are met. This allows for token to become more interactive and reflect owner's dedication and effort. - -### Dynamic State Tracking - -On-Chain storage of token attributes allows for dynamic state tracking. The attributes can be used to track the state of the token and its owner. This allows for the token to be used in a variety of use cases. One such use case is supply chains; the token can represent a product and its attributes can be used to track the state of the product as it transitions from pending, shipped, delivered, etc. - -## Specification - -The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -```solidity -/// @title ERC-7508 Public On-Chain NFT Attributes Repository -/// @dev See https://eips.ethereum.org/EIPS/eip-7508 -/// @dev Note: the ERC-165 identifier for this interface is 0x07cd44c7. - -pragma solidity ^0.8.21; - -interface IRMRKTokenAttributesRepository /*is IERC165*/ { - /** - * @notice A list of supported access types. - * @return The `Issuer` type, where only the issuer can manage the parameter. - * @return The `Collaborator` type, where only the collaborators can manage the parameter. - * @return The `IssuerOrCollaborator` type, where only the issuer or collaborators can manage the parameter. - * @return The `TokenOwner` type, where only the token owner can manage the parameters of their tokens. - * @return The `SpecificAddress` type, where only specific addresses can manage the parameter. - */ - enum AccessType { - Issuer, - Collaborator, - IssuerOrCollaborator, - TokenOwner, - SpecificAddress - } - - /** - * @notice Structure used to represent a string attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct StringAttribute { - string key; - string value; - } - - /** - * @notice Structure used to represent an uint attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct UintAttribute { - string key; - uint256 value; - } - - /** - * @notice Structure used to represent a boolean attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct BoolAttribute { - string key; - bool value; - } - - /** - * @notice Structure used to represent an address attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct AddressAttribute { - string key; - address value; - } - - /** - * @notice Structure used to represent a bytes attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct BytesAttribute { - string key; - bytes value; - } - - /** - * @notice Used to notify listeners that a new collection has been registered to use the repository. - * @param collection Address of the collection - * @param issuer Address of the issuer of the collection; the addess authorized to manage the access control - * @param registeringAddress Address that registered the collection - * @param useOwnable A boolean value indicating whether the collection uses the Ownable extension to verify the - * issuer (`true`) or not (`false`) - */ - event AccessControlRegistration( - address indexed collection, - address indexed issuer, - address indexed registeringAddress, - bool useOwnable - ); - - /** - * @notice Used to notify listeners that the access control settings for a specific parameter have been updated. - * @param collection Address of the collection - * @param key The name of the parameter for which the access control settings have been updated - * @param accessType The AccessType of the parameter for which the access control settings have been updated - * @param specificAddress The specific addresses that has been updated - */ - event AccessControlUpdate( - address indexed collection, - string key, - AccessType accessType, - address specificAddress - ); - - /** - * @notice Used to notify listeners that a new collaborator has been added or removed. - * @param collection Address of the collection - * @param collaborator Address of the collaborator - * @param isCollaborator A boolean value indicating whether the collaborator has been added (`true`) or removed - * (`false`) - */ - event CollaboratorUpdate( - address indexed collection, - address indexed collaborator, - bool isCollaborator - ); - - /** - * @notice Used to notify listeners that a string attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event StringAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - string value - ); - - /** - * @notice Used to notify listeners that an uint attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event UintAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - uint256 value - ); - - /** - * @notice Used to notify listeners that a boolean attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event BoolAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - bool value - ); - - /** - * @notice Used to notify listeners that an address attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event AddressAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - address value - ); - - /** - * @notice Used to notify listeners that a bytes attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event BytesAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - bytes value - ); - - /** - * @notice Used to register a collection to use the RMRK token attributes repository. - * @dev If the collection does not implement the Ownable interface, the `useOwnable` value must be set to `false`. - * @dev Emits an {AccessControlRegistration} event. - * @param collection The address of the collection that will use the RMRK token attributes repository. - * @param issuer The address of the issuer of the collection. - * @param useOwnable The boolean value to indicate if the collection implements the Ownable interface and whether it - * should be used to validate that the caller is the issuer (`true`) or to use the manually set issuer address - * (`false`). - */ - function registerAccessControl( - address collection, - address issuer, - bool useOwnable - ) external; - - /** - * @notice Used to manage the access control settings for a specific parameter. - * @dev Only the `issuer` of the collection can call this function. - * @dev The possible `accessType` values are: - * [ - * Issuer, - * Collaborator, - * IssuerOrCollaborator, - * TokenOwner, - * SpecificAddress, - * ] - * @dev Emits an {AccessControlUpdated} event. - * @param collection The address of the collection being managed. - * @param key The key of the attribute - * @param accessType The type of access control to be applied to the parameter. - * @param specificAddress The address to be added as a specific addresses allowed to manage the given - * parameter. - */ - function manageAccessControl( - address collection, - string memory key, - AccessType accessType, - address specificAddress - ) external; - - /** - * @notice Used to manage the collaborators of a collection. - * @dev The `collaboratorAddresses` and `collaboratorAddressAccess` arrays must be of the same length. - * @dev Emits a {CollaboratorUpdate} event. - * @param collection The address of the collection - * @param collaboratorAddresses The array of collaborator addresses being managed - * @param collaboratorAddressAccess The array of boolean values indicating if the collaborator address should - * receive the permission (`true`) or not (`false`). - */ - function manageCollaborators( - address collection, - address[] memory collaboratorAddresses, - bool[] memory collaboratorAddressAccess - ) external; - - /** - * @notice Used to set a number attribute. - * @dev Emits a {UintAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setUintAttribute( - address collection, - uint256 tokenId, - string memory key, - uint256 value - ) external; - - /** - * @notice Used to set a string attribute. - * @dev Emits a {StringAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setStringAttribute( - address collection, - uint256 tokenId, - string memory key, - string memory value - ) external; - - /** - * @notice Used to set a boolean attribute. - * @dev Emits a {BoolAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setBoolAttribute( - address collection, - uint256 tokenId, - string memory key, - bool value - ) external; - - /** - * @notice Used to set an bytes attribute. - * @dev Emits a {BytesAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setBytesAttribute( - address collection, - uint256 tokenId, - string memory key, - bytes memory value - ) external; - - /** - * @notice Used to set an address attribute. - * @dev Emits a {AddressAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setAddressAttribute( - address collection, - uint256 tokenId, - string memory key, - address value - ) external; - - /** - * @notice Sets multiple string attributes for a token at once. - * @dev The `StringAttribute` struct contains the following fields: - * [ - * string key, - * string value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `StringAttribute` structs to be assigned to the given token - */ - function setStringAttributes( - address collection, - uint256 tokenId, - StringAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple uint attributes for a token at once. - * @dev The `UintAttribute` struct contains the following fields: - * [ - * string key, - * uint value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `UintAttribute` structs to be assigned to the given token - */ - function setUintAttributes( - address collection, - uint256 tokenId, - UintAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple bool attributes for a token at once. - * @dev The `BoolAttribute` struct contains the following fields: - * [ - * string key, - * bool value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `BoolAttribute` structs to be assigned to the given token - */ - function setBoolAttributes( - address collection, - uint256 tokenId, - BoolAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple address attributes for a token at once. - * @dev The `AddressAttribute` struct contains the following fields: - * [ - * string key, - * address value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `AddressAttribute` structs to be assigned to the given token - */ - function setAddressAttributes( - address collection, - uint256 tokenId, - AddressAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple bytes attributes for a token at once. - * @dev The `BytesAttribute` struct contains the following fields: - * [ - * string key, - * bytes value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `BytesAttribute` structs to be assigned to the given token - */ - function setBytesAttributes( - address collection, - uint256 tokenId, - BytesAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple attributes of multiple types for a token at the same time. - * @dev Emits a separate event for each attribute set. - * @dev The `StringAttribute`, `UintAttribute`, `BoolAttribute`, `AddressAttribute` and `BytesAttribute` structs consists - * to the following fields (where `value` is of the appropriate type): - * [ - * key, - * value, - * ] - * @param collection The address of the collection - * @param tokenId The token ID - * @param stringAttributes An array of `StringAttribute` structs containing string attributes to set - * @param uintAttributes An array of `UintAttribute` structs containing uint attributes to set - * @param boolAttributes An array of `BoolAttribute` structs containing bool attributes to set - * @param addressAttributes An array of `AddressAttribute` structs containing address attributes to set - * @param bytesAttributes An array of `BytesAttribute` structs containing bytes attributes to set - */ - function setTokenAttributes( - address collection, - uint256 tokenId, - StringAttribute[] memory stringAttributes, - UintAttribute[] memory uintAttributes, - BoolAttribute[] memory boolAttributes, - AddressAttribute[] memory addressAttributes, - BytesAttribute[] memory bytesAttributes - ) external; - - /** - * @notice Used to set the uint attribute on behalf of an authorized account. - * @dev Emits a {UintAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetUintAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the string attribute on behalf of an authorized account. - * @dev Emits a {StringAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetStringAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - string memory value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the bool attribute on behalf of an authorized account. - * @dev Emits a {BoolAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetBoolAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - bool value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the bytes attribute on behalf of an authorized account. - * @dev Emits a {BytesAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetBytesAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - bytes memory value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the address attribute on behalf of an authorized account. - * @dev Emits a {AddressAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetAddressAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - address value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to check if the specified address is listed as a collaborator of the given collection's parameter. - * @param collaborator Address to be checked. - * @param collection Address of the collection. - * @return Boolean value indicating if the address is a collaborator of the given collection's (`true`) or not - * (`false`). - */ - function isCollaborator( - address collaborator, - address collection - ) external view returns (bool); - - /** - * @notice Used to check if the specified address is listed as a specific address of the given collection's - * parameter. - * @param specificAddress Address to be checked. - * @param collection Address of the collection. - * @param key The key of the attribute - * @return Boolean value indicating if the address is a specific address of the given collection's parameter - * (`true`) or not (`false`). - */ - function isSpecificAddress( - address specificAddress, - address collection, - string memory key - ) external view returns (bool); - - /** - * @notice Used to retrieve the string type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the string attribute - */ - function getStringTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (string memory); - - /** - * @notice Used to retrieve the uint type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the uint attribute - */ - function getUintTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (uint256); - - /** - * @notice Used to retrieve the bool type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the bool attribute - */ - function getBoolTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (bool); - - /** - * @notice Used to retrieve the address type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the address attribute - */ - function getAddressTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (address); - - /** - * @notice Used to retrieve the bytes type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the bytes attribute - */ - function getBytesTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (bytes memory); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned uint attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignUintAttribute( - address collection, - uint256 tokenId, - string memory key, - uint256 value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned string attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignStringAttribute( - address collection, - uint256 tokenId, - string memory key, - string memory value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned bool attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignBoolAttribute( - address collection, - uint256 tokenId, - string memory key, - bool value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned bytes attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignBytesAttribute( - address collection, - uint256 tokenId, - string memory key, - bytes memory value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned address attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignAddressAttribute( - address collection, - uint256 tokenId, - string memory key, - address value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve multiple token attributes of any type at once. - * @dev The `StringAttribute`, `UintAttribute`, `BoolAttribute`, `AddressAttribute` and `BytesAttribute` structs consists - * to the following fields (where `value` is of the appropriate type): - * [ - * key, - * value, - * ] - * @param collection The collection address - * @param tokenId The token ID - * @param stringKeys An array of string type attribute keys to retrieve - * @param uintKeys An array of uint type attribute keys to retrieve - * @param boolKeys An array of bool type attribute keys to retrieve - * @param addressKeys An array of address type attribute keys to retrieve - * @param bytesKeys An array of bytes type attribute keys to retrieve - * @return stringAttributes An array of `StringAttribute` structs containing the string type attributes - * @return uintAttributes An array of `UintAttribute` structs containing the uint type attributes - * @return boolAttributes An array of `BoolAttribute` structs containing the bool type attributes - * @return addressAttributes An array of `AddressAttribute` structs containing the address type attributes - * @return bytesAttributes An array of `BytesAttribute` structs containing the bytes type attributes - */ - function getTokenAttributes( - address collection, - uint256 tokenId, - string[] memory stringKeys, - string[] memory uintKeys, - string[] memory boolKeys, - string[] memory addressKeys, - string[] memory bytesKeys - ) - external - view - returns ( - StringAttribute[] memory stringAttributes, - UintAttribute[] memory uintAttributes, - BoolAttribute[] memory boolAttributes, - AddressAttribute[] memory addressAttributes, - BytesAttribute[] memory bytesAttributes - ); - - /** - * @notice Used to get multiple sting parameter values for a token. - * @dev The `StringAttribute` struct contains the following fields: - * [ - * string key, - * string value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param stringKeys An array of string keys to retrieve - * @return An array of `StringAttribute` structs - */ - function getStringTokenAttributes( - address collection, - uint256 tokenId, - string[] memory stringKeys - ) external view returns (StringAttribute[] memory); - - /** - * @notice Used to get multiple uint parameter values for a token. - * @dev The `UintAttribute` struct contains the following fields: - * [ - * string key, - * uint value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param uintKeys An array of uint keys to retrieve - * @return An array of `UintAttribute` structs - */ - function getUintTokenAttributes( - address collection, - uint256 tokenId, - string[] memory uintKeys - ) external view returns (UintAttribute[] memory); - - /** - * @notice Used to get multiple bool parameter values for a token. - * @dev The `BoolAttribute` struct contains the following fields: - * [ - * string key, - * bool value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param boolKeys An array of bool keys to retrieve - * @return An array of `BoolAttribute` structs - */ - function getBoolTokenAttributes( - address collection, - uint256 tokenId, - string[] memory boolKeys - ) external view returns (BoolAttribute[] memory); - - /** - * @notice Used to get multiple address parameter values for a token. - * @dev The `AddressAttribute` struct contains the following fields: - * [ - * string key, - * address value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param addressKeys An array of address keys to retrieve - * @return An array of `AddressAttribute` structs - */ - function getAddressTokenAttributes( - address collection, - uint256 tokenId, - string[] memory addressKeys - ) external view returns (AddressAttribute[] memory); - - /** - * @notice Used to get multiple bytes parameter values for a token. - * @dev The `BytesAttribute` struct contains the following fields: - * [ - * string key, - * bytes value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param bytesKeys An array of bytes keys to retrieve - * @return An array of `BytesAttribute` structs - */ - function getBytesTokenAttributes( - address collection, - uint256 tokenId, - string[] memory bytesKeys - ) external view returns (BytesAttribute[] memory); -} -``` - -### Message format for presigned attribute - -The message to be signed by the `setter` in order for the attribute setting to be submitted by someone else is formatted as follows: - -```solidity -keccak256( - abi.encode( - DOMAIN_SEPARATOR, - METHOD_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ); -``` - -The values passed when generating the message to be signed are: - -- `DOMAIN_SEPARATOR` - The domain separator of the Attribute repository smart contract -- `METHOD_TYPEHASH` - The typehash of the method being called. The supported values, depending on the method are: - - `SET_UINT_ATTRIBUTE_TYPEHASH` - Used for setting uint attributes - - `SET_STRING_ATTRIBUTE_TYPEHASH` - Used for setting string attributes - - `SET_BOOL_ATTRIBUTE_TYPEHASH` - Used for setting bool attributes - - `SET_BYTES_ATTRIBUTE_TYPEHASH` - Used for setting bytes attributes - - `SET_ADDRESS_ATTRIBUTE_TYPEHASH` - Used for setting address attributes -- `collection` - Address of the collection containing the token receiving the attribute -- `tokenId` - ID of the token receiving the attribute -- `key` - The attribute key -- `value` - The attribute value of the appropriate type -- `deadline` - UNIX timestamp of the deadline for the signature to be submitted. The signed message submitted after the deadline MUST be rejected - -The `DOMAIN_SEPARATOR` is generated as follows: - -```solidity -keccak256( - abi.encode( - "ERC-7508: Public Non-Fungible Token Attributes Repository", - "1", - block.chainid, - address(this) - ) -); -``` - -The `SET_UINT_ATTRIBUTE_TYPEHASH` is generated as follows: - -```solidity -keccak256( - "setUintAttribute(address collection,uint256 tokenId,string memory key,uint256 value)" -); -``` - -The `SET_STRING_ATTRIBUTE_TYPEHASH` is generated as follows: - -```solidity -keccak256( - "setStringAttribute(address collection,uint256 tokenId,string memory key,string memory value)" -); -``` - -The `SET_BOOL_ATTRIBUTE_TYPEHASH` is generated as follows: - -```solidity -keccak256( - "setBoolAttribute(address collection,uint256 tokenId,string memory key,bool value)" -); -``` - -The `SET_BYTES_ATTRIBUTE_TYPEHASH` is generated as follows: - -```solidity -keccak256( - "setBytesAttribute(address collection,uint256 tokenId,string memory key,bytes memory value)" -); -``` - -The `SET_ADDRESS_ATTRIBUTE_TYPEHASH` is generated as follows: - -```solidity -keccak256( - "setAddressAttribute(address collection,uint256 tokenId,string memory key,address value)" -); -``` - -Each chain, that the Attributes repository smart contract is deployed in, will have a different `DOMAIN_SEPARATOR` value due to chain IDs being different. - -### Pre-determined address of the Attributes repository - -The address of the Emotable repository smart contract is designed to resemble the function it serves. It starts with `0xA77B75` which is the abstract representation of `ATTBTS`. The address is: - -``` -0xA77b75D5fDEC6E6e8E00e05c707a7CA81a3F9f4a -``` - -## Rationale - -Designing the proposal, we considered the following questions: - -1. **Should we refer to the values stored by the repository as propertiers or attributes?**\ -Historically values defining characteristics of tokens have been called properties, but have evolved in to being called attributes. Referring to the dictionary, the property is defined as a quality or characteristic that something has, and the attribute is defined as a quality or feature of somebody/something. We felt that using the term attribute fits better and decided to use it. -2. **Should the proposal specify access control?**\ -Designing the proposal, we had two options: either to include the access control within the specification of the proposal or to leave the access control up to the implementers that desire to use the attributes repository. While considering this we also had to consider the usability and compatibility aspects of the repository.\ -On one hand, including access control narrows down the freedom of implementation and requires the implementers to configure it before being able to use the repository. On the other hand, leaving access control up to implementers requires dedicated design of attributes access control within their smart contracts, increasing their size, complexity and deployment costs.\ -Another important thing to note is that including access control in the proposal makes it compatible with collections existing prior to the deployment of the repository and thus powers backwards-compatibility. -3. **Should the proposal establish an attributes extension or a common-good repository?**\ -Initially we set out to create an attributes extension to be used with any ERC-721 compliant tokens. However, we realized that the proposal would be more useful if it was a common-good repository of token attributes. This way, the tokens that can utilize it are not only the new ones but also the old ones that have been around since before the proposal.\ -An additional benefit of this course-correction is the compatibility with ERC-1155 tokens. -4. **Should we include only single-action operations, only multi-action operations, or both?**\ -We've considered including only single-action operations, where the user is only able to assign a single attribute to a single token, but we decided to include both single-action and multi-action operations. This way, the users can choose whether they want to assign an attribute to a single token or on multiple tokens at once.\ -This decision was made for the long-term viability of the proposal. Based on the gas cost of the network and the number of tokens in the collection, the user can choose the most cost-effective way of attribute assigning. -5. **Should we add the ability to assign attributes on someone else's behalf?**\ -While we did not intend to add this as part of the proposal when drafting it, we realized that it would be a useful feature for it. This way, the users can assign attributes on behalf of someone else, for example, if they are not able to do it themselves or if the attribute is earned through an off-chain activity. -6. **How do we ensure that attribute assignment on someone else's behalf is legitimate?**\ -We could add delegates to the proposal; when a user delegates their right to assign attributes to someone else, but having the ability to do so opens up the possibility for abuse and improper setting of attributes.\ -Using ECDSA signatures, we can ensure that the user has given their consent to assign attribute on their behalf. This way, the user can sign a message with the parameters of the attribute and the signature can be submitted by someone else. -7. **Should we add chain ID as a parameter when assigning attribute to a token?**\ -We decided against this as we feel that additional parameter would rarely be used and would add additional cost to the attribute assignment transactions. If the collection smart contract wants to utilize on-chain token attributes, it requires the reactions to be recorded on the same chain. Marketplaces and wallets integrating this proposal will rely on attributes to reside in the same chain as well, because if chain ID parameter was supported this would mean that they would need to query the repository smart contract on all of the chains the repository is deployed in order to get the attributes of a given token.\ -Additionally, if the collection creator wants users to record their reactions on a different chain, they can still direct the users to do just that. The repository does not validate the existence of the token being reacted to (except in an instace where the attribute can be modified by the token's owner), which in theory means that you can assign an attribute to non-existent token or to a token that does not exist yet. -8. **How should we reduce the cost of string usage in the repository?** -One fo the main issues we were dealing with while designing the proposal is the cost of string usage. We considered using bytes instead of strings, but decided against it as it would require the users to encode and decode the strings themselves.\ -The solution for reducing the cost was to use a string indices. This means that the cost of setting a new string attribute or key will only be paid by the first user to do so. The subsequent users will only pay the cost of setting the index of the string attribute or key.\ -We also extended this gas-saving approach to be applicable across the entire repository. This means that if the string was already set by one collection, any other collection using the same string will not have to pay the cost of setting the string again. - -## Backwards Compatibility - -The Attributes repository standard is fully compatible with [ERC-721](./eip-721.md) and [ERC-1155](./eip-1155.md) and with the robust tooling available for implementations of ERC-721 as well as with the existing ERC-721 infrastructure. - -## Test Cases - -Tests are included in [`attributesRepository.ts`](../assets/eip-7508/test/attributesRepository.ts). - -To run them in terminal, you can use the following commands: - -``` -cd ../assets/eip-7508 -npm install -npx hardhat test -``` - -## Reference Implementation - -See [`AttributesRepository.sol`](../assets/eip-7508/contracts/AttributesRepository.sol). - -## Security Considerations - -The proposal does not envision handling any form of assets from the user, so the assets should not be at risk when interacting with an Attributes repository. - -The ability to use ECDSA signatures to set attributes on someone else's behalf introduces the risk of a replay attack, which the format of the message to be signed guards against. The `DOMAIN_SEPARATOR` used in the message to be signed is unique to the repository smart contract of the chain it is deployed on. This means that the signature is invalid on any other chain and the attributes repositories deployed on them should revert the operation if a replay attack is attempted. - -Another thing to consider is the ability of presigned message reuse. Since the message includes the signature validity deadline, the message can be reused any number of times before the deadline is reached. The proposal only allows for a single value for a given key to be set, so the presigned message can not be abused to further modify the attribute value. However, if the service using the repository relies on the ability to revert or modify the attribute after certain actions, a valid presigned message can be used to re-assign the attribute of the token. We suggest that the services using the repository in cnjunction with presigned messages use deadlines that invalidate presigned messages after a reasonalby short period of time. - -Caution is advised when dealing with non-audited contracts. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7508.md diff --git a/EIPS/eip-7511.md b/EIPS/eip-7511.md index 674061ec77f505..84a057a044969d 100644 --- a/EIPS/eip-7511.md +++ b/EIPS/eip-7511.md @@ -1,209 +1 @@ ---- -eip: 7511 -title: Minimal Proxy Contract with PUSH0 -description: Optimizes the previous Minimal Proxy Contract with the PUSH0 opcode -author: 0xAA (@AmazingAng), vectorized (@Vectorized), 0age (@0age) -discussions-to: https://ethereum-magicians.org/t/erc-7511-minimal-proxy-contract-with-push0/15662 -status: Draft -type: Standards Track -category: ERC -created: 2023-09-04 -requires: 7, 211, 1167, 3855 ---- - -## Abstract - -With the `PUSH0` opcode ([EIP-3855](./eip-3855.md)), introduced with the Shanghai upgrade, we optimized the previous Minimal Proxy Contract ([ERC-1167](./eip-1167.md)) by 200 gas at deployment and 5 gas at runtime, while retaining the same functionality. - -## Motivation - - -1. Reduce the contract bytecode size by `1` byte by removing a redundant `SWAP` opcode. -2. Reduce the runtime gas by replacing two `DUP` (cost `3` gas each) with two `PUSH0` (cost `2` gas each). -3. Increase the readability of the proxy contract by redesigning it from first principles with `PUSH0`. - -## Specification - -### Standard Proxy Contract - -The exact runtime code for the minimal proxy contract with `PUSH0` is: - -``` -365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3 -``` - -where the bytes at indices 9 - 28 (inclusive) are replaced with the 20-byte address of the master implementation contract. The length of the runtime code is `44` bytes. - -The disassembly of the new minimal proxy contract code is: - -| pc | op | opcode | stack | -|------|--------|----------------|--------------------| -| [00] | 36 | CALLDATASIZE | cds | -| [01] | 5f | PUSH0 | 0 cds | -| [02] | 5f | PUSH0 | 0 0 cds | -| [03] | 37 | CALLDATACOPY | | -| [04] | 5f | PUSH0 | 0 | -| [05] | 5f | PUSH0 | 0 0 | -| [06] | 36 | CALLDATASIZE | cds 0 0 | -| [07] | 5f | PUSH0 | 0 cds 0 0 | -| [08] | 73bebe.| PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 | -| [1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0| -| [1e] | f4 | DELEGATECALL | suc | -| [1f] | 3d | RETURNDATASIZE | rds suc | -| [20] | 5f | PUSH0 | 0 rds suc | -| [21] | 5f | PUSH0 | 0 0 rds suc | -| [22] | 3e | RETURNDATACOPY | suc | -| [23] | 5f | PUSH0 | 0 suc | -| [24] | 3d | RETURNDATASIZE | rds 0 suc | -| [25] | 91 | SWAP2 | suc 0 rds | -| [26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds | -| [27] | 57 | JUMPI | 0 rds | -| [29] | fd | REVERT | | -| [2a] | 5b | JUMPDEST | 0 rds | -| [2b] | f3 | RETURN | | - -### Minimal Creation Code - -The minimal creation code of the minimal proxy contract is: - -``` -602c8060095f395ff3365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3 -``` - -where the first 9 bytes are the initcode: - -``` -602c8060095f395ff3 -``` - -And the rest are runtime/contract code of the proxy. The length of the creation code is `53` bytes. - -### Deploy with Solidity - -The minimal proxy contract can be deployed with Solidity using the following contract: - -```solidity -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.20; - -// Note: this contract requires `PUSH0`, which is available in solidity > 0.8.20 and EVM version > Shanghai -contract Clone0Factory { - error FailedCreateClone(); - - receive() external payable {} - - /** - * @dev Deploys and returns the address of a clone0 (Minimal Proxy Contract with `PUSH0`) that mimics the behaviour of `implementation`. - * - * This function uses the create opcode, which should never revert. - */ - function clone0(address impl) public payable returns (address addr) { - // first 18 bytes of the creation code - bytes memory data1 = hex"602c8060095f395ff3365f5f375f5f365f73"; - // last 15 bytes of the creation code - bytes memory data2 = hex"5af43d5f5f3e5f3d91602a57fd5bf3"; - // complete the creation code of Clone0 - bytes memory _code = abi.encodePacked(data1, impl, data2); - - // deploy with create op - assembly { - // create(v, p, n) - addr := create(callvalue(), add(_code, 0x20), mload(_code)) - } - - if (addr == address(0)) { - revert FailedCreateClone(); - } - } -} -``` - -## Rationale - -The optimized contract is constructed with essential components of the proxy contract and incorporates the recently added `PUSH0` opcode. The core elements of the minimal proxy include: - -1. Copy the calldata with `CALLDATACOPY`. -2. Forward the calldata to the implementation contract using `DELEGATECALL`. -3. Copy the returned data from the `DELEGATECALL`. -4. Return the results or revert the transaction based on whether the `DELEGATECALL` is successful. - -### Step 1: Copy the Calldata - -To copy the calldata, we need to provide the arguments for the `CALLDATACOPY` opcodes, which are `[0, 0, cds]`, where `cds` represents calldata size. - -| pc | op | opcode | stack | -|------|--------|----------------|--------------------| -| [00] | 36 | CALLDATASIZE | cds | -| [01] | 5f | PUSH0 | 0 cds | -| [02] | 5f | PUSH0 | 0 0 cds | -| [03] | 37 | CALLDATACOPY | | - -### Step 2: Delegatecall - -To forward the calldata to the delegate call, we need to prepare arguments for the `DELEGATECALL` opcodes, which are `[gas 0xbebe. 0 cds 0 0]`, where `gas` represents the remaining gas, `0xbebe.` represents the address of the implementation contract, and `suc` represents whether the delegatecall is successful. - -| pc | op | opcode | stack | -|------|--------|----------------|--------------------| -| [04] | 5f | PUSH0 | 0 | -| [05] | 5f | PUSH0 | 0 0 | -| [06] | 36 | CALLDATASIZE | cds 0 0 | -| [07] | 5f | PUSH0 | 0 cds 0 0 | -| [08] | 73bebe.| PUSH20 0xbebe. | 0xbebe. 0 cds 0 0 | -| [1d] | 5a | GAS | gas 0xbebe. 0 cds 0 0| -| [1e] | f4 | DELEGATECALL | suc | - -### Step 3: Copy the Returned Data from the `DELEGATECALL` - -To copy the returndata, we need to provide the arguments for the `RETURNDATACOPY` opcodes, which are `[0, 0, red]`, where `rds` represents size of returndata from the `DELEGATECALL`. - -| pc | op | opcode | stack | -|------|--------|----------------|--------------------| -| [1f] | 3d | RETURNDATASIZE | rds suc | -| [20] | 5f | PUSH0 | 0 rds suc | -| [21] | 5f | PUSH0 | 0 0 rds suc | -| [22] | 3e | RETURNDATACOPY | suc | - -### Step 4: Return or Revert - -Lastly, we need to return the data or revert the transaction based on whether the `DELEGATECALL` is successful. There is no `if/else` in opcodes, so we need to use `JUMPI` and `JUMPDEST` instead. The arguments for `JUMPI` is `[0x2a, suc]`, where `0x2a` is the destination of the conditional jump. - - We also need to prepare the argument `[0, rds]` for `REVERT` and `RETURN` opcodes before the `JUMPI`, otherwise we have to prepare them twice. We cannot avoid the `SWAP` operation, because we can only get `rds` after the `DELEGATECALL`. - -| pc | op | opcode | stack | -|------|--------|----------------|--------------------| -| [23] | 5f | PUSH0 | 0 suc | -| [24] | 3d | RETURNDATASIZE | rds 0 suc | -| [25] | 91 | SWAP2 | suc 0 rds | -| [26] | 602a | PUSH1 0x2a | 0x2a suc 0 rds | -| [27] | 57 | JUMPI | 0 rds | -| [29] | fd | REVERT | | -| [2a] | 5b | JUMPDEST | 0 rds | -| [2b] | f3 | RETURN | | - -In the end, we arrived at the runtime code for Minimal Proxy Contract with `PUSH0`: - -``` -365f5f375f5f365f73bebebebebebebebebebebebebebebebebebebebe5af43d5f5f3e5f3d91602a57fd5bf3 -``` - -The length of the runtime code is `44` bytes, which reduced `1` byte from the previous Minimal Proxy Contract. Moreover, it replaced the `RETURNDATASIZE` and `DUP` operations with `PUSH0`, which saves gas and increases the readability of the code. In summary, the new Minimal Proxy Contract reduces `200` gas at deployment and `5` gas at runtime, while remaining the same functionalities as the old one. - -## Backwards Compatibility - -Because the new minimal proxy contract uses `PUSH0` opcode, it can only be deployed after the Shanghai Upgrade. It behaves the same as the previous Minimal Proxy Contract. - -## Security Considerations - -The new proxy contract standard is identical to the previous one (ERC-1167). Here are the security considerations when using minimal proxy contracts: - -1. **Non-Upgradability**: Minimal Proxy Contracts delegate their logic to another contract (often termed the "implementation" or "logic" contract). This delegation is fixed upon deployment, meaning you can't change which implementation contract the proxy delegates to after its creation. - -2. **Initialization Concerns**: Proxy contracts lack constructors, so you need to use an initialization function after deployment. Skipping this step could leave the contract unsafe. - -3. **Safety of Logic Contract**: Vulnerabilities in the logic contract affect all associated proxy contracts. - -4. **Transparency Issues**: Because of its complexity, users might see the proxy as an empty contract, making it challenging to trace back to the actual logic contract. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7511.md diff --git a/EIPS/eip-7512.md b/EIPS/eip-7512.md index 87f2978baa31bd..ad5b29800d8e60 100644 --- a/EIPS/eip-7512.md +++ b/EIPS/eip-7512.md @@ -1,198 +1 @@ ---- -eip: 7512 -title: Onchain Representation for Audits -description: Proposal to define a contract parseable representation of Audit reports. -author: Richard Meissner - Safe (@rmeissner), Robert Chen - OtterSec (@chen-robert), Matthias Egli - ChainSecurity (@MatthiasEgli), Jan Kalivoda - Ackee Blockchain (@jaczkal), Michael Lewellen - OpenZeppelin (@cylon56), Shay Zluf - Hats Finance (@shayzluf), Alex Papageorgiou - Omniscia (@alex-ppg) -discussions-to: https://ethereum-magicians.org/t/erc-7512-onchain-audit-representation/15683 -status: Draft -type: Standards Track -category: ERC -created: 2023-09-05 -requires: 712 ---- - - -## Abstract - -The proposal aims to create a standard for an onchain representation of audit reports that can be parsed by contracts to extract relevant information about the audits, such as who performed the audits and what standards have been verified. - -## Motivation - -Audits are an integral part of the smart contract security framework. They are commonly used to increase the security of smart contracts and ensure that they follow best practices as well as correctly implement standards such [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), and similar ERCs. Many essential parts of the blockchain ecosystem are facilitated by the usage of smart contracts. Some examples of this are: - -- Bridges: Most bridges consist of a bridgehead or a lockbox that secures the tokens that should be bridged. If any of these contracts are faulty it might be possible to bring the operation of the bridge to a halt or, in extreme circumstances, cause uncollateralized assets to be minted on satellite chains. -- Token Contracts: Every token in the Ethereum ecosystem is a smart contract. Apps that interact with these tokens rely on them adhering to known token standards, most commonly [ERC-20](./eip-20.md) and [ERC-721](./eip-721.md). Tokens that behave differently can cause unexpected behavior and might even lead to loss of funds. -- Smart Contract Accounts (SCAs): With [ERC-4337](./eip-4337.md) more visibility has been created for smart-contract-based accounts. They provide extreme flexibility and can cater to many different use cases whilst retaining a greater degree of control and security over each account. A concept that has been experimented with is the idea of modules that allow the extension of a smart contract account's functionality. [ERC-6900](./eip-6900.md)) is a recent standard that defines how to register and design plugins that can be registered on an account. -- Interoperability (Hooks & Callbacks): With more protocols supporting external-facing functions to interact with them and different token standards triggering callbacks on a transfer (i.e. [ERC-1155](./eip-1155.md)), it is important to make sure that these interactions are well vetted to minimize the security risks they are associated with as much as possible. - -The usage and impact smart contracts will have on the day-to-day operations of decentralized applications will steadily increase. To provide tangible guarantees about security and allow better composability it is imperative that an onchain verification method exists to validate that a contract has been audited. Creating a system that can verify that an audit has been made for a specific contract will strengthen the security guarantees of the whole smart contract ecosystem. - -While this information alone is no guarantee that there are no bugs or flaws in a contract, it can provide an important building block to create innovative security systems for smart contracts in an onchain way. - -### Example - -Imagine a hypothetical [ERC-1155](./eip-1155.md) token bridge. The goal is to create a scalable system where it is possible to easily register new tokens that can be bridged. To minimize the risk of malicious or faulty tokens being registered, audits will be used and verified onchain. - -![Onchain Audit Example Use Case](../assets/eip-7512/example_use_case.png) - -To illustrate the flow within the diagram clearly, it separates the Bridge and the Verifier roles into distinct actors. Theoretically, both can live in the same contract. - -There are four parties: - -- User: The end user that wants to bridge their token -- Bridge Operator: The operator that maintains the bridge -- Bridge: The contract the user will interact with to trigger the bridge operation -- Validator: The contract that validates that a token can be bridged - -As a first (1) step, the bridge operator should define the keys/accounts for the auditors from which audits are accepted for the token registration process. - -With this, the user (or token owner) can trigger the registration flow (2). There are two steps (3 and 6) that will be performed: verify that the provided audit is valid and has been signed by a trusted auditor (4), and check that the token contract implements the bridge's supported token standard ([ERC-1155](./eip-1155.md)) (7). - -After the audit and token standard validations have been performed, it is still advisable to have some form of manual intervention in place by the operator to activate a token for bridging (10). - -Once the token has been activated on the bridge, Users can start bridging it (11). - -## Specification - -The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. - -### Audit Properties - -- Auditor - - `name`: Name of the auditor (i.e. for displaying to the user) - - `uri`: URI to retrieve more information about the auditor - - `authors`: A list of authors that contributed to this audit. This SHOULD be the persons who audited the contracts and created the audit -- Audit - - `auditor`: Information on the auditor - - `auditedContract`: MUST be the `chainId` as well as `deployment` of the contract the audit is related to - - `issuedAt`: MUST contain the information when the original audit (identified by the `auditHash`) was issued - - `ercs`: A list of ERCs that are implemented by the target contract. The ERCs listed MUST be fully implemented. This list MAY be empty - - `auditHash`: MUST be the hash of the original audit. This allows onchain verification of information that may belong to a specific audit - - `auditUri`: SHOULD point to a source where the audit can be retrieved -- Contract - - `chainId`: MUST be a `bytes32` representation of the [EIP-155](./eip-155.md) chain ID of the blockchain that the contract has been deployed in - - `deployment`: MUST be an `address` representation of a contract's deployment address - -### Auditor Verification - -- Signature - - Type - - `SECP256K1` - - Data is the encoded representation of `r`, `s`, and `v` - - `BLS` - - TBD - - `ERC1271` - - Data is the ABI-encoded representation of `chainId`, `address`, `blocknumber`, and the `signature bytes` - - `SECP256R1` - - Data is the encoded representation of `r`, `s`, and `v` - - Data - -### Data types - -```solidity -struct Auditor { - string name; - string uri; - string[] authors; -} - -struct Contract { - bytes32 chainId; - address deployment; -} - -struct AuditSummary { - Auditor auditor; - uint256 issuedAt; - uint256[] ercs; - Contract auditedContract; - bytes32 auditHash; - string auditUri; -} -``` - -### Signing - -For signing [EIP-712](./eip-712.md) will be used. For this the main type is the `AuditSummary` and as the `EIP712Domain` the following definition applies: - -```solidity -struct EIP712Domain { - string name; - string version; -} - -EIP712Domain auditDomain = EIP712Domain("ERC-7652: Onchain Audit Representation", "1.0"); -``` - -The generated signature can then be attached to the `AuditSummary` to generate a new `SignedAuditSummary` object: - -```solidity -enum SignatureType { - SECP256K1, - BLS, - ERC1271, - SECP256R1 -} - -struct Signature { - SignatureType type; - bytes data; -} - -struct SignedAuditSummary extends AuditSummary { - uint256 signedAt; - Signature auditorSignature; -} -``` - -## Rationale - -The current ERC deliberately does not define the `findings` of an audit. Such a definition would require alignment on the definition of what severities are supported, what data of a finding should be stored onchain vs off-chain, and other similar finding-related attributes that are hard to strictly describe. Given the complexity of this task, we consider it to be outside the scope of this EIP. It is important to note that this ERC proposes that a signed audit summary indicates that a specific contract instance (specified by its `chainId` and `deployment`) has undergone a security audit. - -Furthermore, it indicates that this contract instance correctly implements the listed ERCs. This normally corresponds to the final audit revision for a contract which is then connected to the deployment. As specified above, this ERC MUST NOT be considered an attestation of a contract's security but rather a methodology via which data relevant to a smart contract can be extracted; evaluation of the quality, coverage, and guarantees of the data is left up to the integrators of the ERC. - -### Further Considerations - -- `standards` vs `ercs` - - Limiting the scope to audits related to EVM-based smart contract accounts allows a better definition of parameters. -- `chainId` and `deployment` - - As a contract's behavior depends on the blockchain it is deployed in, we have opted to associate a `chainId` as well as `deployment` address per contract that corresponds to an audit -- `contract` vs `contracts` - - Many audits are related to multiple contracts that make up a protocol. To ensure simplicity in the initial version of this ERC, we chose to only reference one contract per audit summary. If multiple contracts have been audited in the same audit engagement, the same audit summary can be associated with different contract instances. An additional benefit of this is the ability to properly associate contract instances with the `ercs` they support. The main drawback of this approach is that it requires multiple signing passes by the auditors. -- Why [EIP-712](./eip-712.md)? - - [EIP-712](./eip-712.md) was chosen as a base due to its tooling compatibility (i.e. for signing) -- How to assign a specific Signing Key to an Auditor? - - Auditors should publicly share the public part of the signature, which can be done via their website, professional page, and any such social medium - - As an extension to this ERC it would be possible to build a public repository, however, this falls out-of-scope of the ERC -- Polymorphic Contracts and Proxies - - This ERC explicitly does **not** mention polymorphic contracts and proxies. These are important to be considered, however, their proper management is delegated to auditors as well as implementors of this ERC - -### Future Extensions - -- Potential expansion of ERC to accommodate non-EVM chains -- Better support for polymorphic/upgradeable contracts and multi-contract audits -- Management of signing keys for auditors -- Definition of findings of an audit - -## Backwards Compatibility - -No backward compatibility issues have been identified in relation to current ERC standards. - -## Reference Implementation - -TBD. - -The following features will be implemented in a reference implementation: - -- Script to trigger signing based on a JSON representing the audit summary -- Contract to verify signed audit summary - -## Security Considerations - -### Auditor Key Management - -The premise of this ERC relies on proper key management by the auditors who partake in the system. If an auditor's key is compromised, they may be associated with seemingly audited or ERC-compliant contracts that ultimately could not comply with the standards. As a potential protection measure, the ERC may define an "association" of auditors (f.e. auditing companies) that would permit a secondary key to revoke existing signatures of auditors as a secondary security measure in case of an auditor's key compromise. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7512.md diff --git a/EIPS/eip-7521.md b/EIPS/eip-7521.md index f19bea4e37e7d0..cd40feea452a93 100644 --- a/EIPS/eip-7521.md +++ b/EIPS/eip-7521.md @@ -1,207 +1 @@ ---- -eip: 7521 -title: General Intents for Smart Contract Wallets -description: A generalized intent specification for smart contract wallets, allowing authorization of current and future intent structures at sign time -author: Stephen Monn (@pixelcircuits), Bikem Bengisu (@supiket) -discussions-to: https://ethereum-magicians.org/t/erc-7521-generalized-intents-for-smart-contract-wallets/15840 -status: Draft -type: Standards Track -category: ERC -created: 2023-09-19 ---- - -## Abstract - -A generalized intent specification entry point contract which enables support for a multitude of intent standards as they evolve over time. Instead of smart contract wallets having to constantly upgrade to provide support for new intent standards as they pop up, a single entry point contract is trusted to handle signature verification which then passes off the low level intent data handling and defining to other contracts specified by users at intent sign time. These signed messages, called a `UserInent`, are gossipped around any host of mempool strategies for MEV searchers to look through and combine with their own `UserIntent` into an object called an `IntentSolution`. MEV searchers then package up an `IntentSolution` object they build into a transaction making a `handleIntents` call to a special contract. This transaction then goes through the typical MEV channels to eventually be included in a block. - -## Motivation - -See also ["ERC-4337: Account Abstraction via Entry Point Contract specification"](./eip-4337.md) and the links therein for historical work and motivation. - -This proposal uses the same entry point contract idea to enable a single interface which smart contract wallets can support now to unlock future proof access to an evolving intent landscape. It seeks to achieve the following goals: - -- **Achieve the key goal of enabling intents for users**: allow users to use smart contract wallets containing arbitrary verification logic to specify intents as described and handled by various other intent standard contracts. -- **Decentralization** - - Allow any MEV searcher to participate in the process of solving signed intents - - Allow any developer to add their own intent standard definitions for users to opt-in to at sign time -- **Be forward thinking for future intent standard compatibility**: Define an intent standard interface that gives future intent standard defining contracts access to as much information about the current `handleIntents` execution context as possible. -- **Keep gas costs down to a minimum**: Include some key intent handling logic, like basic execution guarantees and a default intent standard definition, into the entry point contract itself in order to optimize gas efficiency for the most common use cases. -- **Enable good user experience** - - Avoid the need for smart contract wallet upgrades when a user wants to use a newly developed intent standard - - Enable complex intent composition that only needs a single signature - -## Specification - -Users package up intents they want their wallet to participate in, in an ABI-encoded struct called a `UserIntent`: - -| Field | Type | Description | -| ------------ | --------- | ------------------------------------------------------------------------------------ | -| `standard` | `bytes32` | The intent standard identifier | -| `sender` | `address` | The wallet making the operation | -| `nonce` | `uint256` | Anti-replay parameter | -| `timestamp` | `uint256` | Time validity parameter | -| `intentData` | `bytes[]` | Data defined by the intent standard broken down into multiple segments for execution | -| `signature` | `bytes` | Data passed into the wallet along with the nonce during the verification step | - -The `intentData` parameter is an array of arbitrary bytes whose use is defined by the intent standard. Each item in this array is referred to as an **intent segment**. Users send `UserIntent` objects to any mempool strategy that works best for the intent standard being used. A specialized class of MEV searchers called **solvers** look for these intents and ways that they can be combined with other intents (including their own) to create an ABI-encoded struct called an `IntentSolution`: - -| Field | Type | Description | -| ----------- | -------------- | --------------------------------------------- | -| `timestamp` | `uint256` | The time at which intents should be evaluated | -| `intents` | `UserIntent[]` | List of intents to execute | -| `order` | `uint256[]` | Order of execution for the included intents | - -The solver then creates a **solution transaction**, which packages up an `IntentSolution` object into a single `handleIntents` call to a pre-published global **entry point contract**. - -The core interface of the entry point contract is as follows: - -```solidity -function handleIntents - (IntentSolution calldata solution) - external; - -function simulateHandleIntents - (IntentSolution calldata solution, address target, bytes calldata targetCallData) - external; - -function simulateValidation - (UserIntent calldata intent) - external; - -function registerIntentStandard - (IIntentStandard intentStandard) - external returns (bytes32); - -function verifyExecutingIntentForStandard - (IIntentStandard intentStandard) - external returns (bool); - -error ValidationResult - (bool sigFailed, uint48 validAfter, uint48 validUntil); - -error ExecutionResult - (bool success, bool targetSuccess, bytes targetResult); -``` - -The core interface required for an intent standard to have is: - -```solidity -function validateUserIntent - (UserIntent calldata intent) - external; - -function executeUserIntent - (IntentSolution calldata solution, uint256 executionIndex, uint256 segmentIndex, bytes memory context) - external returns (bytes memory); - -function isIntentStandardForEntryPoint - (IEntryPoint entryPoint) - external returns (bool); -``` - -The core interface required for a wallet to have is: - -```solidity -function validateUserIntent - (UserIntent calldata intent, bytes32 intentHash) - external returns (uint256); - -function generalizedIntentDelegateCall - (bytes memory data) - external returns (bool); -``` - -### Required entry point contract functionality - -The entry point's `handleIntents` function must perform the following steps. It must make two loops, the **verification loop** and the **execution loop**. - -In the verification loop, the `handleIntents` call must perform the following steps for each `UserIntent`: - -- **Validate `timestamp` value on the `IntentSolution`** by making sure it is within an acceptable range of `block.timestamp` or some time before it. -- **Call `validateUserIntent` on the wallet**, passing in the `UserIntent` and the hash of the intent. The wallet should verify the intent's signature. If any `validateUserIntent` call fails, `handleIntents` must skip execution of at least that intent, and may revert entirely. -- **Call `validateUserIntent` on the intent standard**, specified by the `UserIntent` with the `standard` parameter, passing in the `UserIntent`. The intent standard should verify the `intentData` parameter can successfully be parsed according to what the standard expects. If any `validateUserIntent` call fails, `handleIntents` must skip execution of at least that intent, and may revert entirely. - -In the execution loop, the `handleIntents` call must perform the following steps for all **segments** on the `intentData` bytes array parameter on each `UserIntent`: - -- **Call `executeUserIntent` on the intent standard**, specified by the `UserIntent` with the `standard` parameter. This call passes in the entire `IntentSolution` as well as the current `executionIndex` (the number of times this function has already been called for any standard or intent before this), `segmentIndex` (index in the `intentData` array to execute for) and `context` data. The `executeUserIntent` function returns arbitrary bytes per intent which must be remembered and passed into the next `executeUserIntent` call for the same intent. - -It's up to the intent standard to choose how to parse the `intentData` segment bytes and utilize the `context` data blob that persists across intent execution. - -The order of execution for `UserIntent` segments in the `intentData` array always follows the same order defined on the `intentData` parameter. However, the order of execution for segments between `UserIntent` objects can be specified by the `order` parameter of the `IntentSolution` object. For example, an `order` array of `[1,1,0,1]` would result in the second intent being executed twice (segments 1 and 2 on intent 2), then the first intent would be executed (segment 1 on intent 1), followed by the second intent being executed a third time (segment 3 on intent 2). If no ordering is specified in the solution, or all segments have not been processed for all intents after getting to the end of the order array, a default ordering will be used. This default ordering loops from the first intent to the last as many times as necessary until all intents have had all their segments executed. If the ordering calls for an intent to be executed after it's already been executed for all its segments, then the `executeUserIntent` call is simply skipped and execution across all intents continues. - -Before accepting a `UserIntent`, solvers must use an RPC method to locally call the `simulateValidation` function of the entry point, which verifies that the signature and data formatting is correct; see the [Intent validation section below](#intent-validation) for details. - -#### Registering new entry point intent standards - -The entry point's `registerIntentStandard` function must allow for permissionless registration of new intent standard contracts. During the registration process, the entry point contract must verify the contract is meant to be registered by calling the `isIntentStandardForEntryPoint` function on the intent standard contract. This function passes in the entry point contract address which the intent standard can then verify and return true or false. If the intent standard contract returns true, then the entry point registers it and gives it a **standard ID** which is unique to the intent standard contract, entry point contract and chain ID. - -### Extension: default intent standard - -We extend the entry point logic to support a **default intent standard** that can be used by solvers to perform basic operations in a gas efficient way. This default standard is registered with its own standard ID at entry point contract creation time. The functions `validateUserIntent` and `executeUserIntent` are included as part of entry point contracts code in order to reduce external calls. The `intentData` on this default standard is used as calldata to call to the intent `sender`. This allows the solver to perform a basic list of operations from their own wallet in a more gas efficient manner. - -### Intent standard behavior executing an intent - -The intent standard's `executeUserIntent` function is given access to a wide set of data, including the entire `IntentSolution` in order to allow it to be able to implement any kind of logic that may be seen as useful in the future. Each intent standard contract is expected to parse the `UserIntent` objects `intentData` parameter and use that to validate any constraints or perform any actions relevant to the standard. Intent standards can also take advantage of the `context` data it can return at the end of the `executeUserIntent` function. This data is kept by the entry point and passed in as a parameter to the `executeUserIntent` function the next time it is called for an event. This gives intent standards access to a persistent data store as other intents are executed in between others. One example of a use case for this is an intent standard that is looking for a change in state during intent execution (like releasing tokens and expecting to be given other tokens). - -### Smart contract wallet behavior executing an intent - -Smart contract wallets are not expected to do anything by the entry point during intent execution after validation. However, intent standards may wish for the smart contract wallet to perform some action. The smart contract wallet `generalizedIntentDelegateCall` function must perform a delegate call with the given calldata at the calling intent standard. In order for the wallet to trust making the delegate call it must call the `verifyExecutingIntentForStandard` function on the entry point contract to verify both of the following: - -- The `msg.sender` for `generalizedIntentDelegateCall` on the wallet is the intent standard contract that the entry point is currently calling `executeUserIntent` on. -- The smart contract wallet is the `sender` on the `UserIntent` that the entry point is currently calling `executeUserIntent` for. - -### Intent validation - -To validate a `UserIntent`, the solver makes a view call to `simulateValidation(intent)` on the entry point. This function always reverts with `ValidationResult` as a successful response. If the call reverts with another error, the solver rejects the `UserIntent`. While running, the solver should make sure that the call's execution trace does not invoke any **forbidden opcodes**. If this condition is violated, the solver should also reject the `UserIntent`. - -#### Forbidden opcodes - -The forbidden opcodes are to be forbidden when `depth > 2` (i.e. when it is the wallet, intent standard, or other contracts called by them that are being executed). They are: `GASPRICE`, `GASLIMIT`, `DIFFICULTY`, `TIMESTAMP`, `BASEFEE`, `BLOCKHASH`, `NUMBER`, `SELFBALANCE`, `BALANCE`, `ORIGIN`, `GAS`. The only exception is the `GAS` opcode if it is immediately followed by `CALL`, `DELEGATECALL`, `CALLCODE` or `STATICCALL`. They should only be forbidden during verification, not execution. These opcodes are forbidden because their outputs may differ between simulation and execution, so simulation of calls using these opcodes does not reliably tell what would happen if these calls are later done on-chain. - -### Simulation - -To simulate execution of an `IntentSolution`, the solver makes a view call to `simulateHandleIntents(solution)` on the entry point. This function always reverts with `ExecutionResult` as a successful response. If the call reverts with another error, the solver knows the `IntentSolution` was invalid. The solver also has the option to provide a `target` with `targetCallData`. At the end of simulation, the entry point will call to the target contract with the calldata to do any final analysis and return data through the `ExecutionResult` error. - -## Rationale - -The main challenge with a generalized intent standard is being able to adapt to the evolving world of intents. Users need to have a way to express their intents in a seamless way without having to make constant updates to their smart contract wallets. - -In this proposal, we expect wallets to have a `validateUserIntent` function that takes as input a `UserOperation`, and verifies the signature. A trusted entry point contract uses this function to validate the signature and forwards the intent handling logic to an intent standard contract specified in the `UserOperation`. The wallet is then expected to have a `generalizedIntentDelegateCall` function that allows it to perform intent related actions from the intent standard contract, using the `verifyExecutingIntentForStandard` function on the entry point for security. - -The entry point-based approach allows for a clean separation between verification and intent execution, and prevents wallets from having to constantly update to support the latest version of intent composition that a user wants to use. The alternative would involve developers of new intent standards having to convince wallet software developers to support their new intent standards. This proposal moves the core definition of an intent into the hands of users at signing time. - -### Solvers - -Solvers facilitate the fulfillment of a user's intent in search of their own MEV. They also act as the transaction originator for executing intents on-chain, including having to front any gas fees, removing that burden from the typical user. - -Solvers will rely on gossiping networks and solution algorithms that are to be determined by the individual intent standards. - -### Entry point upgrading - -Wallets are encouraged to be DELEGATECALL forwarding contracts for gas efficiency and to allow wallet upgradability. The wallet code is expected to hard-code the entry point into their code for gas efficiency. If a new entry point is introduced, whether to add new functionality, improve gas efficiency, or fix a critical security bug, users can self-call to replace their wallet's code address with a new code address containing code that points to a new entry point. During an upgrade process, it's expected that intent standard contracts will also have to be redeployed and registered to the new entry point. - -#### Intent standard upgrading - -Because intent standards are not hardcoded into the wallet, users do not need to perform any operation to use any newly registered intent standards. A user can simply sign an intent with the new intent standard. - -## Backwards Compatibility - -This ERC does not change the consensus layer, so there are no backwards compatibility issues for Ethereum as a whole. There is a little more difficulty when trying to integrate with existing smart contract wallets. If the wallet already has support for [ERC-4337](./eip-4337.md), then implementing a `validateUserIntent` function should be very similar to the `validateUserOp` function, but would require an upgrade by the user. - -## Reference Implementation - -See `https://github.com/essential-contributions/ERC-7521` - -## Security Considerations - -The entry point contract will need to be very heavily audited and formally verified, because it will serve as a central trust point for _all_ [ERC-7521](./eip-7521.md) supporting wallets. In total, this architecture reduces auditing and formal verification load for the ecosystem, because the amount of work that individual _wallets_ have to do becomes much smaller (they need only verify the `validateUserIntent` function and its "check signature, increment nonce" logic) and gate any calls to `generalizedIntentDelegateCall` by checking with the entry point using the `verifyExecutingIntentForStandard` function. The concentrated security risk in the entry point contract, however, needs to be verified to be very robust since it is so highly concentrated. - -Verification would need to cover one primary claim (not including claims needed to protect solvers, and intent standard related infrastructure): - -- **Safety against arbitrary hijacking**: The entry point only returns true for `verifyExecutingIntentForStandard` when it has successfully validated the signature of the `UserIntent` and is currently in the middle of calling `executeUserIntent` on the `standard` specified in a `UserIntent` which also has the same `sender` as the `msg.sender` wallet calling the function. - -Additional heavy auditing and formal verification will also need to be done for any intent standard contracts a user decides to interact with. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7521.md diff --git a/EIPS/eip-7522.md b/EIPS/eip-7522.md index 62beb76b5f0b9f..680284e1c6a451 100644 --- a/EIPS/eip-7522.md +++ b/EIPS/eip-7522.md @@ -1,114 +1 @@ ---- -eip: 7522 -title: OIDC ZK Verifier for AA Account -description: A ERC-4337 compatible OIDC ZK Verifier -author: Shu Dong (@dongshu2013) , Yudao Yan , Song Z , Kai Chen -discussions-to: https://ethereum-magicians.org/t/eip-7522-oidc-zk-verifier/15862 -status: Draft -type: Standards Track -category: ERC -created: 2023-09-20 -requires: 4337 ---- - -## Abstract - -Account Abstraction facilitates new use cases for smart accounts, empowering users with the ability to tailor authentication and recovery mechanisms to their specific needs. To unlock the potential for more convenient verification methods such as social login, we inevitably need to connect smart accounts and OpenID Connect(OIDC), given its status as the most widely accepted authentication protocol. In this EIP, we proposed a [ERC-4337](./eip-4337.md) compatible OIDC ZK verifier. Users can link their ERC-4337 accounts with OIDC identities and authorize an OIDC verifier to validate user operations by verifying the linked OIDC identity on-chain. - -## Motivation - -Connecting OIDC identity and smart accounts has been a very interesting but challenging problem. Verifying an OIDC issued IdToken is simple. IdToken are usually in the form of JWT and for common JWTs, they usually consist of three parts, a header section, a claim section and a signature section. The user claimed identity shall be included in the claim section and the signature section is usually an RSA signature of a well-known public key from the issuer against the hash of the combination of the header and claim section. - -The most common way of tackling the issue is by utilizing Multi-Party Computation(MPC). However, the limitation of the MPC solution is obvious. First, it relies on a third-party service to sign and aggregate the signature which introduces centralization risk such as single point of failure and vendor lock-in. Second, it leads to privacy concerns, since the separation between the users' Web2 identity to their Web3 address can not be cryptographically guaranteed. - -All these problems could be solved by ZK verification. Privacy will be guaranteed as the connection between Web2 identity and the Web3 account will be hidden. The ZK proof generation process is completely decentralized since it can be done on the client side without involving any third-party service. ZK proofs aggregation has also proven to be viable and paves the way for cheaper verification cost at scale. - -In this EIP, we propose a new model to apply OIDC ZK verification to ERC-4337 account validation. We also define a minimal set of functions of the verifier as well as the input of the ZK proof to unify the interface for different ZK implementations. Now one can link its ERC-4337 account with an OIDC identity and use the OpenID ZK verifier to validate user operations. Due to the high cost of ZK verification, one common use case is to use the verifier as the guardian to recover the account owner if the owner key is lost or stolen. One may set multiple OIDC identities(e.g. Google Account, Facebook Account) as guardians to minimize the centralization risk introduced by the identity provider. - -## Specification - -The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. - -### Definitions - -**Identity Provider(IDP)**: The service to authenticate users and provide signed ID token - -**User**: The client to authenticate users and generate the ZK proof - -**ZK Aggregrator**: The offchain service to aggregate ZK proof from multiple users - -**OpenIdZkVerifier**: The on-chain contract to verify the ZK proof - -The **EntryPoint**, **Aggregator** and **AA Account** are defined at ERC-4337. - -### Example Flow - -![The example workflow](../assets/eip-7522/workflow.png) - -### Interface - -``` -struct OpenIdZkProofPublicInput { - bytes32 jwtHeaderAndPayloadHash; - bytes32 userIdHash; - uint256 expirationTimestamp; - bytes jwtSignature; -} - -interface IOpenIdZkVerifier { - // @notice get verification key of the open id authenticator - function getVerificationKeyOfIdp() external view returns(bytes memory); - - // @notice get id hash of account - function getIdHash(address account) external view returns(bytes32); - - // @notice the function verifies the proof and given a user op - // @params op: the user operation defined by ERC-4337 - // input: the zk proof input with JWT info to prove - // proof: the generated ZK proof for input and op - function verify( - UserOp memory op, - OpenIdZkProofPublicInput input, - bytes memory proof - ) external; - - // @notice the function verifies the aggregated proof give a list of user ops - // @params ops: a list of user operations defined by ERC-4337 - // inputs: a list of zk proof input with JWT info to prove - // aggregatedProof: the aggregated ZK proof for inputs and ops - function verifyAggregated( - UserOp[] memory ops, - OpenIdZkProofPublicInput[] memory inputs, - bytes memory aggregatedProof - ) external; -} -``` - -## Rationale - -To verify identity ownership on-chain, **IOpenIdVerifier** needs at least three pieces of information: - -1. the user ID to identify the user in the IDP. The **getIdHash** function returns the hash of the user id given smart account address. There may be multiple smart accounts linked to the same user ID. - -2. the public key of the key pair used by identity provider to sign ID token. It is provided by the **getVerificationKeyOfIdp** function. - -3. the ZK proof to verify the OIDC identity. The verification is done by the **verify** function. Besides the proof, the function takes two extra params: the user operation to execute and the public input to prove. The **verifyAggregated** is similar to the **verify** function but with a list of input and ops as parameters - -The **OpenIdZkProofPublicInput** struct must contain the following fields: - -| Field | Description | -| ----------- | ----------- | -| jwtHeaderAndPayloadHash | the hash of the JWT header plus payload | -| userIdHash | the hash of the user id, the user id should present as value of one claim | -| expirationTimestamp | the expiration time of the JWT, which could be value of "exp" claim | -| jwtSignature | the signature of the JWT | - -We didn't include the verification key and the user operation hash in the struct because we assume the public key could be provided by **getVerificationKeyOfIdp** function and the user operation hash could be calculated from the raw user operation passed in. - -## Security Considerations - -The proof must verify the *expirationTimestamp* to prevent replay attacks. **expirationTimestamp** should be incremental and could be the **exp** field in JWT payload. The proof must verify the user operation to prevent front running attacks. The proof must verify the **userIdHash**. The verifier must verify that the sender from each user operation is linked to the user ID hash via the **getIdHash** function. - -## Copyright - -Copyright and related rights waived via CC0. +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7522.md diff --git a/EIPS/eip-7528.md b/EIPS/eip-7528.md index 399c671d72afe3..d8f3fd8bbcdd68 100644 --- a/EIPS/eip-7528.md +++ b/EIPS/eip-7528.md @@ -1,66 +1 @@ ---- -eip: 7528 -title: ETH (Native Asset) Address Convention -description: An address placeholder for ETH when used in the same context as an ERC-20 token. -author: Joey Santoro (@joeysantoro) -discussions-to: https://ethereum-magicians.org/t/eip-7808-eth-native-asset-address-convention/15989 -status: Draft -type: Standards Track -category: ERC -created: 2023-10-03 -requires: 20, 4626 ---- - -## Abstract - -The following standard proposes a convention for using the address `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` in all contexts where an address is used to represent ETH in the same capacity as an [ERC-20](./eip-20.md) token. This would apply to both events where an address field would denote ETH or an [ERC-20](./eip-20.md) token, as well as discriminators such as the `asset` field of an [ERC-4626](./eip-4626.md) vault. - -This standard generalizes to other EVM chains where the native asset is not ETH. - -## Motivation - -ETH, being a fungible unit of value, often behaves similarly to [ERC-20](./eip-20.md) tokens. Protocols tend to implement a standard interface for ERC-20 tokens, and benefit from having the ETH implementation to closely mirror the [ERC-20](./eip-20.md) implementations. - -In many cases, protocols opt to use Wrapped ETH (e.g. WETH9 deployed at address 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 on Etherum Mainnet) for [ERC-20](./eip-20.md) compliance. In other cases, protocols will use native ETH due to gas considerations, or the requirement of using native ETH such as in the case of a Liquid Staking Token (LST). - -In addition, protocols might create separate events for handling ETH native cases and ERC-20 cases. This creates data fragmentation and integration overhead for off-chain infrastructure. By having a strong convention for an ETH address to use for cases where it behaves like an [ERC-20](./eip-20.md) token, it becomes beneficial to use one single event format for both cases. - -One intended use case for the standard is [ERC-4626](./eip-4626.md) compliant LSTs which use ETH as the `asset`. This extends the benefits and tooling of [ERC-4626](./eip-4626.md) to LSTs and integrating protocols. - -This standard allows protocols and off-chain data infrastructure to coordinate around a shared understanding that any time `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` is used as an address in an [ERC-20](./eip-20.md) context, it means ETH. - -## Specification - -This standard applies for all components of smart contract systems in which an address is used to identify an [ERC-20](./eip-20.md) token, and where native ETH is used in certain instances in place of an [ERC-20](./eip-20.md) token. The usage of the term Token below means ETH or an [ERC-20](./eip-20.md) in this context. - -Any fields or events where an [ERC-20](./eip-20.md) address is used, yet the underlying Token is ETH, the address field MUST return `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` - -Any fields or events where the Token is a non-enshrined wrapped ERC-20 version of ETH (i.e WETH9) MUST use that Token's address and MUST NOT use `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`. - -## Rationale - -Smart Contract Systems which use both ETH and [ERC-20](./eip-20.md) in the same context should have a single address convention for the ETH case. - -### Considered alternative addresses - -Many existing implementations of the same use case as this standard use addresses such as 0x0, 0x1, and 0xe for gas efficiency of having leading zero bytes. - -Ultimately, all of these addresses collide with potential precompile addresses and are less distinctive as identifiers for ETH. - -`0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` has the most current usage, is distinctive, and would not collide with any precompiles. These benefits outweigh the potential gas benefits of other alternatives. - -## Backwards Compatibility - -This standard has no known compatibility issues with other standards. - -## Reference Implementation - -N/A - -## Security Considerations - -Using ETH as a Token instead of WETH exposes smart contract systems to re-entrancy and similar classes of vulnerabilities. Implementers must take care to follow the industry standard development patterns (e.g. checks-effects-interactions) when the Token is ETH. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-7528.md diff --git a/EIPS/eip-777.md b/EIPS/eip-777.md index a67dcb1cd4f85b..fe52a4eeb1438c 100644 --- a/EIPS/eip-777.md +++ b/EIPS/eip-777.md @@ -1,1278 +1 @@ ---- -eip: 777 -title: Token Standard -author: Jacques Dafflon , Jordi Baylina , Thomas Shababi -discussions-to: https://github.com/ethereum/EIPs/issues/777 -status: Final -type: Standards Track -category: ERC -created: 2017-11-20 -requires: 1820 ---- - -## Simple Summary - -This EIP defines standard interfaces and behaviors for token contracts. - -## Abstract - -This standard defines a new way to interact with a token contract while remaining backward compatible with [ERC-20]. - -It defines advanced features to interact with tokens. -Namely, *operators* to send tokens on behalf of another address—contract or regular account—and -send/receive *hooks* to offer token holders more control over their tokens. - -It takes advantage of [ERC-1820] to find out whether and where to notify contracts and regular addresses -when they receive tokens as well as to allow compatibility with already-deployed contracts. - -## Motivation - -This standard tries to improve upon the widely used [ERC-20] token standard. -The main advantages of this standard are: - -1. Uses the same philosophy as Ether in that tokens are sent with `send(dest, value, data)`. - -2. Both contracts and regular addresses can control and reject which token they send - by registering a `tokensToSend` hook. - (Rejection is done by `revert`ing in the hook function.) - -3. Both contracts and regular addresses can control and reject which token they receive - by registering a `tokensReceived` hook. - (Rejection is done by `revert`ing in the hook function.) - -4. The `tokensReceived` hook allows to send tokens to a contract and notify it in a single transaction, - unlike [ERC-20] which requires a double call (`approve`/`transferFrom`) to achieve this. - -5. The holder can "authorize" and "revoke" operators which can send tokens on their behalf. - These operators are intended to be verified contracts - such as an exchange, a cheque processor or an automatic charging system. - -6. Every token transaction contains `data` and `operatorData` bytes fields - to be used freely to pass data from the holder and the operator, respectively. - -7. It is backward compatible with wallets that do not contain the `tokensReceived` hook function - by deploying a proxy contract implementing the `tokensReceived` hook for the wallet. - -## Specification - -### ERC777Token (Token Contract) - -``` solidity -interface ERC777Token { - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function totalSupply() external view returns (uint256); - function balanceOf(address holder) external view returns (uint256); - function granularity() external view returns (uint256); - - function defaultOperators() external view returns (address[] memory); - function isOperatorFor( - address operator, - address holder - ) external view returns (bool); - function authorizeOperator(address operator) external; - function revokeOperator(address operator) external; - - function send(address to, uint256 amount, bytes calldata data) external; - function operatorSend( - address from, - address to, - uint256 amount, - bytes calldata data, - bytes calldata operatorData - ) external; - - function burn(uint256 amount, bytes calldata data) external; - function operatorBurn( - address from, - uint256 amount, - bytes calldata data, - bytes calldata operatorData - ) external; - - event Sent( - address indexed operator, - address indexed from, - address indexed to, - uint256 amount, - bytes data, - bytes operatorData - ); - event Minted( - address indexed operator, - address indexed to, - uint256 amount, - bytes data, - bytes operatorData - ); - event Burned( - address indexed operator, - address indexed from, - uint256 amount, - bytes data, - bytes operatorData - ); - event AuthorizedOperator( - address indexed operator, - address indexed holder - ); - event RevokedOperator(address indexed operator, address indexed holder); -} -``` - -The token contract MUST implement the above interface. -The implementation MUST follow the specifications described below. - -The token contract MUST register the `ERC777Token` interface with its own address via [ERC-1820]. - -> This is done by calling the `setInterfaceImplementer` function on the [ERC-1820] registry -> with the token contract address as both the address and the implementer -> and the `keccak256` hash of `ERC777Token` (`0xac7fbab5f54a3ca8194167523c6753bfeb96a445279294b6125b68cce2177054`) -> as the interface hash. - -If the contract has a switch to enable or disable ERC777 functions, every time the switch is triggered, -the token MUST register or unregister the `ERC777Token` interface for its own address accordingly via ERC1820. -Unregistering implies calling the `setInterfaceImplementer` with the token contract address as the address, -the `keccak256` hash of `ERC777Token` as the interface hash and `0x0` as the implementer. -(See [Set An Interface For An Address][erc1820-set] in [ERC-1820] for more details.) - -When interacting with the token contract, all amounts and balances MUST be unsigned integers. -I.e. internally, all values are stored as a denomination of 1E-18 of a token. -The display denomination—to display any amount to the end user—MUST -be 1018 of the internal denomination. - -In other words, the internal denomination is similar to a wei -and the display denomination is similar to an ether. -It is equivalent to an [ERC-20]'s `decimals` function returning `18`. -E.g. if a token contract returns a balance of `500,000,000,000,000,000` (0.5×1018) for a user, -the user interface MUST show `0.5` tokens to the user. -If the user wishes to send `0.3` tokens, -the contract MUST be called with an amount of `300,000,000,000,000,000` (0.3×1018). - -User Interfaces which are generated programmatically from the ABI of the token contract -MAY use and display the internal denomination. -But this MUST be made clear, for example by displaying the `uint256` type. - -#### **View Functions** - -The `view` functions detailed below MUST be implemented. - -**`name` function** - -``` solidity -function name() external view returns (string memory) -``` - -Get the name of the token, e.g., `"MyToken"`. - -> **identifier:** `06fdde03` -> **returns:** Name of the token. - -**`symbol` function** - -``` solidity -function symbol() external view returns (string memory) -``` - -Get the symbol of the token, e.g., `"MYT"`. - -> **identifier:** `95d89b41` -> **returns:** Symbol of the token. - -**`totalSupply` function** - -``` solidity -function totalSupply() external view returns (uint256) -``` - -Get the total number of minted tokens. - -*NOTE*: The total supply MUST be equal to the sum of the balances of all addresses—as -returned by the `balanceOf` function. - -*NOTE*: The total supply MUST be equal to the sum of all the minted tokens -as defined in all the `Minted` events minus the sum of all the burned tokens as defined in all the `Burned` events. - -> **identifier:** `18160ddd` -> **returns:** Total supply of tokens currently in circulation. - -**`balanceOf` function** - -``` solidity -function balanceOf(address holder) external view returns (uint256) -``` - -Get the balance of the account with address `holder`. - -The balance MUST be zero (`0`) or higher. - -> **identifier:** `70a08231` -> **parameters** -> `holder`: Address for which the balance is returned. -> -> **returns:** Amount of tokens held by `holder` in the token contract. - -**`granularity` function** - -``` solidity -function granularity() external view returns (uint256) -``` - -Get the smallest part of the token that's not divisible. - -In other words, the granularity is the smallest amount of tokens (in the internal denomination) -which MAY be minted, sent or burned at any time. - -The following rules MUST be applied regarding the *granularity*: - -- The *granularity* value MUST be set at creation time. - -- The *granularity* value MUST NOT be changed, ever. - -- The *granularity* value MUST be greater than or equal to `1`. - -- All balances MUST be a multiple of the granularity. - -- Any amount of tokens (in the internal denomination) minted, sent or burned - MUST be a multiple of the *granularity* value. - -- Any operation that would result in a balance that's not a multiple of the *granularity* value - MUST be considered invalid, and the transaction MUST `revert`. - -*NOTE*: Most tokens SHOULD be fully partition-able. -I.e., this function SHOULD return `1` unless there is a good reason for not allowing any fraction of the token. - -> **identifier:** `556f0dc7` -> **returns:** The smallest non-divisible part of the token. - -*NOTE*: [`defaultOperators`][defaultOperators] and [`isOperatorFor`][isOperatorFor] are also `view` functions, -defined under the [operators] for consistency. - -*[ERC-20] compatibility requirement*: -The decimals of the token MUST always be `18`. -For a *pure* ERC777 token the [ERC-20] `decimals` function is OPTIONAL, -and its existence SHALL NOT be relied upon when interacting with the token contract. -(The decimal value of `18` is implied.) -For an [ERC-20] compatible token, the `decimals` function is REQUIRED and MUST return `18`. -(In [ERC-20], the `decimals` function is OPTIONAL. -If the function is not present, the `decimals` value is not clearly defined and may be assumed to be `0`. -Hence for compatibility reasons, `decimals` MUST be implemented for [ERC-20] compatible tokens.) - -#### **Operators** - -An `operator` is an address which is allowed to send and burn tokens on behalf of some *holder*. - -When an address becomes an *operator* for a *holder*, an `AuthorizedOperator` event MUST be emitted. -The `AuthorizedOperator`'s `operator` (topic 1) and `holder` (topic 2) -MUST be the addresses of the *operator* and the *holder* respectively. - -When a *holder* revokes an *operator*, a `RevokedOperator` event MUST be emitted. -The `RevokedOperator`'s `operator` (topic 1) and `holder` (topic 2) -MUST be the addresses of the *operator* and the *holder* respectively. - -*NOTE*: A *holder* MAY have multiple *operators* at the same time. - -The token MAY define *default operators*. -A *default operator* is an implicitly authorized *operator* for all *holders*. -`AuthorizedOperator` events MUST NOT be emitted when defining the *default operators*. -The rules below apply to *default operators*: - -- The token contract MUST define *default operators* at creation time. - -- The *default operators* MUST be invariants. I.e., the token contract MUST NOT add or remove *default operators* ever. - -- `AuthorizedOperator` events MUST NOT be emitted when defining *default operators*. - -- A *holder* MUST be allowed to revoke a *default operator* - (unless the *holder* is the *default operator* in question). - -- A *holder* MUST be allowed to re-authorize a previously revoked *default operator*. - -- When a *default operator* is explicitly authorized or revoked for a specific *holder*, - an `AuthorizedOperator` or `RevokedOperator` event (respectively) MUST be emitted. - -The following rules apply to any *operator*: - -- An address MUST always be an *operator* for itself. Hence an address MUST NOT ever be revoked as its own *operator*. - -- If an address is an *operator* for a *holder*, `isOperatorFor` MUST return `true`. - -- If an address is not an *operator* for a *holder*, `isOperatorFor` MUST return `false`. - -- The token contract MUST emit an `AuthorizedOperator` event with the correct values - when a *holder* authorizes an address as its *operator* as defined in the - [`AuthorizedOperator` Event][authorizedoperator]. - -- The token contract MUST emit a `RevokedOperator` event with the correct values - when a *holder* revokes an address as its *operator* as defined in the - [`RevokedOperator` Event][revokedoperator]. - -*NOTE*: A *holder* MAY authorize an already authorized *operator*. -An `AuthorizedOperator` MUST be emitted each time. - -*NOTE*: A *holder* MAY revoke an already revoked *operator*. -A `RevokedOperator` MUST be emitted each time. - -**`AuthorizedOperator` event** - -``` solidity -event AuthorizedOperator(address indexed operator, address indexed holder) -``` - -Indicates the authorization of `operator` as an *operator* for `holder`. - -*NOTE*: This event MUST NOT be emitted outside of an *operator* authorization process. - -> **parameters** -> `operator`: Address which became an *operator* of `holder`. -> `holder`: Address of a *holder* which authorized the `operator` address as an *operator*. - -**`RevokedOperator` event** - -``` solidity -event RevokedOperator(address indexed operator, address indexed holder) -``` - -Indicates the revocation of `operator` as an *operator* for `holder`. - -*NOTE*: This event MUST NOT be emitted outside of an *operator* revocation process. - -> **parameters** -> `operator`: Address which was revoked as an *operator* of `holder`. -> `holder`: Address of a *holder* which revoked the `operator` address as an *operator*. - -The `defaultOperators`, `authorizeOperator`, `revokeOperator` and `isOperatorFor` functions described below -MUST be implemented to manage *operators*. -Token contracts MAY implement other functions to manage *operators*. - -**`defaultOperators` function** - -``` solidity -function defaultOperators() external view returns (address[] memory) -``` - -Get the list of *default operators* as defined by the token contract. - -*NOTE*: If the token contract does not have any *default operators*, this function MUST return an empty list. - -> **identifier:** `06e48538` -> **returns:** List of addresses of all the *default operators*. - -**`authorizeOperator` function** - -``` solidity -function authorizeOperator(address operator) external -``` - -Set a third party `operator` address as an *operator* of `msg.sender` to send and burn tokens on its behalf. - -*NOTE*: The *holder* (`msg.sender`) is always an *operator* for itself. -This right SHALL NOT be revoked. -Hence this function MUST `revert` if it is called to authorize the holder (`msg.sender`) -as an *operator* for itself (i.e. if `operator` is equal to `msg.sender`). - -> **identifier:** `959b8c3f` -> **parameters** -> `operator`: Address to set as an *operator* for `msg.sender`. - -**`revokeOperator` function** - -``` solidity -function revokeOperator(address operator) external -``` - -Remove the right of the `operator` address to be an *operator* for `msg.sender` -and to send and burn tokens on its behalf. - -*NOTE*: The *holder* (`msg.sender`) is always an *operator* for itself. -This right SHALL NOT be revoked. -Hence this function MUST `revert` if it is called to revoke the holder (`msg.sender`) -as an *operator* for itself (i.e., if `operator` is equal to `msg.sender`). - -> **identifier:** `fad8b32a` -> **parameters** -> `operator`: Address to rescind as an *operator* for `msg.sender`. - -**`isOperatorFor` function** - -``` solidity -function isOperatorFor( - address operator, - address holder -) external view returns (bool) -``` - -Indicate whether the `operator` address is an *operator* of the `holder` address. - -> **identifier:** `d95b6371` -> **parameters** -> `operator`: Address which may be an *operator* of `holder`. -> `holder`: Address of a *holder* which may have the `operator` address as an *operator*. -> -> **returns:** `true` if `operator` is an *operator* of `holder` and `false` otherwise. - -*NOTE*: To know which addresses are *operators* for a given *holder*, -one MUST call `isOperatorFor` with the *holder* for each *default operator* -and parse the `AuthorizedOperator`, and `RevokedOperator` events for the *holder* in question. - -#### **Sending Tokens** - -When an *operator* sends an `amount` of tokens from a *holder* to a *recipient* -with the associated `data` and `operatorData`, the token contract MUST apply the following rules: - -- Any authorized *operator* MAY send tokens to any *recipient* (except to `0x0`). - -- The balance of the *holder* MUST be decreased by the `amount`. - -- The balance of the *recipient* MUST be increased by the `amount`. - -- The balance of the *holder* MUST be greater or equal to the `amount`—such - that its resulting balance is greater or equal to zero (`0`) after the send. - -- The token contract MUST emit a `Sent` event with the correct values as defined in the [`Sent` Event][sent]. - -- The *operator* MAY include information in the `operatorData`. - -- The token contract MUST call the `tokensToSend` hook of the *holder* - if the *holder* registers an `ERC777TokensSender` implementation via [ERC-1820]. - -- The token contract MUST call the `tokensReceived` hook of the *recipient* - if the *recipient* registers an `ERC777TokensRecipient` implementation via [ERC-1820]. - -- The `data` and `operatorData` MUST be immutable during the entire send process—hence - the same `data` and `operatorData` MUST be used to call both hooks and emit the `Sent` event. - -The token contract MUST `revert` when sending in any of the following cases: - -- The *operator* address is not an authorized operator for the *holder*. - -- The resulting *holder* balance or *recipient* balance after the send - is not a multiple of the *granularity* defined by the token contract. - -- The *recipient* is a contract, and it does not implement the `ERC777TokensRecipient` interface via [ERC-1820]. - -- The address of the *holder* or the *recipient* is `0x0`. - -- Any of the resulting balances becomes negative, i.e. becomes less than zero (`0`). - -- The `tokensToSend` hook of the *holder* `revert`s. - -- The `tokensReceived` hook of the *recipient* `revert`s. - -The token contract MAY send tokens from many *holders*, to many *recipients*, or both. In this case: - -- The previous send rules MUST apply to all the *holders* and all the *recipients*. -- The sum of all the balances incremented MUST be equal to the total sent `amount`. -- The sum of all the balances decremented MUST be equal to the total sent `amount`. -- A `Sent` event MUST be emitted for every *holder* and *recipient* pair with the corresponding amount for each pair. -- The sum of all the amounts from the `Sent` event MUST be equal to the total sent `amount`. - -*NOTE*: Mechanisms such as applying a fee on a send is considered as a send to multiple *recipients*: -the intended *recipient* and the fee *recipient*. - -*NOTE*: Movements of tokens MAY be chained. -For example, if a contract upon receiving tokens sends them further to another address. -In this case, the previous send rules apply to each send, in order. - -*NOTE*: Sending an amount of zero (`0`) tokens is valid and MUST be treated as a regular send. - -*Implementation Requirement*: -- The token contract MUST call the `tokensToSend` hook *before* updating the state. -- The token contract MUST call the `tokensReceived` hook *after* updating the state. -I.e., `tokensToSend` MUST be called first, -then the balances MUST be updated to reflect the send, -and finally `tokensReceived` MUST be called *afterward*. -Thus a `balanceOf` call within `tokensToSend` returns the balance of the address *before* the send -and a `balanceOf` call within `tokensReceived` returns the balance of the address *after* the send. - -*NOTE*: The `data` field contains information provided by the *holder*—similar -to the data field in a regular ether send transaction. -The `tokensToSend()` hook, the `tokensReceived()`, or both -MAY use the information to decide if they wish to reject the transaction. - -*NOTE*: The `operatorData` field is analogous to the `data` field except it SHALL be provided by the *operator*. - -The `operatorData` MUST only be provided by the *operator*. -It is intended more for logging purposes and particular cases. -(Examples include payment references, cheque numbers, countersignatures and more.) -In most of the cases the recipient would ignore the `operatorData`, or at most, it would log the `operatorData`. - -**`Sent` event** - -``` solidity -event Sent( - address indexed operator, - address indexed from, - address indexed to, - uint256 amount, - bytes data, - bytes operatorData -) -``` - -Indicate a send of `amount` of tokens from the `from` address to the `to` address by the `operator` address. - -*NOTE*: This event MUST NOT be emitted outside of a send or an [ERC-20] transfer process. - -> **parameters** -> `operator`: Address which triggered the send. -> `from`: *Holder* whose tokens were sent. -> `to`: Recipient of the tokens. -> `amount`: Number of tokens sent. -> `data`: Information provided by the *holder*. -> `operatorData`: Information provided by the *operator*. - -The `send` and `operatorSend` functions described below MUST be implemented to send tokens. -Token contracts MAY implement other functions to send tokens. - -**`send` function** - -``` solidity -function send(address to, uint256 amount, bytes calldata data) external -``` - -Send the `amount` of tokens from the address `msg.sender` to the address `to`. - -The *operator* and the *holder* MUST both be the `msg.sender`. - -> **identifier:** `9bd9bbc6` -> **parameters** -> `to`: Recipient of the tokens. -> `amount`: Number of tokens to send. -> `data`: Information provided by the *holder*. - -**`operatorSend` function** - -``` solidity -function operatorSend( - address from, - address to, - uint256 amount, - bytes calldata data, - bytes calldata operatorData -) external -``` - -Send the `amount` of tokens on behalf of the address `from` to the address `to`. - -*Reminder*: If the *operator* address is not an authorized operator of the `from` address, -then the send process MUST `revert`. - -*NOTE*: `from` and `msg.sender` MAY be the same address. -I.e., an address MAY call `operatorSend` for itself. -This call MUST be equivalent to `send` with the addition -that the *operator* MAY specify an explicit value for `operatorData` -(which cannot be done with the `send` function). - -> **identifier:** `62ad1b83` -> **parameters** -> `from`: *Holder* whose tokens are being sent. -> `to`: Recipient of the tokens. -> `amount`: Number of tokens to send. -> `data`: Information provided by the *holder*. -> `operatorData`: Information provided by the *operator*. - -#### **Minting Tokens** - -Minting tokens is the act of producing new tokens. -[ERC-777] intentionally does not define specific functions to mint tokens. -This intent comes from the wish not to limit the use of the [ERC-777] standard -as the minting process is generally specific for every token. - -Nonetheless, the rules below MUST be respected when minting for a *recipient*: - -- Tokens MAY be minted for any *recipient* address (except `0x0`). - -- The total supply MUST be increased by the amount of tokens minted. - -- The balance of `0x0` MUST NOT be decreased. - -- The balance of the *recipient* MUST be increased by the amount of tokens minted. - -- The token contract MUST emit a `Minted` event with the correct values as defined in the [`Minted` Event][minted]. - -- The token contract MUST call the `tokensReceived` hook of the *recipient* - if the *recipient* registers an `ERC777TokensRecipient` implementation via [ERC-1820]. - -- The `data` and `operatorData` MUST be immutable during the entire mint process—hence - the same `data` and `operatorData` MUST be used to call the `tokensReceived` hook and emit the `Minted` event. - -The token contract MUST `revert` when minting in any of the following cases: - -- The resulting *recipient* balance after the mint is not a multiple of the *granularity* defined by the token contract. -- The *recipient* is a contract, and it does not implement the `ERC777TokensRecipient` interface via [ERC-1820]. -- The address of the *recipient* is `0x0`. -- The `tokensReceived` hook of the *recipient* `revert`s. - -*NOTE*: The initial token supply at the creation of the token contract MUST be considered as minting -for the amount of the initial supply to the address(es) receiving the initial supply. -This means one or more `Minted` events must be emitted -and the `tokensReceived` hook of the recipient(s) MUST be called. - -*[ERC-20] compatibility requirement*: -While a `Sent` event MUST NOT be emitted when minting, -if the token contract is [ERC-20] backward compatible, -a `Transfer` event with the `from` parameter set to `0x0` SHOULD be emitted as defined in the [ERC-20] standard. - -The token contract MAY mint tokens for multiple *recipients* at once. In this case: - -- The previous mint rules MUST apply to all the *recipients*. -- The sum of all the balances incremented MUST be equal to the total minted amount. -- A `Minted` event MUST be emitted for every *recipient* with the corresponding amount for each *recipient*. -- The sum of all the amounts from the `Minted` event MUST be equal to the total minted `amount`. - -*NOTE*: Minting an amount of zero (`0`) tokens is valid and MUST be treated as a regular mint. - -*NOTE*: While during a send or a burn, the data is provided by the *holder*, it is inapplicable for a mint. -In this case the data MAY be provided by the token contract or the *operator*, -for example to ensure a successful minting to a *holder* expecting specific data. - -*NOTE*: The `operatorData` field contains information provided by the *operator*—similar -to the data field in a regular ether send transaction. -The `tokensReceived()` hooks MAY use the information to decide if it wish to reject the transaction. - -**`Minted` event** - -``` solidity -event Minted( - address indexed operator, - address indexed to, - uint256 amount, - bytes data, - bytes operatorData -) -``` - -Indicate the minting of `amount` of tokens to the `to` address by the `operator` address. - -*NOTE*: This event MUST NOT be emitted outside of a mint process. - -> **parameters** -> `operator`: Address which triggered the mint. -> `to`: Recipient of the tokens. -> `amount`: Number of tokens minted. -> `data`: Information provided for the *recipient*. -> `operatorData`: Information provided by the *operator*. - -#### **Burning Tokens** - -Burning tokens is the act of destroying existing tokens. -[ERC-777] explicitly defines two functions to burn tokens (`burn` and `operatorBurn`). -These functions facilitate the integration of the burning process in wallets and dapps. -However, the token contract MAY prevent some or all *holders* from burning tokens for any reason. -The token contract MAY also define other functions to burn tokens. - -The rules below MUST be respected when burning the tokens of a *holder*: - -- Tokens MAY be burned from any *holder* address (except `0x0`). - -- The total supply MUST be decreased by the amount of tokens burned. - -- The balance of `0x0` MUST NOT be increased. - -- The balance of the *holder* MUST be decreased by amount of tokens burned. - -- The token contract MUST emit a `Burned` event with the correct values as defined in the [`Burned` Event][burned]. - -- The token contract MUST call the `tokensToSend` hook of the *holder* - if the *holder* registers an `ERC777TokensSender` implementation via [ERC-1820]. - -- The `operatorData` MUST be immutable during the entire burn process—hence - the same `operatorData` MUST be used to call the `tokensToSend` hook and emit the `Burned` event. - -The token contract MUST `revert` when burning in any of the following cases: - -- The *operator* address is not an authorized operator for the *holder*. - -- The resulting *holder* balance after the burn is not a multiple of the *granularity* - defined by the token contract. - -- The balance of *holder* is inferior to the amount of tokens to burn - (i.e., resulting in a negative balance for the *holder*). - -- The address of the *holder* is `0x0`. - -- The `tokensToSend` hook of the *holder* `revert`s. - -*[ERC-20] compatibility requirement*: -While a `Sent` event MUST NOT be emitted when burning; -if the token contract is [ERC-20] enabled, a `Transfer` event with the `to` parameter set to `0x0` SHOULD be emitted. -The [ERC-20] standard does not define the concept of burning tokens, but this is a commonly accepted practice. - -The token contract MAY burn tokens for multiple *holders* at once. In this case: - -- The previous burn rules MUST apply to each *holders*. -- The sum of all the balances decremented MUST be equal to the total burned amount. -- A `Burned` event MUST be emitted for every *holder* with the corresponding amount for each *holder*. -- The sum of all the amounts from the `Burned` event MUST be equal to the total burned `amount`. - -*NOTE*: Burning an amount of zero (`0`) tokens is valid and MUST be treated as a regular burn. - -*NOTE*: The `data` field contains information provided by the holder—similar -to the data field in a regular ether send transaction. -The `tokensToSend()` hook, the `tokensReceived()`, or both -MAY use the information to decide if they wish to reject the transaction. - -*NOTE*: The `operatorData` field is analogous to the `data` field except it SHALL be provided by the *operator*. - -**`Burned` event** - -``` solidity -event Burned( - ddress indexed operator, - address indexed from, - uint256 amount, - bytes data, - bytes operatorData -); -``` - -Indicate the burning of `amount` of tokens from the `from` address by the `operator` address. - -*NOTE*: This event MUST NOT be emitted outside of a burn process. - -> **parameters** -> `operator`: Address which triggered the burn. -> `from`: *Holder* whose tokens were burned. -> `amount`: Number of tokens burned. -> `data`: Information provided by the *holder*. -> `operatorData`: Information provided by the *operator*. - -The `burn` and `operatorBurn` functions described below MUST be implemented to burn tokens. -Token contracts MAY implement other functions to burn tokens. - -**`burn` function** - -``` solidity -function burn(uint256 amount, bytes calldata data) external -``` - -Burn the `amount` of tokens from the address `msg.sender`. - -The *operator* and the *holder* MUST both be the `msg.sender`. - -> **identifier:** `fe9d9303` -> **parameters** -> `amount`: Number of tokens to burn. -> `data`: Information provided by the *holder*. - -**`operatorBurn` function** - -``` solidity -function operatorBurn( - address from, - uint256 amount, - bytes calldata data, - bytes calldata operatorData -) external -``` - -Burn the `amount` of tokens on behalf of the address `from`. - -*Reminder*: If the *operator* address is not an authorized operator of the `from` address, -then the burn process MUST `revert`. - -> **identifier:** `fc673c4f` -> **parameters** -> `from`: *Holder* whose tokens will be burned. -> `amount`: Number of tokens to burn. -> `data`: Information provided by the *holder*. -> `operatorData`: Information provided by the *operator*. - -*NOTE*: The *operator* MAY pass any information via `operatorData`. -The `operatorData` MUST only be provided by the *operator*. - -*NOTE*: `from` and `msg.sender` MAY be the same address. -I.e., an address MAY call `operatorBurn` for itself. -This call MUST be equivalent to `burn` -with the addition that the *operator* MAY specify an explicit value for `operatorData` -(which cannot be done with the `burn` function). - -#### **`ERC777TokensSender` And The `tokensToSend` Hook** - -The `tokensToSend` hook notifies of any request to decrement the balance (send and burn) for a given *holder*. -Any address (regular or contract) wishing to be notified of token debits from their address -MAY register the address of a contract implementing the `ERC777TokensSender` interface described below via [ERC-1820]. - -> This is done by calling the `setInterfaceImplementer` function on the [ERC-1820] registry -> with the *holder* address as the address, -> the `keccak256` hash of `ERC777TokensSender` -> (`0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895`) as the interface hash, -> and the address of the contract implementing the `ERC777TokensSender` as the implementer. - -``` solidity -interface ERC777TokensSender { - function tokensToSend( - address operator, - address from, - address to, - uint256 amount, - bytes calldata userData, - bytes calldata operatorData - ) external; -} -``` - -*NOTE*: A regular address MAY register a different address—the address of a contract—implementing -the interface on its behalf. -A contract MAY register either its address or the address of another contract -but said address MUST implement the interface on its behalf. - -**`tokensToSend`** - -``` solidity -function tokensToSend( - address operator, - address from, - address to, - uint256 amount, - bytes calldata userData, - bytes calldata operatorData -) external -``` - -Notify a request to send or burn (if `to` is `0x0`) an `amount` tokens from the `from` address to the `to` address -by the `operator` address. - -*NOTE*: This function MUST NOT be called outside of a burn, send or [ERC-20] transfer process. - -> **identifier:** `75ab9782` -> **parameters** -> `operator`: Address which triggered the balance decrease (through sending or burning). -> `from`: *Holder* whose tokens were sent. -> `to`: Recipient of the tokens for a send (or `0x0` for a burn). -> `amount`: Number of tokens the *holder* balance is decreased by. -> `data`: Information provided by the *holder*. -> `operatorData`: Information provided by the *operator*. - -The following rules apply when calling the `tokensToSend` hook: - -- The `tokensToSend` hook MUST be called for every send and burn processes. - -- The `tokensToSend` hook MUST be called *before* the state is updated—i.e. *before* the balance is decremented. - -- `operator` MUST be the address which triggered the send or burn process. - -- `from` MUST be the address of the *holder* whose tokens are sent or burned. - -- `to` MUST be the address of the *recipient* which receives the tokens for a send. - -- `to` MUST be `0x0` for a burn. - -- `amount` MUST be the number of tokens the *holder* sent or burned. - -- `data` MUST contain the extra information (if any) provided to the send or the burn process. - -- `operatorData` MUST contain the extra information provided by the address - which triggered the decrease of the balance (if any). - -- The *holder* MAY block a send or burn process by `revert`ing. - (I.e., reject the withdrawal of tokens from its account.) - -*NOTE*: Multiple *holders* MAY use the same implementation of `ERC777TokensSender`. - -*NOTE*: An address can register at most one implementation at any given time for all [ERC-777] tokens. -Hence the `ERC777TokensSender` MUST expect to be called by different token contracts. -The `msg.sender` of the `tokensToSend` call is expected to be the address of the token contract. - -*[ERC-20] compatibility requirement*: -This hook takes precedence over [ERC-20] and MUST be called (if registered) -when calling [ERC-20]'s `transfer` and `transferFrom` event. -When called from a `transfer`, `operator` MUST be the same value as the `from`. -When called from a `transferFrom`, `operator` MUST be the address which issued the `transferFrom` call. - -#### **`ERC777TokensRecipient` And The `tokensReceived` Hook** - -The `tokensReceived` hook notifies of any increment of the balance (send and mint) for a given *recipient*. -Any address (regular or contract) wishing to be notified of token credits to their address -MAY register the address of a contract implementing the `ERC777TokensRecipient` interface described below via [ERC-1820]. - -> This is done by calling the `setInterfaceImplementer` function on the [ERC-1820] registry -> with the *recipient* address as the address, -> the `keccak256` hash of `ERC777TokensRecipient` -> (`0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b`) as the interface hash, -> and the address of the contract implementing the `ERC777TokensRecipient` as the implementer. - -``` solidity -interface ERC777TokensRecipient { - function tokensReceived( - address operator, - address from, - address to, - uint256 amount, - bytes calldata data, - bytes calldata operatorData - ) external; -} -``` - -If the *recipient* is a contract, which has not registered an `ERC777TokensRecipient` implementation; -then the token contract: - -- MUST `revert` if the `tokensReceived` hook is called from a mint or send call. - -- SHOULD continue processing the transaction - if the `tokensReceived` hook is called from an ERC20 `transfer` or `transferFrom` call. - -*NOTE*: A regular address MAY register a different address—the address of a contract—implementing -the interface on its behalf. -A contract MUST register either its address or the address of another contract -but said address MUST implement the interface on its behalf. - -**`tokensReceived`** - -``` solidity -function tokensReceived( - address operator, - address from, - address to, - uint256 amount, - bytes calldata data, - bytes calldata operatorData -) external -``` - -Notify a send or mint (if `from` is `0x0`) of `amount` tokens from the `from` address to the `to` address -by the `operator` address. - -*NOTE*: This function MUST NOT be called outside of a mint, send or [ERC-20] transfer process. - -> **identifier:** `0023de29` -> **parameters** -> `operator`: Address which triggered the balance increase (through sending or minting). -> `from`: *Holder* whose tokens were sent (or `0x0` for a mint). -> `to`: Recipient of the tokens. -> `amount`: Number of tokens the *recipient* balance is increased by. -> `data`: Information provided by the *holder*. -> `operatorData`: Information provided by the *operator*. - -The following rules apply when calling the `tokensReceived` hook: - -- The `tokensReceived` hook MUST be called for every send and mint processes. - -- The `tokensReceived` hook MUST be called *after* the state is updated—i.e. *after* the balance is incremented. - -- `operator` MUST be the address which triggered the send or mint process. - -- `from` MUST be the address of the *holder* whose tokens are sent for a send. - -- `from` MUST be `0x0` for a mint. - -- `to` MUST be the address of the *recipient* which receives the tokens. - -- `amount` MUST be the number of tokens the *recipient* sent or minted. - -- `data` MUST contain the extra information (if any) provided to the send or the mint process. - -- `operatorData` MUST contain the extra information provided by the address - which triggered the increase of the balance (if any). - -- The *holder* MAY block a send or mint process by `revert`ing. - (I.e., reject the reception of tokens.) - -*NOTE*: Multiple *holders* MAY use the same implementation of `ERC777TokensRecipient`. - -*NOTE*: An address can register at most one implementation at any given time for all [ERC-777] tokens. -Hence the `ERC777TokensRecipient` MUST expect to be called by different token contracts. -The `msg.sender` of the `tokensReceived` call is expected to be the address of the token contract. - -*[ERC-20] compatibility requirement*: -This hook takes precedence over [ERC-20] and MUST be called (if registered) -when calling [ERC-20]'s `transfer` and `transferFrom` event. -When called from a `transfer`, `operator` MUST be the same value as the `from`. -When called from a `transferFrom`, `operator` MUST be the address which issued the `transferFrom` call. - -#### **Note On Gas Consumption** - -Dapps and wallets SHOULD first estimate the gas required when sending, minting, or burning tokens—using -[`eth_estimateGas`][eth_estimateGas]—to avoid running out of gas during the transaction. - -### Logo - -| **Image** | ![beige logo] | ![white logo] | ![light grey logo] | ![dark grey logo] | ![black logo] | -|----------:|:-------------:|:-------------:|:------------------:|:-----------------:|:-------------:| -| **Color** | beige | white | light grey | dark grey | black | -| **Hex** | `#C99D66` | `#FFFFFF` | `#EBEFF0` | `#3C3C3D` | `#000000` | - -The logo MAY be used, modified and adapted to promote valid [ERC-777] token implementations -and [ERC-777] compliant technologies such as wallets and dapps. - -[ERC-777] token contract authors MAY create a specific logo for their token based on this logo. - -The logo MUST NOT be used to advertise, promote or associate in any way technology—such -as tokens—which is not [ERC-777] compliant. - -The logo for the standard can be found in the [`/assets/eip-777/logo`][logos] folder in `SVG` and `PNG` formats. -The `PNG` version of the logo offers a few sizes in pixels. -If needed, other sizes MAY be created by converting from `SVG` into `PNG`. - -## Rationale - -The principal intent for this standard is -to solve some of the shortcomings of [ERC-20] while maintaining backward compatibility with [ERC-20], -and avoiding the problems and vulnerabilities of [EIP-223]. - -Below are the rationales for the decisions regarding the main aspects of the standards. - -*NOTE*: Jacques Dafflon ([0xjac]), one of the authors of the standard, -conjointly wrote his [master thesis] on the standard, -which goes in more details than could reasonably fit directly within the standard, -and can provide further clarifications regarding certain aspects or decisions. - -### Lifecycle - -More than just sending tokens, [ERC-777] defines the entire lifecycle of a token, -starting with the minting process, followed by the sending process and terminating with the burn process. - -Having a lifecycle clearly defined is important for consistency and accuracy, -especially when value is derived from scarcity. -In contrast when looking at some [ERC-20] tokens, a discrepancy can be observed -between the value returned by the `totalSupply` and the actual circulating supply, -as the standard does not clearly define a process to create and destroy tokens. - -### Data - -The mint, send and burn processes can all make use of a `data` and `operatorData` fields -which are passed to any movement (mint, send or burn). -Those fields may be empty for simple use cases, -or they may contain valuable information related to the movement of tokens, -similar to information attached to a bank transfer by the sender or the bank itself. - -The use of a `data` field is equally present in other standard proposals such as [EIP-223], -and was requested by multiple members of the community who reviewed this standard. - -### Hooks - -In most cases, [ERC-20] requires two calls to safely transfer tokens to a contract without locking them. -A call from the sender, using the `approve` function -and a call from the recipient using `transferFrom`. -Furthermore, this requires extra communication between the parties which is not clearly defined. -Finally, holders can get confused between `transfer` and `approve`/`transferFrom`. -Using the former to transfer tokens to a contract will most likely result in locked tokens. - -Hooks allow streamlining of the sending process and offer a single way to send tokens to any recipient. -Thanks to the `tokensReceived` hook, contracts are able to react and prevent locking tokens upon reception. - -#### **Greater Control For Holders** - -The `tokensReceived` hook also allows holders to reject the reception of some tokens. -This gives greater control to holders who can accept or reject incoming tokens based on some parameters, -for example located in the `data` or `operatorData` fields. - -Following the same intentions and based on suggestions from the community, -the `tokensToSend` hook was added to give control over and prevent the movement of outgoing tokens. - -#### **[ERC-1820] Registry** - -The [ERC-1820] Registry allows holders to register their hooks. -Other alternatives were examined beforehand to link hooks and holders. - -The first was for hooks to be defined at the sender's or recipient's address. -This approach is similar to [EIP-223] which proposes a `tokenFallback` function on recipient contracts -to be called when receiving tokens, -but improves on it by relying on [ERC-165] for interface detection. -While straightforward to implement, this approach imposes several limitations. -In particular, the sender and recipient must be contracts in order to provide their implementation of the hooks. -Preventing externally owned addresses to benefit from hooks. -Existing contracts have a strong probability not to be compatible, -as they undoubtedly were unaware and do not define the new hooks. -Consequently existing smart contract infrastructure such as multisig wallets -which potentially hold large amounts of ether and tokens would need to be migrated to new updated contracts. - -The second approach considered was to use [ERC-672] which offered pseudo-introspection for addresses using reverse-ENS. -However, this approach relied heavily on ENS, on top of which reverse lookup would need to be implemented. -Analysis of this approach promptly revealed a certain degree of complexity and security concerns -which would transcend the benefits of approach. - -The third solution—used in this standard—is to rely on a unique registry -where any address can register the addresses of contracts implementing the hooks on its behalf. -This approach has the advantage that externally owned accounts and contracts can benefit from hooks, -including existing contracts which can rely on hooks deployed on proxy contracts. - -The decision was made to keep this registry in a separate EIP, -as to not over complicate this standard. -More importantly, the registry is designed in a flexible fashion, -such that other EIPs and smart contract infrastructures can benefit from it -for their own use cases, outside the realm of [ERC-777] and tokens. -The first proposal for this registry was [ERC-820]. -Unfortunately, issues emanating from upgrades in the Solidity language to versions 0.5 and above -resulted in a bug in a separated part of the registry, which required changes. -This was discovered right after the last call period. -Attempts made to avoid creating a separate EIP, such as [ERC820a], were rejected. -Hence the standard for the registry used for [ERC-777] became [ERC-1820]. -[ERC-1820] and [ERC-820] are functionally equivalent. [ERC-1820] simply contains the fix for newer versions of Solidity. - -### Operators - -The standard defines the concept of operators as any address which moves tokens. -While intuitively every address moves its own tokens, -separating the concepts of holder and operator allows for greater flexibility. -Primarily, this originates from the fact that the standard defines a mechanism for holders -to let other addresses become their operators. -Moreover, unlike the approve calls in [ERC-20] where the role of an approved address is not clearly defined, -[ERC-777] details the intent of and interactions with operators, -including an obligation for operators to be approved, -and an irrevocable right for any holder to revoke operators. - -#### **Default Operators** - -Default operators were added based on community demand for pre-approved operators. -That is operators which are approved for all holders by default. -For obvious security reasons, the list of default operators is defined at the token contract creation time, -and cannot be changed. -Any holder still has the right to revoke default operators. -One of the obvious advantages of default operators is to allow ether-less movements of tokens. -Default operators offer other usability advantages, -such as allowing token providers to offer functionality in a modular way, -and to reduce the complexity for holders to use features provided through operators. - -## Backward Compatibility - -This EIP does not introduce backward incompatibilities and is backward compatible with the older [ERC-20] token standard. - -This EIP does not use `transfer` and `transferFrom` and uses `send` and `operatorSend` -to avoid confusion and mistakes when deciphering which token standard is being used. - -This standard allows the implementation of [ERC-20] functions `transfer`, `transferFrom`, `approve` and `allowance` -alongside to make a token fully compatible with [ERC-20]. - -The token MAY implement `decimals()` for backward compatibility with [ERC-20]. -If implemented, it MUST always return `18`. - -Therefore a token contract MAY implement both [ERC-20] and [ERC-777] in parallel. -The specification of the `view` functions (such as `name`, `symbol`, `balanceOf`, `totalSupply`) and internal data -(such as the mapping of balances) overlap without problems. -Note however that the following functions are mandatory in [ERC-777] and MUST be implemented: -`name`, `symbol` `balanceOf` and `totalSupply` -(`decimals` is not part of the [ERC-777] standard). - -The state-modifying functions from both standards are decoupled and can operate independently from each other. -Note that [ERC-20] functions SHOULD be limited to only being called from old contracts. - -If the token implements [ERC-20], -it MUST register the `ERC20Token` interface with its own address via [ERC-1820]. -This is done by calling the `setInterfaceImplementer` function on the ERC1820 registry -with the token contract address as both the address and the implementer -and the `keccak256` hash of `ERC20Token` (`0xaea199e31a596269b42cdafd93407f14436db6e4cad65417994c2eb37381e05a`) -as the interface hash. - -If the contract has a switch to enable or disable ERC20 functions, every time the switch is triggered, -the token MUST register or unregister the `ERC20Token` interface for its own address accordingly via ERC1820. -Unregistering implies calling the `setInterfaceImplementer` with the token contract address as the address, -the `keccak256` hash of `ERC20Token` as the interface hash and `0x0` as the implementer. -(See [Set An Interface For An Address][erc1820-set] in [ERC-1820] for more details.) - -The difference for new contracts implementing [ERC-20] is that -`tokensToSend` and `tokensReceived` hooks take precedence over [ERC-20]. -Even with an [ERC-20] `transfer` and `transferFrom` call, the token contract MUST check via [ERC-1820] -if the `from` and the `to` address implement `tokensToSend` and `tokensReceived` hook respectively. -If any hook is implemented, it MUST be called. -Note that when calling [ERC-20] `transfer` on a contract, if the contract does not implement `tokensReceived`, -the `transfer` call SHOULD still be accepted even if this means the tokens will probably be locked. - -The table below summarizes the different actions the token contract MUST take -when sending, minting and transferring token via [ERC-777] and [ERC-20]: - - - - - - - - - - - - - - - - - - - - - - - - - - -
ERC1820to addressERC777 Sending And MintingERC20 transfer/transferFrom
- ERC777TokensRecipient
registered -
regular address - MUST call tokensReceived -
contract
- ERC777TokensRecipient
not registered -
regular addresscontinue
contractMUST revertSHOULD continue1
- -> 1. -> The transaction SHOULD continue for clarity as ERC20 is not aware of hooks. -> However, this can result in accidentally locked tokens. -> If avoiding accidentally locked tokens is paramount, the transaction MAY revert. - - -There is no particular action to take if `tokensToSend` is not implemented. -The movement MUST proceed and only be canceled if another condition is not respected -such as lack of funds or a `revert` in `tokensReceived` (if present). - -During a send, mint and burn, the respective `Sent`, `Minted` and `Burned` events MUST be emitted. -Furthermore, if the token contract declares that it implements `ERC20Token` via [ERC-1820], -the token contract SHOULD emit a `Transfer` event for minting and burning -and MUST emit a `Transfer` event for sending (as specified in the [ERC-20] standard). -During an [ERC-20]'s `transfer` or `transferFrom` functions, a valid `Sent` event MUST be emitted. - -Hence for any movement of tokens, two events MAY be emitted: -an [ERC-20] `Transfer` and an [ERC-777] `Sent`, `Minted` or `Burned` (depending on the type of movement). -Third-party developers MUST be careful not to consider both events as separate movements. -As a general rule, if an application considers the token as an ERC20 token, -then only the `Transfer` event MUST be taken into account. -If the application considers the token as an ERC777 token, -then only the `Sent`, `Minted` and `Burned` events MUST be considered. - -## Test Cases - -The [repository with the reference implementation][0xjac/ERC777] contains all the [tests][ref tests]. - -## Implementation - -The GitHub repository [0xjac/ERC777] contains the [reference implementation]. -The reference implementation is also available via [npm][npm/erc777] and can be installed with `npm install erc777`. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - -[operators]: #operators - -[ERC-20]: ./eip-20.md -[ERC-165]: ./eip-165.md -[ERC-672]: https://github.com/ethereum/EIPs/issues/672 -[ERC-777]: ./eip-777.md -[ERC-820]: ./eip-820.md -[ERC820a]: https://github.com/ethereum/EIPs/pull/1758 -[ERC-1820]: ./eip-1820.md -[erc1820-set]: ./eip-1820.md#set-an-interface-for-an-address -[0xjac]: https://github.com/0xjac -[0xjac/ERC777]: https://github.com/0xjac/ERC777 -[master thesis]: https://github.com/0xjac/master-thesis -[npm/erc777]: https://www.npmjs.com/package/erc777 -[ref tests]: https://github.com/0xjac/ERC777/blob/master/test/ReferenceToken.test.js -[reference implementation]: https://github.com/0xjac/ERC777/blob/master/contracts/examples/ReferenceToken.sol -[EIP-223]: https://github.com/ethereum/EIPs/issues/223 -[eth_estimateGas]: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_estimategas - -[authorizedoperator]: #authorizedoperator -[revokedoperator]: #revokedoperator -[isOperatorFor]: #isOperatorFor -[defaultOperators]: #defaultOperators -[sent]: #sent -[minted]: #minted -[burned]: #burned - -[logos]: https://github.com/ethereum/EIPs/tree/master/assets/eip-777/logo -[beige logo]: ../assets/eip-777/logo/png/ERC-777-logo-beige-48px.png -[white logo]: ../assets/eip-777/logo/png/ERC-777-logo-white-48px.png -[light grey logo]: ../assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png -[dark grey logo]: ../assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png -[black logo]: ../assets/eip-777/logo/png/ERC-777-logo-black-48px.png +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-777.md diff --git a/EIPS/eip-801.md b/EIPS/eip-801.md index d0e979b27a9fc4..8d8f2108f46044 100644 --- a/EIPS/eip-801.md +++ b/EIPS/eip-801.md @@ -1,78 +1 @@ ---- -eip: 801 -title: Canary Standard -author: ligi -type: Standards Track -category: ERC -status: Stagnant -created: 2017-12-16 ---- - -## Simple Summary - -A standard interface for canary contracts. - -## Abstract - -The following standard allows the implementation of canaries within contracts. -This standard provides basic functionality to check if a canary is alive, keeping the canary alive and optionally manage feeders. - -## Motivation - -The canary can e.g. be used as a [warrant canary](https://en.wikipedia.org/wiki/Warrant_canary). -A standard interface allows other applications to easily interface with canaries on Ethereum - e.g. for visualizing the state, automated alarms, applications to feed the canary or contracts (e.g. insurance) that use the state. - -## Specification - -### Methods - -#### isAlive() - -Returns if the canary was fed properly to signal e.g. that no warrant was received. - -``` js -function isAlive() constant returns (bool alive) -``` - -#### getBlockOfDeath() - -Returns the block the canary died. -Throws if the canary is alive. - -``` js -function getBlockOfDeath() constant returns (uint256 block) -``` - -#### getType() - -Returns the type of the canary: - -* `1` = Simple (just the pure interface as defined in this ERC) -* `2` = Single feeder (as defined in ERC-TBD) -* `3` = Single feeder with bad food (as defined in ERC-TBD) -* `4` = Multiple feeders (as defined in ERC-TBD) -* `5` = Multiple mandatory feeders (as defined in ERC-TBD) -* `6` = IOT (as defined in ERC-TBD) - -`1` might also be used for a special purpose contract that does not need a special type but still wants to expose the functions and provide events as defined in this ERC. - -``` js -function getType() constant returns (uint8 type) -``` - -### Events - -#### RIP - -MUST trigger when the contract is called the first time after the canary died. - -``` js -event RIP() -``` - -## Implementation - -TODO - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-801.md diff --git a/EIPS/eip-820.md b/EIPS/eip-820.md index fdbafe7fb6b3b2..0204ffe99de486 100644 --- a/EIPS/eip-820.md +++ b/EIPS/eip-820.md @@ -1,904 +1 @@ ---- -eip: 820 -title: Pseudo-introspection Registry Contract -author: Jordi Baylina , Jacques Dafflon -discussions-to: https://github.com/ethereum/EIPs/issues/820 -status: Final -type: Standards Track -category: ERC -requires: 165, 214 -created: 2018-01-05 ---- - -> :information_source: **[ERC-1820] has superseded [ERC-820].** :information_source: -> [ERC-1820] fixes the incompatibility in the [ERC-165] logic which was introduced by the Solidty 0.5 update. -> Have a look at the [official announcement][erc1820-annoucement], and the comments about the [bug][erc820-bug] and the [fix][erc820-fix]. -> Apart from this fix, [ERC-1820] is functionally equivalent to [ERC-820]. -> -> :warning: [ERC-1820] MUST be used in lieu of [ERC-820]. :warning: - - -## Simple Summary - -This standard defines a universal registry smart contract where any address (contract or regular account) can register which interface it supports and which smart contract is responsible for its implementation. - -This standard keeps backward compatibility with [ERC-165]. - -## Abstract - -This standard defines a registry where smart contracts and regular accounts can publish which functionalities they implement---either directly or through a proxy contract. - -Anyone can query this registry to ask if a specific address implements a given interface and which smart contract handles its implementation. - -This registry MAY be deployed on any chain and shares the same address on all chains. - -Interfaces with zeroes (`0`) as the last 28 bytes are considered [ERC-165] interfaces, and this registry SHALL forward the call to the contract to see if it implements the interface. - -This contract also acts as an [ERC-165] cache to reduce gas consumption. - -## Motivation - -There have been different approaches to define pseudo-introspection in Ethereum. The first is [ERC-165] which has the limitation that it cannot be used by regular accounts. The second attempt is [ERC-672] which uses reverse [ENS]. Using reverse [ENS] has two issues. First, it is unnecessarily complicated, and second, [ENS] is still a centralized contract controlled by a multisig. This multisig theoretically would be able to modify the system. - -This standard is much simpler than [ERC-672], and it is *fully* decentralized. - -This standard also provides a *unique* address for all chains. Thus solving the problem of resolving the correct registry address for different chains. - -## Specification - -### [ERC-820] Registry Smart Contract - -> This is an exact copy of the code of the [ERC820 registry smart contract]. - -``` solidity -/* ERC820 Pseudo-introspection Registry Contract - * This standard defines a universal registry smart contract where any address - * (contract or regular account) can register which interface it supports and - * which smart contract is responsible for its implementation. - * - * Written in 2018 by Jordi Baylina and Jacques Dafflon - * - * To the extent possible under law, the author(s) have dedicated all copyright - * and related and neighboring rights to this software to the public domain - * worldwide. This software is distributed without any warranty. - * - * You should have received a copy of the CC0 Public Domain Dedication along - * with this software. If not, see - * . - * - * ███████╗██████╗ ██████╗ █████╗ ██████╗ ██████╗ - * ██╔════╝██╔══██╗██╔════╝██╔══██╗╚════██╗██╔═████╗ - * █████╗ ██████╔╝██║ ╚█████╔╝ █████╔╝██║██╔██║ - * ██╔══╝ ██╔══██╗██║ ██╔══██╗██╔═══╝ ████╔╝██║ - * ███████╗██║ ██║╚██████╗╚█████╔╝███████╗╚██████╔╝ - * ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚════╝ ╚══════╝ ╚═════╝ - * - * ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗ - * ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝ - * ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝ - * ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝ - * ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║ - * ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ - * - */ -pragma solidity 0.4.24; -// IV is value needed to have a vanity address starting with `0x820`. -// IV: 9513 - -/// @dev The interface a contract MUST implement if it is the implementer of -/// some (other) interface for any address other than itself. -interface ERC820ImplementerInterface { - /// @notice Indicates whether the contract implements the interface `interfaceHash` for the address `addr` or not. - /// @param interfaceHash keccak256 hash of the name of the interface - /// @param addr Address for which the contract will implement the interface - /// @return ERC820_ACCEPT_MAGIC only if the contract implements `interfaceHash` for the address `addr`. - function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32); -} - - -/// @title ERC820 Pseudo-introspection Registry Contract -/// @author Jordi Baylina and Jacques Dafflon -/// @notice This contract is the official implementation of the ERC820 Registry. -/// @notice For more details, see https://eips.ethereum.org/EIPS/eip-820 -contract ERC820Registry { - /// @notice ERC165 Invalid ID. - bytes4 constant INVALID_ID = 0xffffffff; - /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`). - bytes4 constant ERC165ID = 0x01ffc9a7; - /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address. - bytes32 constant ERC820_ACCEPT_MAGIC = keccak256(abi.encodePacked("ERC820_ACCEPT_MAGIC")); - - mapping (address => mapping(bytes32 => address)) interfaces; - mapping (address => address) managers; - mapping (address => mapping(bytes4 => bool)) erc165Cached; - - /// @notice Indicates a contract is the `implementer` of `interfaceHash` for `addr`. - event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer); - /// @notice Indicates `newManager` is the address of the new manager for `addr`. - event ManagerChanged(address indexed addr, address indexed newManager); - - /// @notice Query if an address implements an interface and through which contract. - /// @param _addr Address being queried for the implementer of an interface. - /// (If `_addr == 0` then `msg.sender` is assumed.) - /// @param _interfaceHash keccak256 hash of the name of the interface as a string. - /// E.g., `web3.utils.keccak256('ERC777Token')`. - /// @return The address of the contract which implements the interface `_interfaceHash` for `_addr` - /// or `0x0` if `_addr` did not register an implementer for this interface. - function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) { - address addr = _addr == 0 ? msg.sender : _addr; - if (isERC165Interface(_interfaceHash)) { - bytes4 erc165InterfaceHash = bytes4(_interfaceHash); - return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : 0; - } - return interfaces[addr][_interfaceHash]; - } - - /// @notice Sets the contract which implements a specific interface for an address. - /// Only the manager defined for that address can set it. - /// (Each address is the manager for itself until it sets a new manager.) - /// @param _addr Address to define the interface for. (If `_addr == 0` then `msg.sender` is assumed.) - /// @param _interfaceHash keccak256 hash of the name of the interface as a string. - /// For example, `web3.utils.keccak256('ERC777TokensRecipient')` for the `ERC777TokensRecipient` interface. - /// @param _implementer Contract address implementing _interfaceHash for _addr. - function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external { - address addr = _addr == 0 ? msg.sender : _addr; - require(getManager(addr) == msg.sender, "Not the manager"); - - require(!isERC165Interface(_interfaceHash), "Must not be a ERC165 hash"); - if (_implementer != 0 && _implementer != msg.sender) { - require( - ERC820ImplementerInterface(_implementer) - .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC820_ACCEPT_MAGIC, - "Does not implement the interface" - ); - } - interfaces[addr][_interfaceHash] = _implementer; - emit InterfaceImplementerSet(addr, _interfaceHash, _implementer); - } - - /// @notice Sets the `_newManager` as manager for the `_addr` address. - /// The new manager will be able to call `setInterfaceImplementer` for `_addr`. - /// @param _addr Address for which to set the new manager. - /// @param _newManager Address of the new manager for `addr`. - function setManager(address _addr, address _newManager) external { - require(getManager(_addr) == msg.sender, "Not the manager"); - managers[_addr] = _newManager == _addr ? 0 : _newManager; - emit ManagerChanged(_addr, _newManager); - } - - /// @notice Get the manager of an address. - /// @param _addr Address for which to return the manager. - /// @return Address of the manager for a given address. - function getManager(address _addr) public view returns(address) { - // By default the manager of an address is the same address - if (managers[_addr] == 0) { - return _addr; - } else { - return managers[_addr]; - } - } - - /// @notice Compute the keccak256 hash of an interface given its name. - /// @param _interfaceName Name of the interface. - /// @return The keccak256 hash of an interface name. - function interfaceHash(string _interfaceName) external pure returns(bytes32) { - return keccak256(abi.encodePacked(_interfaceName)); - } - - /* --- ERC165 Related Functions --- */ - /* --- Developed in collaboration with William Entriken. --- */ - - /// @notice Updates the cache with whether the contract implements an ERC165 interface or not. - /// @param _contract Address of the contract for which to update the cache. - /// @param _interfaceId ERC165 interface for which to update the cache. - function updateERC165Cache(address _contract, bytes4 _interfaceId) external { - interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(_contract, _interfaceId) ? _contract : 0; - erc165Cached[_contract][_interfaceId] = true; - } - - /// @notice Checks whether a contract implements an ERC165 interface or not. - /// The result may be cached, if not a direct lookup is performed. - /// @param _contract Address of the contract to check. - /// @param _interfaceId ERC165 interface to check. - /// @return `true` if `_contract` implements `_interfaceId`, false otherwise. - function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) { - if (!erc165Cached[_contract][_interfaceId]) { - return implementsERC165InterfaceNoCache(_contract, _interfaceId); - } - return interfaces[_contract][_interfaceId] == _contract; - } - - /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache. - /// @param _contract Address of the contract to check. - /// @param _interfaceId ERC165 interface to check. - /// @return `true` if `_contract` implements `_interfaceId`, false otherwise. - function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) { - uint256 success; - uint256 result; - - (success, result) = noThrowCall(_contract, ERC165ID); - if (success == 0 || result == 0) { - return false; - } - - (success, result) = noThrowCall(_contract, INVALID_ID); - if (success == 0 || result != 0) { - return false; - } - - (success, result) = noThrowCall(_contract, _interfaceId); - if (success == 1 && result == 1) { - return true; - } - return false; - } - - /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not. - /// @param _interfaceHash The hash to check. - /// @return `true` if the hash is a ERC165 interface (ending with 28 zeroes), `false` otherwise. - function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) { - return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0; - } - - /// @dev Make a call on a contract without throwing if the function does not exist. - function noThrowCall(address _contract, bytes4 _interfaceId) - internal view returns (uint256 success, uint256 result) - { - bytes4 erc165ID = ERC165ID; - - assembly { - let x := mload(0x40) // Find empty storage location using "free memory pointer" - mstore(x, erc165ID) // Place signature at beginning of empty storage - mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature - - success := staticcall( - 30000, // 30k gas - _contract, // To addr - x, // Inputs are stored at location x - 0x08, // Inputs are 8 bytes long - x, // Store output over input (saves space) - 0x20 // Outputs are 32 bytes long - ) - - result := mload(x) // Load the result - } - } -} - -``` - -### Deployment Transaction - -Below is the raw transaction which MUST be used to deploy the smart contract on any chain. - -``` -0xf90a2a8085174876e800830c35008080b909d7608060405234801561001057600080fd5b506109b7806100206000396000f30060806040526004361061008d5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166329965a1d81146100925780633d584063146100bf5780635df8122f146100fc57806365ba36c114610123578063a41e7d5114610155578063aabbb8ca14610183578063b7056765146101a7578063f712f3e8146101e9575b600080fd5b34801561009e57600080fd5b506100bd600160a060020a036004358116906024359060443516610217565b005b3480156100cb57600080fd5b506100e0600160a060020a0360043516610512565b60408051600160a060020a039092168252519081900360200190f35b34801561010857600080fd5b506100bd600160a060020a036004358116906024351661055e565b34801561012f57600080fd5b506101436004803560248101910135610655565b60408051918252519081900360200190f35b34801561016157600080fd5b506100bd600160a060020a0360043516600160e060020a0319602435166106e3565b34801561018f57600080fd5b506100e0600160a060020a036004351660243561076d565b3480156101b357600080fd5b506101d5600160a060020a0360043516600160e060020a0319602435166107e7565b604080519115158252519081900360200190f35b3480156101f557600080fd5b506101d5600160a060020a0360043516600160e060020a03196024351661089c565b6000600160a060020a0384161561022e5783610230565b335b90503361023c82610512565b600160a060020a03161461029a576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b6102a38361091c565b156102f8576040805160e560020a62461bcd02815260206004820152601960248201527f4d757374206e6f74206265206120455243313635206861736800000000000000604482015290519081900360640190fd5b600160a060020a038216158015906103195750600160a060020a0382163314155b156104a15760405160200180807f4552433832305f4143434550545f4d414749430000000000000000000000000081525060130190506040516020818303038152906040526040518082805190602001908083835b6020831061038d5780518252601f19909201916020918201910161036e565b51815160209384036101000a6000190180199092169116179052604080519290940182900382207f249cb3fa000000000000000000000000000000000000000000000000000000008352600483018a9052600160a060020a0388811660248501529451909650938816945063249cb3fa936044808401945091929091908290030181600087803b15801561042057600080fd5b505af1158015610434573d6000803e3d6000fd5b505050506040513d602081101561044a57600080fd5b5051146104a1576040805160e560020a62461bcd02815260206004820181905260248201527f446f6573206e6f7420696d706c656d656e742074686520696e74657266616365604482015290519081900360640190fd5b600160a060020a03818116600081815260208181526040808320888452909152808220805473ffffffffffffffffffffffffffffffffffffffff19169487169485179055518692917f93baa6efbd2244243bfee6ce4cfdd1d04fc4c0e9a786abd3a41313bd352db15391a450505050565b600160a060020a03808216600090815260016020526040812054909116151561053c575080610559565b50600160a060020a03808216600090815260016020526040902054165b919050565b3361056883610512565b600160a060020a0316146105c6576040805160e560020a62461bcd02815260206004820152600f60248201527f4e6f7420746865206d616e616765720000000000000000000000000000000000604482015290519081900360640190fd5b81600160a060020a031681600160a060020a0316146105e557806105e8565b60005b600160a060020a03838116600081815260016020526040808220805473ffffffffffffffffffffffffffffffffffffffff19169585169590951790945592519184169290917f605c2dbf762e5f7d60a546d42e7205dcb1b011ebc62a61736a57c9089d3a43509190a35050565b60008282604051602001808383808284378201915050925050506040516020818303038152906040526040518082805190602001908083835b602083106106ad5780518252601f19909201916020918201910161068e565b6001836020036101000a038019825116818451168082178552505050505050905001915050604051809103902090505b92915050565b6106ed82826107e7565b6106f85760006106fa565b815b600160a060020a03928316600081815260208181526040808320600160e060020a031996909616808452958252808320805473ffffffffffffffffffffffffffffffffffffffff19169590971694909417909555908152600284528181209281529190925220805460ff19166001179055565b60008080600160a060020a038516156107865784610788565b335b91506107938461091c565b156107b85750826107a4828261089c565b6107af5760006107b1565b815b92506107df565b600160a060020a038083166000908152602081815260408083208884529091529020541692505b505092915050565b60008080610815857f01ffc9a70000000000000000000000000000000000000000000000000000000061093e565b9092509050811580610825575080155b1561083357600092506107df565b61084585600160e060020a031961093e565b909250905081158061085657508015155b1561086457600092506107df565b61086e858561093e565b90925090506001821480156108835750806001145b1561089157600192506107df565b506000949350505050565b600160a060020a0382166000908152600260209081526040808320600160e060020a03198516845290915281205460ff1615156108e4576108dd83836107e7565b90506106dd565b50600160a060020a03808316600081815260208181526040808320600160e060020a0319871684529091529020549091161492915050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff161590565b6040517f01ffc9a7000000000000000000000000000000000000000000000000000000008082526004820183905260009182919060208160088189617530fa9051909690955093505050505600a165627a7a723058204fc4461c9d5a247b0eafe0f9c508057bc0ad72bc24668cb2a35ea65850e10d3100291ba08208208208208208208208208208208208208208208208208208208208208200a00820820820820820820820820820820820820820820820820820820820820820 -``` - -The strings of `820`'s at the end of the transaction are the `r` and `s` of the signature. From this deterministic pattern (generated by a human), anyone can deduce that no one knows the private key for the deployment account. - -### Deployment Method - -This contract is going to be deployed using the keyless deployment method---also known as [Nick]'s method---which relies on a single-use address. (See [Nick's article] for more details). This method works as follows: - -1. Generate a transaction which deploys the contract from a new random account. - - This transaction MUST NOT use [EIP-155] in order to work on any chain. - - This transaction MUST have a relatively high gas price to be deployed on any chain. In this case, it is going to be 100 Gwei. - -2. Set the `v`, `r`, `s` of the transaction signature to the following values: - - ``` - v: 27 - r: 0x8208208208208208208208208208208208208208208208208208208208208200 - s: 0x0820820820820820820820820820820820820820820820820820820820820820 - ``` - - Those `r` and `s` values---made of a repeating pattern of `820`'s---are predictable "random numbers" generated deterministically by a human. - - > The values of `r` and `s` must be 32 bytes long each---or 64 characters in hexadecimal. Since `820` is 3 characters long and 3 is not a divisor of 64, but it is a divisor of 63, the `r` and `s` values are padded with one extra character. - > The `s` value is prefixed with a single zero (`0`). The `0` prefix also guarantees that `s < secp256k1n ÷ 2 + 1`. - > The `r` value, cannot be prefixed with a zero, as the transaction becomes invalid. Instead it is suffixed with a zero (`0`) which still respects the condition `s < secp256k1n`. - -3. We recover the sender of this transaction, i.e., the single-use deployment account. - - > Thus we obtain an account that can broadcast that transaction, but we also have the warranty that nobody knows the private key of that account. - -4. Send exactly 0.08 ethers to this single-use deployment account. - -5. Broadcast the deployment transaction. - -This operation can be done on any chain, guaranteeing that the contract address is always the same and nobody can use that address with a different contract. - - -### Single-use Registry Deployment Account - -``` -0xE6C244a1C10Aa0085b0cf92f04cdaD947C2988b8 -``` - -This account is generated by reverse engineering it from its signature for the transaction. This way no one knows the private key, but it is known that it is the valid signer of the deployment transaction. - -> To deploy the registry, 0.08 ethers MUST be sent to this account *first*. - -### Registry Contract Address - -``` -0x820b586C8C28125366C998641B09DCbE7d4cBF06 -``` - -The contract has the address above for every chain on which it is deployed. - -
-Raw metadata of ./contracts/ERC820Registry.sol - -```json -{ - "compiler": { - "version": "0.4.24+commit.e67f0147" - }, - "language": "Solidity", - "output": { - "abi": [ - { - "constant": false, - "inputs": [ - { - "name": "_addr", - "type": "address" - }, - { - "name": "_interfaceHash", - "type": "bytes32" - }, - { - "name": "_implementer", - "type": "address" - } - ], - "name": "setInterfaceImplementer", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_addr", - "type": "address" - } - ], - "name": "getManager", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_addr", - "type": "address" - }, - { - "name": "_newManager", - "type": "address" - } - ], - "name": "setManager", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_interfaceName", - "type": "string" - } - ], - "name": "interfaceHash", - "outputs": [ - { - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_contract", - "type": "address" - }, - { - "name": "_interfaceId", - "type": "bytes4" - } - ], - "name": "updateERC165Cache", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_addr", - "type": "address" - }, - { - "name": "_interfaceHash", - "type": "bytes32" - } - ], - "name": "getInterfaceImplementer", - "outputs": [ - { - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_contract", - "type": "address" - }, - { - "name": "_interfaceId", - "type": "bytes4" - } - ], - "name": "implementsERC165InterfaceNoCache", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_contract", - "type": "address" - }, - { - "name": "_interfaceId", - "type": "bytes4" - } - ], - "name": "implementsERC165Interface", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "addr", - "type": "address" - }, - { - "indexed": true, - "name": "interfaceHash", - "type": "bytes32" - }, - { - "indexed": true, - "name": "implementer", - "type": "address" - } - ], - "name": "InterfaceImplementerSet", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "addr", - "type": "address" - }, - { - "indexed": true, - "name": "newManager", - "type": "address" - } - ], - "name": "ManagerChanged", - "type": "event" - } - ], - "devdoc": { - "author": "Jordi Baylina and Jacques Dafflon", - "methods": { - "getInterfaceImplementer(address,bytes32)": { - "params": { - "_addr": "Address being queried for the implementer of an interface. (If `_addr == 0` then `msg.sender` is assumed.)", - "_interfaceHash": "keccak256 hash of the name of the interface as a string. E.g., `web3.utils.keccak256('ERC777Token')`." - }, - "return": "The address of the contract which implements the interface `_interfaceHash` for `_addr` or `0x0` if `_addr` did not register an implementer for this interface." - }, - "getManager(address)": { - "params": { - "_addr": "Address for which to return the manager." - }, - "return": "Address of the manager for a given address." - }, - "implementsERC165Interface(address,bytes4)": { - "params": { - "_contract": "Address of the contract to check.", - "_interfaceId": "ERC165 interface to check." - }, - "return": "`true` if `_contract` implements `_interfaceId`, false otherwise." - }, - "implementsERC165InterfaceNoCache(address,bytes4)": { - "params": { - "_contract": "Address of the contract to check.", - "_interfaceId": "ERC165 interface to check." - }, - "return": "`true` if `_contract` implements `_interfaceId`, false otherwise." - }, - "interfaceHash(string)": { - "params": { - "_interfaceName": "Name of the interface." - }, - "return": "The keccak256 hash of an interface name." - }, - "setInterfaceImplementer(address,bytes32,address)": { - "params": { - "_addr": "Address to define the interface for. (If `_addr == 0` then `msg.sender` is assumed.)", - "_implementer": "Contract address implementing _interfaceHash for _addr.", - "_interfaceHash": "keccak256 hash of the name of the interface as a string. For example, `web3.utils.keccak256('ERC777TokensRecipient')` for the `ERC777TokensRecipient` interface." - } - }, - "setManager(address,address)": { - "params": { - "_addr": "Address for which to set the new manager.", - "_newManager": "Address of the new manager for `addr`." - } - }, - "updateERC165Cache(address,bytes4)": { - "params": { - "_contract": "Address of the contract for which to update the cache.", - "_interfaceId": "ERC165 interface for which to update the cache." - } - } - }, - "title": "ERC820 Pseudo-introspection Registry Contract" - }, - "userdoc": { - "methods": { - "getInterfaceImplementer(address,bytes32)": { - "notice": "Query if an address implements an interface and through which contract." - }, - "getManager(address)": { - "notice": "Get the manager of an address." - }, - "implementsERC165Interface(address,bytes4)": { - "notice": "Checks whether a contract implements an ERC165 interface or not. The result may be cached, if not a direct lookup is performed." - }, - "implementsERC165InterfaceNoCache(address,bytes4)": { - "notice": "Checks whether a contract implements an ERC165 interface or not without using nor updating the cache." - }, - "interfaceHash(string)": { - "notice": "Compute the keccak256 hash of an interface given its name." - }, - "setInterfaceImplementer(address,bytes32,address)": { - "notice": "Sets the contract which implements a specific interface for an address. Only the manager defined for that address can set it. (Each address is the manager for itself until it sets a new manager.)" - }, - "setManager(address,address)": { - "notice": "Sets the `_newManager` as manager for the `_addr` address. The new manager will be able to call `setInterfaceImplementer` for `_addr`." - }, - "updateERC165Cache(address,bytes4)": { - "notice": "Updates the cache with whether the contract implements an ERC165 interface or not." - } - } - } - }, - "settings": { - "compilationTarget": { - "./contracts/ERC820Registry.sol": "ERC820Registry" - }, - "evmVersion": "byzantium", - "libraries": {}, - "optimizer": { - "enabled": true, - "runs": 200 - }, - "remappings": [] - }, - "sources": { - "./contracts/ERC820Registry.sol": { - "content": "/* ERC820 Pseudo-introspection Registry Contract\n * This standard defines a universal registry smart contract where any address\n * (contract or regular account) can register which interface it supports and\n * which smart contract is responsible for its implementation.\n *\n * Written in 2018 by Jordi Baylina and Jacques Dafflon\n *\n * To the extent possible under law, the author(s) have dedicated all copyright\n * and related and neighboring rights to this software to the public domain\n * worldwide. This software is distributed without any warranty.\n *\n * You should have received a copy of the CC0 Public Domain Dedication along\n * with this software. If not, see\n * .\n *\n * ███████╗██████╗ ██████╗ █████╗ ██████╗ ██████╗\n * ██╔════╝██╔══██╗██╔════╝██╔══██╗╚════██╗██╔═████╗\n * █████╗ ██████╔╝██║ ╚█████╔╝ █████╔╝██║██╔██║\n * ██╔══╝ ██╔══██╗██║ ██╔══██╗██╔═══╝ ████╔╝██║\n * ███████╗██║ ██║╚██████╗╚█████╔╝███████╗╚██████╔╝\n * ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚════╝ ╚══════╝ ╚═════╝\n *\n * ██████╗ ███████╗ ██████╗ ██╗███████╗████████╗██████╗ ██╗ ██╗\n * ██╔══██╗██╔════╝██╔════╝ ██║██╔════╝╚══██╔══╝██╔══██╗╚██╗ ██╔╝\n * ██████╔╝█████╗ ██║ ███╗██║███████╗ ██║ ██████╔╝ ╚████╔╝\n * ██╔══██╗██╔══╝ ██║ ██║██║╚════██║ ██║ ██╔══██╗ ╚██╔╝\n * ██║ ██║███████╗╚██████╔╝██║███████║ ██║ ██║ ██║ ██║\n * ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝\n *\n */\npragma solidity 0.4.24;\n// IV is value needed to have a vanity address starting with `0x820`.\n// IV: 9513\n\n/// @dev The interface a contract MUST implement if it is the implementer of\n/// some (other) interface for any address other than itself.\ninterface ERC820ImplementerInterface {\n /// @notice Indicates whether the contract implements the interface `interfaceHash` for the address `addr` or not.\n /// @param interfaceHash keccak256 hash of the name of the interface\n /// @param addr Address for which the contract will implement the interface\n /// @return ERC820_ACCEPT_MAGIC only if the contract implements `interfaceHash` for the address `addr`.\n function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) external view returns(bytes32);\n}\n\n\n/// @title ERC820 Pseudo-introspection Registry Contract\n/// @author Jordi Baylina and Jacques Dafflon\n/// @notice This contract is the official implementation of the ERC820 Registry.\n/// @notice For more details, see https://eips.ethereum.org/EIPS/eip-820\ncontract ERC820Registry {\n /// @notice ERC165 Invalid ID.\n bytes4 constant INVALID_ID = 0xffffffff;\n /// @notice Method ID for the ERC165 supportsInterface method (= `bytes4(keccak256('supportsInterface(bytes4)'))`).\n bytes4 constant ERC165ID = 0x01ffc9a7;\n /// @notice Magic value which is returned if a contract implements an interface on behalf of some other address.\n bytes32 constant ERC820_ACCEPT_MAGIC = keccak256(abi.encodePacked(\"ERC820_ACCEPT_MAGIC\"));\n\n mapping (address => mapping(bytes32 => address)) interfaces;\n mapping (address => address) managers;\n mapping (address => mapping(bytes4 => bool)) erc165Cached;\n\n /// @notice Indicates a contract is the `implementer` of `interfaceHash` for `addr`.\n event InterfaceImplementerSet(address indexed addr, bytes32 indexed interfaceHash, address indexed implementer);\n /// @notice Indicates `newManager` is the address of the new manager for `addr`.\n event ManagerChanged(address indexed addr, address indexed newManager);\n\n /// @notice Query if an address implements an interface and through which contract.\n /// @param _addr Address being queried for the implementer of an interface.\n /// (If `_addr == 0` then `msg.sender` is assumed.)\n /// @param _interfaceHash keccak256 hash of the name of the interface as a string.\n /// E.g., `web3.utils.keccak256('ERC777Token')`.\n /// @return The address of the contract which implements the interface `_interfaceHash` for `_addr`\n /// or `0x0` if `_addr` did not register an implementer for this interface.\n function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address) {\n address addr = _addr == 0 ? msg.sender : _addr;\n if (isERC165Interface(_interfaceHash)) {\n bytes4 erc165InterfaceHash = bytes4(_interfaceHash);\n return implementsERC165Interface(addr, erc165InterfaceHash) ? addr : 0;\n }\n return interfaces[addr][_interfaceHash];\n }\n\n /// @notice Sets the contract which implements a specific interface for an address.\n /// Only the manager defined for that address can set it.\n /// (Each address is the manager for itself until it sets a new manager.)\n /// @param _addr Address to define the interface for. (If `_addr == 0` then `msg.sender` is assumed.)\n /// @param _interfaceHash keccak256 hash of the name of the interface as a string.\n /// For example, `web3.utils.keccak256('ERC777TokensRecipient')` for the `ERC777TokensRecipient` interface.\n /// @param _implementer Contract address implementing _interfaceHash for _addr.\n function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) external {\n address addr = _addr == 0 ? msg.sender : _addr;\n require(getManager(addr) == msg.sender, \"Not the manager\");\n\n require(!isERC165Interface(_interfaceHash), \"Must not be a ERC165 hash\");\n if (_implementer != 0 && _implementer != msg.sender) {\n require(\n ERC820ImplementerInterface(_implementer)\n .canImplementInterfaceForAddress(_interfaceHash, addr) == ERC820_ACCEPT_MAGIC,\n \"Does not implement the interface\"\n );\n }\n interfaces[addr][_interfaceHash] = _implementer;\n emit InterfaceImplementerSet(addr, _interfaceHash, _implementer);\n }\n\n /// @notice Sets the `_newManager` as manager for the `_addr` address.\n /// The new manager will be able to call `setInterfaceImplementer` for `_addr`.\n /// @param _addr Address for which to set the new manager.\n /// @param _newManager Address of the new manager for `addr`.\n function setManager(address _addr, address _newManager) external {\n require(getManager(_addr) == msg.sender, \"Not the manager\");\n managers[_addr] = _newManager == _addr ? 0 : _newManager;\n emit ManagerChanged(_addr, _newManager);\n }\n\n /// @notice Get the manager of an address.\n /// @param _addr Address for which to return the manager.\n /// @return Address of the manager for a given address.\n function getManager(address _addr) public view returns(address) {\n // By default the manager of an address is the same address\n if (managers[_addr] == 0) {\n return _addr;\n } else {\n return managers[_addr];\n }\n }\n\n /// @notice Compute the keccak256 hash of an interface given its name.\n /// @param _interfaceName Name of the interface.\n /// @return The keccak256 hash of an interface name.\n function interfaceHash(string _interfaceName) external pure returns(bytes32) {\n return keccak256(abi.encodePacked(_interfaceName));\n }\n\n /* --- ERC165 Related Functions --- */\n /* --- Developed in collaboration with William Entriken. --- */\n\n /// @notice Updates the cache with whether the contract implements an ERC165 interface or not.\n /// @param _contract Address of the contract for which to update the cache.\n /// @param _interfaceId ERC165 interface for which to update the cache.\n function updateERC165Cache(address _contract, bytes4 _interfaceId) external {\n interfaces[_contract][_interfaceId] = implementsERC165InterfaceNoCache(_contract, _interfaceId) ? _contract : 0;\n erc165Cached[_contract][_interfaceId] = true;\n }\n\n /// @notice Checks whether a contract implements an ERC165 interface or not.\n /// The result may be cached, if not a direct lookup is performed.\n /// @param _contract Address of the contract to check.\n /// @param _interfaceId ERC165 interface to check.\n /// @return `true` if `_contract` implements `_interfaceId`, false otherwise.\n function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) {\n if (!erc165Cached[_contract][_interfaceId]) {\n return implementsERC165InterfaceNoCache(_contract, _interfaceId);\n }\n return interfaces[_contract][_interfaceId] == _contract;\n }\n\n /// @notice Checks whether a contract implements an ERC165 interface or not without using nor updating the cache.\n /// @param _contract Address of the contract to check.\n /// @param _interfaceId ERC165 interface to check.\n /// @return `true` if `_contract` implements `_interfaceId`, false otherwise.\n function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) {\n uint256 success;\n uint256 result;\n\n (success, result) = noThrowCall(_contract, ERC165ID);\n if (success == 0 || result == 0) {\n return false;\n }\n\n (success, result) = noThrowCall(_contract, INVALID_ID);\n if (success == 0 || result != 0) {\n return false;\n }\n\n (success, result) = noThrowCall(_contract, _interfaceId);\n if (success == 1 && result == 1) {\n return true;\n }\n return false;\n }\n\n /// @notice Checks whether the hash is a ERC165 interface (ending with 28 zeroes) or not.\n /// @param _interfaceHash The hash to check.\n /// @return `true` if the hash is a ERC165 interface (ending with 28 zeroes), `false` otherwise.\n function isERC165Interface(bytes32 _interfaceHash) internal pure returns (bool) {\n return _interfaceHash & 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0;\n }\n\n /// @dev Make a call on a contract without throwing if the function does not exist.\n function noThrowCall(address _contract, bytes4 _interfaceId)\n internal view returns (uint256 success, uint256 result)\n {\n bytes4 erc165ID = ERC165ID;\n\n assembly {\n let x := mload(0x40) // Find empty storage location using \"free memory pointer\"\n mstore(x, erc165ID) // Place signature at beginning of empty storage\n mstore(add(x, 0x04), _interfaceId) // Place first argument directly next to signature\n\n success := staticcall(\n 30000, // 30k gas\n _contract, // To addr\n x, // Inputs are stored at location x\n 0x08, // Inputs are 8 bytes long\n x, // Store output over input (saves space)\n 0x20 // Outputs are 32 bytes long\n )\n\n result := mload(x) // Load the result\n }\n }\n}\n", - "keccak256": "0x8eecce3912a15087b3f5845d5a74af7712c93d0a8fcd6f2d40f07ed5032022ab" - } - }, - "version": 1 -} -``` - -
- -### Interface Name - -Any interface name is hashed using `keccak256` and sent to `getInterfaceImplementer()`. - -If the interface is part of a standard, it is best practice to explicitly state the interface name and link to this published [ERC-820] such that other people don't have to come here to look up these rules. - -For convenience, the registry provides a function to compute the hash on-chain: - -``` solidity -function interfaceHash(string _interfaceName) public pure returns(bytes32) -``` - -Compute the keccak256 hash of an interface given its name. - -> **identifier:** `65ba36c1` -> **parameters** -> `_interfaceName`: Name of the interface. -> **returns:** The `keccak256` hash of an interface name. - -#### **Approved ERCs** - -If the interface is part of an approved ERC, it MUST be named `ERC###XXXXX` where `###` is the number of the ERC and XXXXX should be the name of the interface in CamelCase. The meaning of this interface SHOULD be defined in the specified ERC. - -Examples: - -- `keccak256("ERC20Token")` -- `keccak256("ERC777Token")` -- `keccak256("ERC777TokensSender")` -- `keccak256("ERC777TokensRecipient")` - -#### **[ERC-165] Compatible Interfaces** - -> The compatibility with [ERC-165], including the [ERC165 Cache], has been designed and developed with [William Entriken]. - -Any interface where the last 28 bytes are zeroes (`0`) SHALL be considered an [ERC-165] interface. - -**[ERC-165] Lookup** - -Anyone can explicitly check if a contract implements an [ERC-165] interface using the registry by calling one of the two functions below: - -``` solidity -function implementsERC165Interface(address _contract, bytes4 _interfaceId) public view returns (bool) -``` - -Checks whether a contract implements an [ERC-165] interface or not. - -*NOTE*: The result is cached. If the cache is out of date, it MUST be updated by calling `updateERC165Cache`. (See [ERC165 Cache] for more details.) - -> **identifier:** `f712f3e8` -> **parameters** -> `_contract`: Address of the contract to check. -> `_interfaceId`: [ERC-165] interface to check. -> **returns:** `true` if `_contract` implements `_interfaceId`, false otherwise. - -``` solidity -function implementsERC165InterfaceNoCache(address _contract, bytes4 _interfaceId) public view returns (bool) -``` - -Checks whether a contract implements an [ERC-165] interface or not without using nor updating the cache. - -> **identifier:** `b7056765` -> **parameters** -> `_contract`: Address of the contract to check. -> `_interfaceId`: [ERC-165] interface to check. -> **returns:** `true` if `_contract` implements `_interfaceId`, false otherwise. - -**[ERC-165] Cache** - -Whether a contract implements an [ERC-165] interface or not can be cached manually to save gas. - -If a contract dynamically changes its interface and relies on the [ERC-165] cache of the [ERC-820] registry, the cache MUST be updated manually---there is no automatic cache invalidation or cache update. Ideally the contract SHOULD automatically update the cache when changing its interface. However anyone MAY update the cache on the contract's behalf. - -The cache update MUST be done using the `updateERC165Cache` function: - -``` solidity -function updateERC165Cache(address _contract, bytes4 _interfaceId) public -``` - -> **identifier:** `a41e7d51` -> **parameters** -> `_contract`: Address of the contract for which to update the cache. -> `_interfaceId`: [ERC-165] interface for which to update the cache. - -#### **Private User-defined Interfaces** - -This scheme is extensible. You MAY make up your own interface name and raise awareness to get other people to implement it and then check for those implementations. Have fun but please, you MUST not conflict with the reserved designations above. - -### Set An Interface For An Address - -For any address to set a contract as the interface implementation, it must call the following function of the [ERC-820] registry: - -``` solidity -function setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer) public -``` - -Sets the contract which implements a specific interface for an address. - -Only the `manager` defined for that address can set it. (Each address is the manager for itself, see the [manager] section for more details.) - -*NOTE*: If `_addr` and `_implementer` are two different addresses, then: - -- The `_implementer` MUST implement the `ERC820ImplementerInterface` (detailed below). -- Calling `canImplementInterfaceForAddress` on `_implementer` with the given `_addr` and `_interfaceHash` MUST return the `ERC820_ACCEPT_MAGIC` value. - -*NOTE*: The `_interfaceHash` MUST NOT be an [ERC-165] interface---it MUST NOT end with 28 zeroes (`0`). - -*NOTE*: The `_addr` MAY be `0`, then `msg.sender` is assumed. This default value simplifies interactions via multisigs where the data of the transaction to sign is constant regardless of the address of the multisig instance. - -> **identifier:** `29965a1d` -> **parameters** -> `_addr`: Address to define the interface for (if `_addr == 0` them `msg.sender`: is assumed) -> `_interfaceHash`: `keccak256` hash of the name of the interface as a string, for example `web3.utils.keccak256('ERC777TokensRecipient')` for the ERC777TokensRecipient interface. -> `_implementer`: Contract implementing `_interfaceHash` for `_addr`. - -### Get An Implementation Of An Interface For An Address - -Anyone MAY query the [ERC-820] Registry to obtain the address of a contract implementing an interface on behalf of some address using the `getInterfaceImplementer` function. - -``` solidity -function getInterfaceImplementer(address _addr, bytes32 _interfaceHash) public view returns (address) -``` - -Query if an address implements an interface and through which contract. - -*NOTE*: If the last 28 bytes of the `_interfaceHash` are zeroes (`0`), then the first 4 bytes are considered an [ERC-165] interface and the registry SHALL forward the call to the contract at `_addr` to see if it implements the [ERC-165] interface (the first 4 bytes of `_interfaceHash`). The registry SHALL also cache [ERC-165] queries to reduce gas consumption. Anyone MAY call the `erc165UpdateCache` function to update whether a contract implements an interface or not. - -*NOTE*: The `_addr` MAY be `0`, then `msg.sender` is assumed. This default value is consistent with the behavior of the `setInterfaceImplementer` function and simplifies interactions via multisigs where the data of the transaction to sign is constant regardless of the address of the multisig instance. - -> **identifier:** `aabbb8ca` -> **parameters** -> `_addr`: Address being queried for the implementer of an interface. (If `_addr == 0` them `msg.sender` is assumed.) -> `_interfaceHash`: keccak256 hash of the name of the interface as a string. E.g. `web3.utils.keccak256('ERC777Token')` -> **returns:** The address of the contract which implements the interface `_interfaceHash` for `_addr` or `0x0` if `_addr` did not register an implementer for this interface. - - -### Interface Implementation (`ERC820ImplementerInterface`) - -``` solidity -interface ERC820ImplementerInterface { - /// @notice Indicates whether the contract implements the interface `interfaceHash` for the address `addr`. - /// @param addr Address for which the contract will implement the interface - /// @param interfaceHash keccak256 hash of the name of the interface - /// @return ERC820_ACCEPT_MAGIC only if the contract implements `ìnterfaceHash` for the address `addr`. - function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) public view returns(bytes32); -} -``` - -Any contract being registered as the implementation of an interface for a given address MUST implement said interface. In addition if it implements an interface on behalf of a different address, the contract MUST implement the `ERC820ImplementerInterface` shown above. - -``` solidity -function canImplementInterfaceForAddress(bytes32 interfaceHash, address addr) view public returns(bytes32); -``` - -Indicates whether a contract implements an interface (`interfaceHash`) for a given address (`addr`). - -If a contract implements the interface (`interfaceHash`) for a given address (`addr`), it MUST return `ERC820_ACCEPT_MAGIC` when called with the `addr` and the `interfaceHash`. If it does not implement the `interfaceHash` for a given address (`addr`), it MUST NOT return `ERC820_ACCEPT_MAGIC`. - -> **identifier:** `f0083250` -> **parameters** -> `interfaceHash`: Hash of the interface which is implemented -> `addr`: Address for which the interface is implemented -> **returns:** `ERC820_ACCEPT_MAGIC` only if the contract implements `ìnterfaceHash` for the address `addr`. - -The special value `ERC820_ACCEPT_MAGIC` is defined as the `keccka256` hash of the string `"ERC820_ACCEPT_MAGIC"`. - -``` solidity -bytes32 constant ERC820_ACCEPT_MAGIC = keccak256("ERC820_ACCEPT_MAGIC"); -``` - -> The reason to return `ERC820_ACCEPT_MAGIC` instead of a boolean is to prevent cases where a contract fails to implement the `canImplementInterfaceForAddress` but implements a fallback function which does not throw. In this case, since `canImplementInterfaceForAddress` does not exist, the fallback function is called instead, executed without throwing and returns `1`. Thus making it appear as if `canImplementInterfaceForAddress` returned `true`. - -### Manager - -The manager of an address (regular account or a contract) is the only entity allowed to register implementations of interfaces for the address. By default, any address is its own manager. - -The manager can transfer its role to another address by calling `setManager` on the registry contract with the address for which to transfer the manager and the address of the new manager. - -**`setManager` Function** - -``` solidity -function setManager(address _addr, address _newManager) public -``` - -Sets the `_newManager` as manager for the `_addr` address. - -The new manager will be able to call `setInterfaceImplementer` for `_addr`. - -If `_newManager` is `0x0`, the manager is reset to `_addr` itself as the manager. - -> **identifier:** `5df8122f` -> **parameters** -> `_addr`: Address for which to set the new manager. -> `_newManager`: The address of the new manager for `_addr`. (Pass `0x0` to reset the manager to `_addr`.) - -**`getManager` Function** - -``` solidity -function getManager(address _addr) public view returns(address) -``` - -Get the manager of an address. - -> **identifier:** `3d584063` -> **parameters** -> `_addr`: Address for which to return the manager. -> **returns:** Address of the manager for a given address. - -## Rationale - -This standards offers a way for any type of address (externally owned and contracts) to implement an interface and potentially delegate the implementation of the interface to a proxy contract. This delegation to a proxy contract is necessary for externally owned accounts and useful to avoid redeploying existing contracts such as multisigs and DAOs. - -The registry can also act as a [ERC-165] cache in order to save gas when looking up if a contract implements a specific [ERC-165] interface. This cache is intentionally kept simple, without automatic cache update or invalidation. Anyone can easily and safely update the cache for any interface and any contract by calling the `updateERC165Cache` function. - -The registry is deployed using a keyless deployment method relying on a single-use deployment address to ensure no one controls the registry, thereby ensuring trust. - -## Backward Compatibility - -This standard is backward compatible with [ERC-165], as both methods MAY be implemented without conflicting with each other. - -## Test Cases - -Please check the [jbaylina/ERC820] repository for the full test suite. - -## Implementation - -The implementation is available in the repo: [jbaylina/ERC820]. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - -[EIP-155]: ./eip-155.md -[ERC-165]: ./eip-165.md -[ERC-672]: https://github.com/ethereum/EIPs/issues/672 -[ERC-820]: ./eip-820.md -[ERC820 registry smart contract]: https://github.com/jbaylina/ERC820/blob/master/contracts/ERC820Registry.sol -[manager]: #manager -[lookup]: #get-an-implementation-of-an-interface-for-an-address -[ERC165 Cache]: #erc165-cache -[Nick's article]: https://medium.com/@weka/how-to-send-ether-to-11-440-people-187e332566b7 -[jbaylina/ERC820]: https://github.com/jbaylina/ERC820 -[Nick]: https://github.com/Arachnid/ -[William Entriken]: https://github.com/fulldecent -[ENS]: https://ens.domains/ -[ERC-1820]: ./eip-1820.md -[erc1820-annoucement]: https://github.com/ethereum/EIPs/issues/820#issuecomment-464109166 -[erc820-bug]: https://github.com/ethereum/EIPs/issues/820#issuecomment-452465748 -[erc820-fix]: https://github.com/ethereum/EIPs/issues/820#issuecomment-454021564 +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-820.md diff --git a/EIPS/eip-823.md b/EIPS/eip-823.md index 825a184d28641e..1ae595aa14aabc 100644 --- a/EIPS/eip-823.md +++ b/EIPS/eip-823.md @@ -1,214 +1 @@ ---- -eip: 823 -title: Token Exchange Standard -author: Kashish Khullar -type: Standards Track -category: ERC -status: Stagnant -created: 2018-01-06 -requires: 20 ---- - -## Simple Summary -A standard for token contracts, providing token exchange services thereby facilitating cross token payments. - -## Abstract -The following standard provides functionally to make payments in the form of any other registered tokens, as well as allow token contracts to store any other tokens in an existing token contract. This standard allows ERC20 token holders to exchange their token with another ERC20 token and use the exchanged tokens to make payments. After a successful payment, the former specified ERC20 tokens, will be stored within the ERC20 token contract they are exchanged with. This proposal uses the term target contract which is used to denote the contract to the token with whom we want to exchange our tokens. - -## Motivation -Existing token standards do not provide functionality to exchange tokens. Existing token converters reduce the total supply of an existing token, which in the sense destroys the currency. Token converters do not solve this problem and hence discourages creation of new tokens. This solution does not destroy the existing token but in essence preserve them in the token contract that they are exchanged with, which in turn increases the market value of the latter. - -## Specification -### Sender Interface -This interface must be inherited by a ERC20 token contract that wants to exchange its tokens with another token. - -#### Storage Variables -##### exchnagedWith -This mapping stores the number of tokens exchanged with another token, along with the latter’s address. Every time more tokens are exchanged the integer value is incremented consequently. This mapping acts as a record to denote which target contract holds our tokens. - -```solidity -mapping ( address => uint ) private exchangedWith; -``` -##### exchangedBy -This mapping stores the address of the person who initiated the exchange and the amount of tokens exchanged. - -```solidity -mapping ( address => uint ) private exhangedBy; -``` - -#### Methods - -NOTE: Callers MUST handle false from returns (bool success). Callers MUST NOT assume that false is never returned! - -##### exchangeToken -This function calls the intermediate exchange service contract that handles the exchanges. This function takes the address of the target contract and the amount we want to exchange as parameters and returns boolean `success` and `creditedAmount`. - -```solidity -function exchangeToken(address _targetContract, uint _amount) public returns(bool success, uint creditedAmount) -``` - -##### exchangeAndSpend -This function calls an intermediate exchange service contract that handles exchange and expenditure. This function takes the address of the target contract, the amount we want to spend in terms of target contract tokens and address of the receiver as parameters and returns boolean `success`. - -```solidity -function exchangeAndSpend(address _targetContract, uint _amount,address _to) public returns(bool success) -``` - -##### __exchangerCallback -This function is called by the exchange service contract to our token contract to deduct calculated amount from our balance. It takes the address of the targert contract , the address of the person who exchanged the tokens and amount to be deducted from exchangers account as parameters and returns boolean `success`. - -NOTE: It is required that only the exchange service contract has the authority to call this function. - -```solidity -function __exchangerCallback(address _targetContract,address _exchanger, uint _amount) public returns(bool success) -``` - -#### Events - -##### Exchange -This event logs any new exchanges that have taken place. - -```solidity -event Exchange(address _from, address _ targetContract, uint _amount) -``` - -##### ExchangeSpent -This event logs any new exchange that have taken place and have been spent immediately. - -```solidity -event ExchangeSpent(address _from, address _targetContract, address _to, uint _amount) -``` - -### Receiver Interface -This interface must be inherited by a ERC20 token contract that wants to receive exchanged tokens. - -#### Storage Variables -##### exchangesRecieved -This mapping stores the number of tokens received in terms of another token, along with its address. Every time more tokens are exchanged the integer value is incremented consequently. This mapping acts as a record to denote which tokens do this contract holds apart from its own. - -```solidity -mapping ( address => uint ) private exchnagesReceived; -``` -#### Methods - -NOTE: Callers MUST handle false from returns (bool success). Callers MUST NOT assume that false is never returned! - -##### __targetExchangeCallback -This function is called by the intermediate exchange service contract. This function should add `_amount` tokens of the target contract to the exchangers address for exchange to be completed successfully. - -NOTE: It is required that only the exchange service contract has the authority to call this function. - -```solidity -function __targetExchangeCallback (uint _to, uint _amount) public returns(bool success) -``` - -##### __targetExchangeAndSpendCallback -This function is called by the intermediate exchange service contract. This function should add `_amount` tokens of the target contract to the exchangers address and transfer it to the `_to` address for the exchange and expenditure to be completed successfully. - -NOTE: It is required that only the exchange service contract has the authority to call this function. - -```solidity -function __targetExchangeAndSpendCallback (address _from, address _to, uint _amount) public returns(bool success) -``` - -#### Events -##### Exchange -This event logs any new exchanges that have taken place. - -```solidity -event Exchange(address _from, address _with, uint _amount) -``` - -##### ExchangeSpent -This event logs any new exchange that have taken place and have been spent immediately. -```solidity -event ExchangeSpent(address _from, address _ targetContract, address _to, uint _amount) -``` - -### Exchange Service Contract - -This is an intermediate contract that provides a gateway for exchanges and expenditure. This contract uses oracles to get the authenticated exchange rates. - -#### Storage Variables - -##### registeredTokens - -This array stores all the tokens that are registered for exchange. Only register tokens can participate in exchanges. - -```solidity -address[] private registeredTokens; -``` - -#### Methods - -##### registerToken - -This function is called by the owner of the token contract to get it’s tokens registered. It takes the address of the token as the parameter and return boolean `success`. - -NOTE: Before any exchange it must be ensured that the token is registered. - -```solidity -function registerToken(address _token) public returns(bool success) -``` - -##### exchangeToken - -This function is called by the token holder who wants to exchange his token with the `_targetContract` tokens. This function queries the exchange rate, calculates the converted amount, calls `__exchangerCallback` and calls the `__targetExchangeCallback`. It takes address of the target contract and amount to exchange as parameter and returns boolean `success` and amount credited. - -```solidity -function exchangeToken(address _targetContract, uint _amount, address _from) public returns(bool success, uint creditedAmount) -``` - -##### exchangeAndSpend - -This function is called by the token holder who wants to exchange his token with the `_targetContract` tokens. This function queries the exchange rate, calculates the converted amount, calls `__exchangerCallback` and calls the `__targetExchangeAndSpendCallback`. It takes address of the target contract and amount to exchange as parameter and returns boolean `success` and amount credited. - -```solidity -function exchangeAndSpend(address _targetContract, uint _amount, address _from, address _to) public returns(bool success) -``` - -#### Events - -##### Exchanges - -This event logs any new exchanges that have taken place. - -```solidity -event Exchange( address _from, address _by, uint _value ,address _target ) -``` -##### ExchangeAndSpent - -This event logs any new exchange that have taken place and have been spent immediately. - -```solidity -event ExchangeAndSpent ( address _from, address _by, uint _value ,address _target ,address _to) -``` - -### Diagramatic Explanation - -#### Exchanging Tokens -![token-exchange-standard-visual-representation-1](../assets/eip-823/eip-823-token-exchange-standard-visual-representation-1.png) - -NOTE: After the successful exchange the contract on right owns some tokens of the contract on the left. - -#### Exchanging And Spending Tokens - -![token-exchange-standard-visual-representation-2](../assets/eip-823/eip-823-token-exchange-standard-visual-representation-2.png) - -NOTE: After the successful exchange the contract on right owns some tokens of the contract on the left. - -## Rationale - -Such a design provides a consistent exchange standard -applicable to all ERC20 tokens that follow it. -The primary advantage for of this strategy is that the exchanged tokens will not be lost. They can either be spent or preserved. -Token convert face a major drawback of destroying tokens after conversion. This mechanism treats tokens like conventional currency where tokens are not destroyed but are stored. - -## Backward Compatibility - -This proposal is fully backward compatible. Tokens extended by this proposal should also be following ERC20 standard. The functionality of ERC20 standard should not be affected by this proposal but will provide additional functionality to it. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-823.md diff --git a/EIPS/eip-831.md b/EIPS/eip-831.md index adb991e464d526..2a3c5fde2f81e7 100644 --- a/EIPS/eip-831.md +++ b/EIPS/eip-831.md @@ -1,44 +1 @@ ---- -eip: 831 -title: URI Format for Ethereum -description: A way of creating Ethereum URIs for various use-cases. -author: ligi (@ligi) -discussions-to: https://ethereum-magicians.org/t/eip-831-uri-format-for-ethereum/10105 -status: Stagnant -type: Standards Track -category: ERC -created: 2018-01-15 -requires: 67, 681 ---- - -## Abstract - -URIs embedded in QR-codes, hyperlinks in web-pages, emails or chat messages provide for robust cross-application signaling between very loosely coupled applications. A standardized URI format allows for instant invocation of the user's preferred wallet application. - -## Specification - -### Syntax - -Ethereum URIs contain "ethereum" or "eth" in their schema (protocol) part and are constructed as follows: - - request = "eth" [ "ereum" ] ":" [ prefix "-" ] payload - prefix = STRING - payload = STRING - -### Semantics - -`prefix` is optional and defines the use-case for this URI. If no prefix is given: "pay-" is assumed to be concise and ensure backward compatibility to [EIP-67](./eip-67.md). When the prefix is omitted, the payload must start with `0x`. Also prefixes must not start with `0x`. So starting with `0x` can be used as a clear signal that there is no prefix. - -`payload` is mandatory and the content depends on the prefix. Structuring of the content is defined in the ERC for the specific use-case and not in the scope of this document. One example is [EIP-681](./eip-681) for the pay- prefix. - -## Rationale - -The need for this ERC emerged when refining EIP-681. We need a container that does not carry the weight of the use-cases. EIP-67 was the first attempt on defining Ethereum-URIs. This ERC tries to keep backward compatibility and not break existing things. This means EIP-67 URIs should still be valid and readable. Only if the prefix feature is used, EIP-67 parsers might break. No way was seen to avoid this and innovate on the same time. This is also the reason this open prefix approach was chosen to being able to adopt to future use-cases and not block the whole "ethereum:" scheme for a limited set of use-cases that existed at the time of writing this. - -## Security Considerations - -There are no known security considerations at this time. - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-831.md diff --git a/EIPS/eip-875.md b/EIPS/eip-875.md index 9ac2c225923dd7..0f9c9083fc3650 100644 --- a/EIPS/eip-875.md +++ b/EIPS/eip-875.md @@ -1,105 +1 @@ ---- -eip: 875 -title: Simpler NFT standard with batching and native atomic swaps -author: Weiwu Zhang , James Sangalli -discussions-to: https://github.com/ethereum/EIPs/issues/875 -status: Withdrawn -type: Standards Track -category: ERC -created: 2018-02-08 ---- - -## Summary -A simple non fungible token standard that allows batching tokens into lots and settling p2p atomic transfers in one transaction. You can test out an example implementation on rinkeby here: https://rinkeby.etherscan.io/address/0xffab5ce7c012bc942f5ca0cd42c3c2e1ae5f0005 and view the repo here: https://github.com/alpha-wallet/ERC-Example - -## Purpose -While other standards allow the user to transfer a non-fungible token, they require one transaction per token, this is heavy on gas and partially responsible for clogging the ethereum network. There are also few definitions for how to do a simple atomic swap. - -## Rinkeby example -This standard has been implemented in an example contract on rinkeby: https://rinkeby.etherscan.io/address/0xffab5ce7c012bc942f5ca0cd42c3c2e1ae5f0005 - -## Specification - -### function name() constant returns (string name) - -returns the name of the contract e.g. CarLotContract - -### function symbol() constant returns (string symbol) - -Returns a short string of the symbol of the in-fungible token, this should be short and generic as each token is non-fungible. - -### function balanceOf(address _owner) public view returns (uint256[] balance) - -Returns an array of the users balance. - -### function transfer(address _to, uint256[] _tokens) public; - -Transfer your unique tokens to an address by adding an array of the token indices. This compares favourable to ERC721 as you can transfer a bulk of tokens in one go rather than one at a time. This has a big gas saving as well as being more convenient. - -### function transferFrom(address _from, address _to, uint256[] _tokens) public; - -Transfer a variable amount of tokens from one user to another. This can be done from an authorised party with a specified key e.g. contract owner. - -## Optional functions - -### function totalSupply() constant returns (uint256 totalSupply); - -Returns the total amount of tokens in the given contract, this should be optional as assets might be allocated and issued on the fly. This means that supply is not always fixed. - -### function ownerOf(uint256 _tokenId) public view returns (address _owner); - -Returns the owner of a particular token, I think this should be optional as not every token contract will need to track the owner of a unique token and it costs gas to loop and map the token id owners each time the balances change. - -### function trade(uint256 expiryTimeStamp, uint256[] tokenIndices, uint8 v, bytes32 r, bytes32 s) public payable - -A function which allows a user to sell a batch of non-fungible tokens without paying for the gas fee (only the buyer has to) in a p2p atomic swap. This is achieved by signing an attestation containing the amount of tokens to sell, the contract address, an expiry timestamp, the price and a prefix containing the ERC spec name and chain id. A buyer can then pay for the deal in one transaction by attaching the appropriate ether to satisfy the deal. - -This design is also more efficient as it allows orders to be done offline until settlement as opposed to creating orders in a smart contract and updating them. The expiry timestamp protects the seller against people using old orders. - -This opens up the gates for a p2p atomic swap but should be optional to this standard as some may not have use for it. - -Some protections need to be added to the message such as encoding the chain id, contract address and the ERC spec name to prevent replays and spoofing people into signing message that allow a trade. - -## Interface - -```solidity -contract ERC165 -{ - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} - -interface ERC875 /* is ERC165 */ -{ - event Transfer(address indexed _from, address indexed _to, uint256[] tokenIndices); - - function name() constant public returns (string name); - function symbol() constant public returns (string symbol); - function balanceOf(address _owner) public view returns (uint256[] _balances); - function transfer(address _to, uint256[] _tokens) public; - function transferFrom(address _from, address _to, uint256[] _tokens) public; -} - -//If you want the standard functions with atomic swap trading added -interface ERC875WithAtomicSwapTrading is ERC875 { - function trade( - uint256 expiryTimeStamp, - uint256[] tokenIndices, - uint8 v, - bytes32 r, - bytes32 s - ) public payable; -} -``` - -## Example implementation - -Please visit this [repo](https://github.com/alpha-wallet/ERC875) to see an example implementation - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-875.md diff --git a/EIPS/eip-884.md b/EIPS/eip-884.md index c2bb76c72ccc19..d7bb4992222513 100644 --- a/EIPS/eip-884.md +++ b/EIPS/eip-884.md @@ -1,322 +1 @@ ---- -eip: 884 -title: DGCL Token -author: Dave Sag -type: Standards Track -category: ERC -status: Stagnant -created: 2018-02-14 ---- - -# Delaware General Corporations Law (DGCL) compatible share token - -Ref: [proposing-an-eip-for-DGCL-tokens](https://forum.ethereum.org/discussion/17200/proposing-an-eip-for-regulation-a-Tokens) - -## Simple Summary - -An `ERC-20` compatible token that conforms to [Delaware State Senate, 149th General Assembly, Senate Bill No. 69: An act to Amend Title 8 of the Delaware Code Relating to the General Corporation Law](https://legis.delaware.gov/json/BillDetail/GenerateHtmlDocument?legislationId=25730&legislationTypeId=1&docTypeId=2&legislationName=SB69), henceforth referred to as 'The Act'. - -## Abstract - -The recently amended 'Title 8 of the Delaware Code Relating to the General Corporation Law' now explicitly allows for the use of blockchains to maintain corporate share registries. This means it is now possible to create a tradable `ERC-20` token where each token represents a share issued by a Delaware corporation. Such a token must conform to the following principles over and above the `ERC-20` standard. - -1. Token owners must have their identity verified. -2. The token contract must provide the following three functions of a `Corporations Stock ledger` (Ref: Section 224 of The Act): - - 1. Reporting: - - It must enable the corporation to prepare the list of shareholders specified in Sections 219 and 220 of The Act. - - 2. It must record the information specified in Sections 156, 159, 217(a) and 218 of The Act: - - - Partly paid shares - - Total amount paid - - Total amount to be paid - - 3. Transfers of shares as per section 159 of The Act: - - It must record transfers of shares as governed by Article 8 of subtitle I of Title 6. - -3. Each token MUST correspond to a single share, each of which would be paid for in full, so there is no need to record information concerning partly paid shares, and there are no partial tokens. - -4. There must be a mechanism to allow a shareholder who has lost their private key, or otherwise lost access to their tokens to have their address `cancelled` and the tokens re-issued to a new address. - -## Motivation - -1. Delaware General Corporation Law requires that shares issued by a Delaware corporation be recorded in a share registry. -2. The share registry can be represented by an `ERC-20` token contract that is compliant with Delaware General Corporation Law. -3. This standard can cover equity issued by any Delaware corporation, whether private or public. - -By using a `DGCL` compatible token, a firm may be able to raise funds via IPO, conforming to Delaware Corporations Law, but bypassing the need for involvement of a traditional Stock Exchange. - -There are currently no token standards that conform to the `DGCL` rules. `ERC-20` tokens do not support KYC/AML rules required by the General Corporation Law, and do not provide facilities for the exporting of lists of shareholders. - -### What about ERC-721? - -The proposed standard could easily be used to enhance `ERC-721`, adding features for associating tokens with assets such as share certificates. - -While the `ERC-721` token proposal allows for some association of metadata with an Ethereum address, its uses are _not completely aligned_ with The Act, and it is not, in its current form, fully `ERC-20` compatible. - -## Specification - -The `ERC-20` token provides the following basic features: - - contract ERC20 { - function totalSupply() public view returns (uint256); - function balanceOf(address who) public view returns (uint256); - function transfer(address to, uint256 value) public returns (bool); - function allowance(address owner, address spender) public view returns (uint256); - function transferFrom(address from, address to, uint256 value) public returns (bool); - function approve(address spender, uint256 value) public returns (bool); - event Approval(address indexed owner, address indexed spender, uint256 value); - event Transfer(address indexed from, address indexed to, uint256 value); - } - -This will be extended as follows: - - /** - * An `ERC20` compatible token that conforms to Delaware State Senate, - * 149th General Assembly, Senate Bill No. 69: An act to Amend Title 8 - * of the Delaware Code Relating to the General Corporation Law. - * - * Implementation Details. - * - * An implementation of this token standard SHOULD provide the following: - * - * `name` - for use by wallets and exchanges. - * `symbol` - for use by wallets and exchanges. - * - * The implementation MUST take care not to allow unauthorised access to - * share-transfer functions. - * - * In addition to the above the following optional `ERC20` function MUST be defined. - * - * `decimals` — MUST return `0` as each token represents a single share and shares are non-divisible. - * - * @dev Ref https://github.com/ethereum/EIPs/pull/884 - */ - contract ERC884 is ERC20 { - - /** - * This event is emitted when a verified address and associated identity hash are - * added to the contract. - * @param addr The address that was added. - * @param hash The identity hash associated with the address. - * @param sender The address that caused the address to be added. - */ - event VerifiedAddressAdded( - address indexed addr, - bytes32 hash, - address indexed sender - ); - - /** - * This event is emitted when a verified address and associated identity hash are - * removed from the contract. - * @param addr The address that was removed. - * @param sender The address that caused the address to be removed. - */ - event VerifiedAddressRemoved(address indexed addr, address indexed sender); - - /** - * This event is emitted when the identity hash associated with a verified address is updated. - * @param addr The address whose hash was updated. - * @param oldHash The identity hash that was associated with the address. - * @param hash The hash now associated with the address. - * @param sender The address that caused the hash to be updated. - */ - event VerifiedAddressUpdated( - address indexed addr, - bytes32 oldHash, - bytes32 hash, - address indexed sender - ); - - /** - * This event is emitted when an address is cancelled and replaced with - * a new address. This happens in the case where a shareholder has - * lost access to their original address and needs to have their share - * reissued to a new address. This is the equivalent of issuing replacement - * share certificates. - * @param original The address being superseded. - * @param replacement The new address. - * @param sender The address that caused the address to be superseded. - */ - event VerifiedAddressSuperseded( - address indexed original, - address indexed replacement, - address indexed sender - ); - - /** - * Add a verified address, along with an associated verification hash to the contract. - * Upon successful addition of a verified address, the contract must emit - * `VerifiedAddressAdded(addr, hash, msg.sender)`. - * It MUST throw if the supplied address or hash are zero, or if the address has already been supplied. - * @param addr The address of the person represented by the supplied hash. - * @param hash A cryptographic hash of the address holder's verified information. - */ - function addVerified(address addr, bytes32 hash) public; - - /** - * Remove a verified address, and the associated verification hash. If the address is - * unknown to the contract then this does nothing. If the address is successfully removed, this - * function must emit `VerifiedAddressRemoved(addr, msg.sender)`. - * It MUST throw if an attempt is made to remove a verifiedAddress that owns tokens. - * @param addr The verified address to be removed. - */ - function removeVerified(address addr) public; - - /** - * Update the hash for a verified address known to the contract. - * Upon successful update of a verified address the contract must emit - * `VerifiedAddressUpdated(addr, oldHash, hash, msg.sender)`. - * If the hash is the same as the value already stored then - * no `VerifiedAddressUpdated` event is to be emitted. - * It MUST throw if the hash is zero, or if the address is unverified. - * @param addr The verified address of the person represented by the supplied hash. - * @param hash A new cryptographic hash of the address holder's updated verified information. - */ - function updateVerified(address addr, bytes32 hash) public; - - /** - * Cancel the original address and reissue the tokens to the replacement address. - * Access to this function MUST be strictly controlled. - * The `original` address MUST be removed from the set of verified addresses. - * Throw if the `original` address supplied is not a shareholder. - * Throw if the `replacement` address is not a verified address. - * Throw if the `replacement` address already holds tokens. - * This function MUST emit the `VerifiedAddressSuperseded` event. - * @param original The address to be superseded. This address MUST NOT be reused. - */ - function cancelAndReissue(address original, address replacement) public; - - /** - * The `transfer` function MUST NOT allow transfers to addresses that - * have not been verified and added to the contract. - * If the `to` address is not currently a shareholder then it MUST become one. - * If the transfer will reduce `msg.sender`'s balance to 0 then that address - * MUST be removed from the list of shareholders. - */ - function transfer(address to, uint256 value) public returns (bool); - - /** - * The `transferFrom` function MUST NOT allow transfers to addresses that - * have not been verified and added to the contract. - * If the `to` address is not currently a shareholder then it MUST become one. - * If the transfer will reduce `from`'s balance to 0 then that address - * MUST be removed from the list of shareholders. - */ - function transferFrom(address from, address to, uint256 value) public returns (bool); - - /** - * Tests that the supplied address is known to the contract. - * @param addr The address to test. - * @return true if the address is known to the contract. - */ - function isVerified(address addr) public view returns (bool); - - /** - * Checks to see if the supplied address is a shareholder. - * @param addr The address to check. - * @return true if the supplied address owns a token. - */ - function isHolder(address addr) public view returns (bool); - - /** - * Checks that the supplied hash is associated with the given address. - * @param addr The address to test. - * @param hash The hash to test. - * @return true if the hash matches the one supplied with the address in `addVerified`, or `updateVerified`. - */ - function hasHash(address addr, bytes32 hash) public view returns (bool); - - /** - * The number of addresses that hold tokens. - * @return the number of unique addresses that hold tokens. - */ - function holderCount() public view returns (uint); - - /** - * By counting the number of token holders using `holderCount` - * you can retrieve the complete list of token holders, one at a time. - * It MUST throw if `index >= holderCount()`. - * @param index The zero-based index of the holder. - * @return the address of the token holder with the given index. - */ - function holderAt(uint256 index) public view returns (address); - - /** - * Checks to see if the supplied address was superseded. - * @param addr The address to check. - * @return true if the supplied address was superseded by another address. - */ - function isSuperseded(address addr) public view returns (bool); - - /** - * Gets the most recent address, given a superseded one. - * Addresses may be superseded multiple times, so this function needs to - * follow the chain of addresses until it reaches the final, verified address. - * @param addr The superseded address. - * @return the verified address that ultimately holds the share. - */ - function getCurrentFor(address addr) public view returns (address); - } - -### Securities Exchange Commission Requirements - -The Securities Exchange Commission (SEC) has additional requirements as to how a crowdsale ought to be run and what information must be made available to the general public. This information is however out of scope from this standard, though the standard does support the requirements. - -For example: The SEC requires a crowdsale's website display the amount of money raised in US Dollars. To support this a crowdsale contract minting these tokens must maintain a USD to ETH conversion rate (via Oracle or some other mechanism) and must record the conversion rate used at time of minting. - -Also, depending on the type of raise, the SEC (or other statutory body) can apply limits to the number of shareholders allowed. To support this the standard provides the `holderCount` and `isHolder` functions which a crowdsale can invoke to check that limits have not been exceeded. - -### Use of the Identity `hash` value - -Implementers of a crowdsale, in order to comply with The Act, must be able to produce an up-to-date list of the names and addresses of all shareholders. It is not desirable to include those details in a public blockchain, both for reasons of privacy, and also for reasons of economy. Storing arbitrary string data on the blockchain is strongly discouraged. - -Implementers should maintain an off-chain private database that records the owner's name, residential address, and Ethereum address. The implementer must then be able to extract the name and address for any address, and hash the name + address data and compare that hash to the hash recorded in the contract using the `hasHash` function. The specific details of this system are left to the implementer. - -It is also desirable that the implementers offer a REST API endpoint along the lines of - - GET https:////:ethereumAddress -> [true|false] - -to enable third party auditors to verify that a given Ethereum address is known to the implementers as a verified address. - -How the implementers verify a person's identity is up to them and beyond the scope of this standard. - -### Handling users who have lost access to their addresses - -A traditional share register is typically managed by a Transfer Agent who is authorised to maintain the register accurately, and to handle shareholder enquiries. A common request is for share certificates to be reissued in the case where the shareholder has lost or destroyed their original. - -Token implementers can handle that via the `cancelAndReissue` function, which must perform the various changes to ensure that the old address now points to the new one, and that cancelled addresses are not then reused. - -### Permissions management - -It is not desirable that anyone can add, remove, update, or supersede verified addresses. How access to these functions is controlled is outside of the scope of this standard. - -## Rationale - -The proposed standard offers as minimal an extension as possible over the existing `ERC-20` standard in order to conform to the requirements of The Act. Rather than return a `bool` for successful or unsuccessful completion of state-changing functions such as `addVerified`, `removeVerified`, and `updateVerified`, we have opted to require that implementations `throw` (preferably by using the [forthcoming `require(condition, 'fail message')` syntax](https://github.com/ethereum/solidity/issues/1686#issuecomment-328181514)). - -## Backwards Compatibility - -The proposed standard is designed to maintain compatibility with `ERC-20` tokens with the following provisos: - -1. The `decimals` function MUST return `0` as the tokens MUST NOT be divisible, -2. The `transfer` and `transferFrom` functions MUST NOT allow transfers to non-verified addresses, and MUST maintain a list of shareholders. -3. Shareholders who transfer away their remaining tokens must be pruned from the list of shareholders. - -Proviso 1 will not break compatibility with modern wallets or exchanges as they all appear to use that information if available. - -Proviso 2 will cause transfers to fail if an attempt is made to transfer tokens to a non-verified address. This is implicit in the design and implementers are encouraged to make this abundantly clear to market participants. We appreciate that this will make the standard unpalatable to some exchanges, but it is an SEC requirement that shareholders of a corporation provide verified names and addresses. - -Proviso 3 is an implementation detail. - -## Test Cases and Reference Implementation - -Test cases and a reference implementation are available at [github.com/davesag/ERC884-reference-implementation](https://github.com/davesag/ERC884-reference-implementation). - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-884.md diff --git a/EIPS/eip-897.md b/EIPS/eip-897.md index c5bb3c05a77d61..eb0e345954577b 100644 --- a/EIPS/eip-897.md +++ b/EIPS/eip-897.md @@ -1,73 +1 @@ ---- -eip: 897 -title: DelegateProxy -author: Jorge Izquierdo , Manuel Araoz -type: Standards Track -category: ERC -status: Stagnant -created: 2018-02-21 -discussions-to: https://github.com/ethereum/EIPs/pull/897 ---- - -## Simple Summary -Proxy contracts are being increasingly used as both as an upgradeability mechanism -and a way to save gas when deploying many instances of a particular contract. This -standard proposes a set of interfaces for proxies to signal how they work and what -their main implementation is. - -## Abstract -Using proxies that delegate their own logic to another contract is becoming an -increasingly popular technique for both smart contract upgradeability and creating -cheap clone contracts. - -We don't believe there is value in standardizing any particular implementation -of a DelegateProxy, given its simplicity, but we believe there is a lot of value -in agreeing on an interface all proxies use that allows for a standard way to -operate with proxies. - -## Implementations - -- **aragonOS**: [AppProxyUpgradeable](https://github.com/aragon/aragonOS/blob/master/contracts/apps/AppProxyUpgradeable.sol), [AppProxyPinned](https://github.com/aragon/aragonOS/blob/master/contracts/apps/AppProxyPinned.sol) and [KernelProxy](https://github.com/aragon/aragonOS/blob/master/contracts/kernel/KernelProxy.sol) - -- **zeppelinOS**: [Proxy](https://github.com/zeppelinos/labs/blob/2da9e859db81a61f2449d188e7193788ca721c65/upgradeability_ownership/contracts/Proxy.sol) - -## Standardized interface - -```solidity -interface ERCProxy { - function proxyType() public pure returns (uint256 proxyTypeId); - function implementation() public view returns (address codeAddr); -} -``` - -### Code address (`implementation()`) -The returned code address is the address the proxy would delegate calls to at that -moment in time, for that message. - -### Proxy Type (`proxyType()`) - -Checking the proxy type is the way to check whether a contract is a proxy at all. -When a contract fails to return to this method or it returns 0, it can be assumed -that the contract is not a proxy. - -It also allows for communicating a bit more of information about how the proxy -operates. It is a pure function, therefore making it effectively constant as -it cannot return a different value depending on state changes. - -- **Forwarding proxy** (`id = 1`): The proxy will always forward to the same code -address. The following invariant should always be true: once the proxy returns -a non-zero code address, that code address should never change. - -- **Upgradeable proxy** (`id = 2`): The proxy code address can be changed depending -on some arbitrary logic implemented either at the proxy level or in its forwarded -logic. - -## Benefits - -- **Source code verification**: right now when checking the code of a proxy in explorers -like Etherscan, it just shows the code in the proxy itself but not the actual -code of the contract. By standardizing this construct, they will be able to show -both the actual ABI and code for the contract. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-897.md diff --git a/EIPS/eip-900.md b/EIPS/eip-900.md index 4333f2161229e6..8f920393aaf993 100644 --- a/EIPS/eip-900.md +++ b/EIPS/eip-900.md @@ -1,109 +1 @@ ---- -eip: 900 -title: Simple Staking Interface -author: Dean Eigenmann , Jorge Izquierdo -type: Standards Track -category: ERC -status: Stagnant -created: 2018-02-22 -discussions-to: https://github.com/ethereum/EIPs/issues/900 ---- - -## Abstract - -The following standard describes a common staking interface allowing for easy to use staking systems. The interface is kept simple allowing for various use cases to be implemented. This standard describes the common functionality for staking as well as providing information on stakes. - -## Motivation - -As we move to more token models, having a common staking interface which is familiar to users can be useful. The common interface can be used by a variety of applications, this common interface could be beneficial especially to things like Token curated registries which have recently gained popularity. - -## Specification - -```solidity -interface Staking { - - event Staked(address indexed user, uint256 amount, uint256 total, bytes data); - event Unstaked(address indexed user, uint256 amount, uint256 total, bytes data); - - function stake(uint256 amount, bytes data) public; - function stakeFor(address user, uint256 amount, bytes data) public; - function unstake(uint256 amount, bytes data) public; - function totalStakedFor(address addr) public view returns (uint256); - function totalStaked() public view returns (uint256); - function token() public view returns (address); - function supportsHistory() public pure returns (bool); - - // optional - function lastStakedFor(address addr) public view returns (uint256); - function totalStakedForAt(address addr, uint256 blockNumber) public view returns (uint256); - function totalStakedAt(uint256 blockNumber) public view returns (uint256); -} -``` - -### stake - -Stakes a certain amount of tokens, this MUST transfer the given amount from the user. - -*The data field can be used to add signalling information in more complex staking applications* - -MUST trigger ```Staked``` event. - -### stakeFor - -Stakes a certain amount of tokens, this MUST transfer the given amount from the caller. - -*The data field can be used to add signalling information in more complex staking applications* - -MUST trigger ```Staked``` event. - -### unstake - -Unstakes a certain amount of tokens, this SHOULD return the given amount of tokens to the user, if unstaking is currently not possible the function MUST revert. - -*The data field can be used to remove signalling information in more complex staking applications* - -MUST trigger ```Unstaked``` event. - -### totalStakedFor - -Returns the current total of tokens staked for an address. - -### totalStaked - -Returns the current total of tokens staked. - -### token - -Address of the token being used by the staking interface. - -### supportsHistory - -MUST return true if the optional history functions are implemented, otherwise false. - -### lastStakedFor - -***OPTIONAL:** As not all staking systems require a complete history, this function is optional.* - -Returns last block address staked at. - -### totalStakedForAt - -***OPTIONAL:** As not all staking systems require a complete history, this function is optional.* - -Returns total amount of tokens staked at block for address. - -### totalStakedAt - -***OPTIONAL:** As not all staking systems require a complete history, this function is optional.* - -Returns the total tokens staked at block. - -## Implementation - -- [Stakebank](https://github.com/HarbourProject/stakebank) -- [Aragon](https://github.com/aragon/aragon-apps/pull/101) -- [PoS Staking](https://github.com/maticnetwork/contracts/blob/master/contracts/StakeManager.sol) -- [BasicStakeContract](https://github.com/codex-protocol/contract.erc-900) - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-900.md diff --git a/EIPS/eip-902.md b/EIPS/eip-902.md index c7b67b4cc921ad..d9dcb104555ad2 100644 --- a/EIPS/eip-902.md +++ b/EIPS/eip-902.md @@ -1,153 +1 @@ ---- -eip: 902 -title: Token Validation -author: Brooklyn Zelenka (@expede), Tom Carchrae (@carchrae), Gleb Naumenko (@naumenkogs) -discussions-to: https://ethereum-magicians.org/t/update-on-erc902-validated-token/1639 -type: Standards Track -category: ERC -status: Stagnant -created: 2018-02-14 -requires: 1066 ---- - -# Simple Summary -A protocol for services providing token ownership and transfer validation. - -# Abstract -This standard provides a registry contract method for authorizing token transfers. By nature, this covers both initially issuing tokens to users (ie: transfer from contract to owner), transferring tokens between users, and token spends. - -# Motivation -The tokenization of assets has wide application, not least of which is financial instruments such as securities and security tokens. Most jurisdictions have placed legal constraints on what may be traded, and who can hold such tokens which are regarded as securities. Broadly this includes KYC and AML validation, but may also include time-based spend limits, total volume of transactions, and so on. - -Regulators and sanctioned third-party compliance agencies need some way to link off-chain compliance information such as identity and residency to an on-chain service. The application of this design is broader than legal regulation, encompassing all manner of business logic permissions for the creation, management, and trading of tokens. - -Rather than each token maintaining its own whitelist (or other mechanism), it is preferable to share on-chain resources, rules, lists, and so on. There is also a desire to aggregate data and rules spread across multiple validators, or to apply complex behaviours (ex. switching logic, gates, state machines) to apply distributed data to an application. - -# Specification - -## `TokenValidator` - -```solidity -interface TokenValidator { - function check( - address _token, - address _subject - ) public returns(byte statusCode) - - function check( - address _token, - address _from, - address _to, - uint256 _amount - ) public returns (byte statusCode) -} -``` - -### Methods - -#### `check`/2 - -`function check(address _token, address _subject) public returns (byte _resultCode)` - -> parameters -> * `_token`: the token under review -> * `_subject`: the user or contract to check -> -> *returns* an ERC1066 status code - -#### `check`/4 - -`function check(address token, address from, address to, uint256 amount) public returns (byte resultCode)` - -> parameters -> * `_token`: the token under review -> * `_from`: in the case of a transfer, who is relinquishing token ownership -> * `_to`: in the case of a transfer, who is accepting token ownership -> * `_amount`: The number of tokens being transferred -> -> *returns* an ERC1066 status code - -## `ValidatedToken` - -```solidity -interface ValidatedToken { - event Validation( - address indexed subject, - byte indexed result - ) - - event Validation( - address indexed from, - address indexed to, - uint256 value, - byte indexed statusCode - ) -} -``` - -### Events - -#### `Validation`/2 - -`event Validation(address indexed subject, byte indexed resultCode)` - -This event MUST be fired on return from a call to a `TokenValidator.check/2`. - -> parameters -> * `subject`: the user or contract that was checked -> * `statusCode`: an ERC1066 status code - - -#### `Validation`/4 - -```solidity -event Validation( - address indexed from, - address indexed to, - uint256 amount, - byte indexed statusCode -) -``` - -This event MUST be fired on return from a call to a `TokenValidator.check/4`. - -> parameters -> * `from`: in the case of a transfer, who is relinquishing token ownership -> * `to`: in the case of a transfer, who is accepting token ownership -> * `amount`: The number of tokens being transferred -> * `statusCode`: an ERC1066 status code - -# Rationale - -This proposal includes a financial permissions system on top of any financial token. This design is not a general roles/permission system. In any system, the more you know about the context where a function will be called, the more powerful your function can be. By restricting ourselves to token transfers (ex. ERC20 or EIP-777), we can make assumptions about the use cases our validators will need to handle, and can make the API both small, useful, and extensible. - -The events are fired by the calling token. Since `Validator`s may aggregate or delegate to other `Validator`s, it would generate a lot of useless events were it the -`Validator`'s responsibility. This is also the reason why we include the `token` in the `call/4` arguments: a `Validator` cannot rely on `msg.sender` to determine the token that the call is concerning. - -We have also seen a similar design from [R-Token](https://github.com/harborhq/r-token) that uses an additional field: `spender`. While there are potential use cases for this, it's not widely used enough to justify passing a dummy value along with every call. Instead, such a call would look more like this: - -```solidity -function approve(address spender, uint amount) public returns (bool success) { - if (validator.check(this, msg.sender, spender, amount) == okStatusCode) { - allowed[msg.sender][spender] = amount; - Approval(msg.sender, spender, amount); - return true; - } else { - return false; - } -} -``` - -A second `check/2` function is also required, that is more general-purpose, and does not specify a transfer amount or recipient. This is intended for general checks, such as checking roles (admin, owner, &c), or if a user is on a simple whitelist. - -We have left the decision to make associated `Validator` addresses public, private, or hardcoded up to the implementer. The proposed design does not include a centralized registry. It also does not include an interface for a `Validated` contract. A token may require one or many `Validator`s for different purposes, requiring different validations for different, or just a single `Validator`. The potential use cases are too varied to provide a single unified set of methods. We have provided a set of example contracts [here](https://github.com/Finhaven/ValidatedToken/) that may be inherited from for common use cases. - -The status codes in the `byte` returns are unspecified. Any status code scheme may be used, though a general status code proposal is fortcoming. - -By only defining the validation check, this standard is widely compatible with ERC-20, EIP-721, EIP-777, future token standards, centralized and decentralized exchanges, and so on. - -# Implementation -[Reference implementation](https://github.com/expede/validated-token/) - -# Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-902.md diff --git a/EIPS/eip-918.md b/EIPS/eip-918.md index cc6e0d0aaad016..eab01d37230541 100644 --- a/EIPS/eip-918.md +++ b/EIPS/eip-918.md @@ -1,479 +1 @@ ---- -eip: 918 -title: Mineable Token Standard -author: Jay Logelin , Infernal_toast , Michael Seiler , Brandon Grill -type: Standards Track -category: ERC -status: Stagnant -created: 2018-03-07 ---- - -### Simple Summary - -A specification for a standardized Mineable Token that uses a Proof of Work algorithm for distribution. - -### Abstract - -This specification describes a method for initially locking tokens within a token contract and slowly dispensing them with a mint() function which acts like a faucet. This mint() function uses a Proof of Work algorithm in order to minimize gas fees and control the distribution rate. Additionally, standardization of mineable tokens will give rise to standardized CPU and GPU token mining software, token mining pools and other external tools in the token mining ecosystem. - -### Motivation - -Token distribution via the ICO model and its derivatives is susceptible to illicit behavior by human actors. Furthermore, new token projects are centralized because a single entity must handle and control all of the initial coins and all of the raised ICO money. By distributing tokens via an 'Initial Mining Offering' (or IMO), the ownership of the token contract no longer belongs with the deployer at all and the deployer is 'just another user.' As a result, investor risk exposure utilizing a mined token distribution model is significantly diminished. This standard is intended to be standalone, allowing maximum interoperability with ERC20, ERC721, and others. - -### Specification - -#### Interface -The general behavioral specification includes a primary function that defines the token minting operation, an optional merged minting operation for issuing multiple tokens, getters for challenge number, mining difficulty, mining target and current reward, and finally a Mint event, to be emitted upon successful solution validation and token issuance. At a minimum, contracts must adhere to this interface (save the optional merge operation). It is recommended that contracts interface with the more behaviorally defined Abstract Contract described below, in order to leverage a more defined construct, allowing for easier external implementations via overridden phased functions. (see 'Abstract Contract' below) - -``` solidity -interface ERC918 { - - function mint(uint256 nonce) public returns (bool success); - - function getAdjustmentInterval() public view returns (uint); - - function getChallengeNumber() public view returns (bytes32); - - function getMiningDifficulty() public view returns (uint); - - function getMiningTarget() public view returns (uint); - - function getMiningReward() public view returns (uint); - - function decimals() public view returns (uint8); - - event Mint(address indexed from, uint rewardAmount, uint epochCount, bytes32 newChallengeNumber); -} -``` - -#### Abstract Contract (Optional) - -The Abstract Contract adheres to the EIP918 Interface and extends behavioral definition through the introduction of 4 internal phases of token mining and minting: hash, reward, epoch and adjust difficulty, all called during the mint() operation. This construct provides a balance between being too general for use while providing ample room for multiple mined implementation types. - -### Fields - -#### adjustmentInterval -The amount of time between difficulty adjustments in seconds. - -``` solidity -bytes32 public adjustmentInterval; -``` - -#### challengeNumber -The current challenge number. It is expected that a new challenge number is generated after a new reward is minted. - -``` solidity -bytes32 public challengeNumber; -``` - -#### difficulty -The current mining difficulty which should be adjusted via the \_adjustDifficulty minting phase - -``` solidity -uint public difficulty; -``` - -#### tokensMinted -Cumulative counter of the total minted tokens, usually modified during the \_reward phase - -``` solidity -uint public tokensMinted; -``` - -#### epochCount -Number of 'blocks' mined - -``` solidity -uint public epochCount; -``` - -### Mining Operations - -#### mint - -Returns a flag indicating a successful hash digest verification, and reward allocation to msg.sender. In order to prevent MiTM attacks, it is recommended that the digest include a recent Ethereum block hash and msg.sender's address. Once verified, the mint function calculates and delivers a mining reward to the sender and performs internal accounting operations on the contract's supply. - -The mint operation exists as a public function that invokes 4 separate phases, represented as functions hash, \_reward, \_newEpoch, and \_adjustDifficulty. In order to create the most flexible implementation while adhering to a necessary contract protocol, it is recommended that token implementors override the internal methods, allowing the base contract to handle their execution via mint. - -This externally facing function is called by miners to validate challenge digests, calculate reward, -populate statistics, mutate epoch variables and adjust the solution difficulty as required. Once complete, -a Mint event is emitted before returning a boolean success flag. - -``` solidity -contract AbstractERC918 is EIP918Interface { - - // the amount of time between difficulty adjustments - uint public adjustmentInterval; - - // generate a new challenge number after a new reward is minted - bytes32 public challengeNumber; - - // the current mining target - uint public miningTarget; - - // cumulative counter of the total minted tokens - uint public tokensMinted; - - // number of blocks per difficulty readjustment - uint public blocksPerReadjustment; - - //number of 'blocks' mined - uint public epochCount; - - /* - * Externally facing mint function that is called by miners to validate challenge digests, calculate reward, - * populate statistics, mutate epoch variables and adjust the solution difficulty as required. Once complete, - * a Mint event is emitted before returning a success indicator. - **/ - function mint(uint256 nonce) public returns (bool success) { - require(msg.sender != address(0)); - - // perform the hash function validation - hash(nonce); - - // calculate the current reward - uint rewardAmount = _reward(); - - // increment the minted tokens amount - tokensMinted += rewardAmount; - - epochCount = _epoch(); - - //every so often, readjust difficulty. Don't readjust when deploying - if(epochCount % blocksPerReadjustment == 0){ - _adjustDifficulty(); - } - - // send Mint event indicating a successful implementation - emit Mint(msg.sender, rewardAmount, epochCount, challengeNumber); - - return true; - } -} -``` - -##### *Mint Event* - -Upon successful verification and reward the mint method dispatches a Mint Event indicating the reward address, the reward amount, the epoch count and newest challenge number. - -``` solidity -event Mint(address indexed from, uint reward_amount, uint epochCount, bytes32 newChallengeNumber); -``` - -#### hash - -Public interface function hash, meant to be overridden in implementation to define hashing algorithm and validation. Returns the validated digest - -``` solidity -function hash(uint256 nonce) public returns (bytes32 digest); -``` - -#### \_reward - -Internal interface function \_reward, meant to be overridden in implementation to calculate and allocate the reward amount. The reward amount must be returned by this method. - -``` solidity -function _reward() internal returns (uint); -``` - -#### \_newEpoch - -Internal interface function \_newEpoch, meant to be overridden in implementation to define a cutpoint for mutating mining variables in preparation for the next phase of mine. - -``` solidity -function _newEpoch(uint256 nonce) internal returns (uint); -``` - -#### \_adjustDifficulty - -Internal interface function \_adjustDifficulty, meant to be overridden in implementation to adjust the difficulty (via field difficulty) of the mining as required - -``` solidity -function _adjustDifficulty() internal returns (uint); -``` - -#### getAdjustmentInterval - -The amount of time, in seconds, between difficulty adjustment operations. - -``` solidity -function getAdjustmentInterval() public view returns (uint); -``` - -#### getChallengeNumber - -Recent ethereum block hash, used to prevent pre-mining future blocks. - -``` solidity -function getChallengeNumber() public view returns (bytes32); -``` - -#### getMiningDifficulty - -The number of digits that the digest of the PoW solution requires which typically auto adjusts during reward generation. - -``` solidity -function getMiningDifficulty() public view returns (uint) -``` - -#### getMiningReward - -Return the current reward amount. Depending on the algorithm, typically rewards are divided every reward era as tokens are mined to provide scarcity. - -``` solidity -function getMiningReward() public view returns (uint) -``` - -### Example mining function -A general mining function written in python for finding a valid nonce for keccak256 mined token, is as follows: -``` python -def generate_nonce(): - myhex = b'%064x' % getrandbits(32*8) - return codecs.decode(myhex, 'hex_codec') - -def mine(challenge, public_address, difficulty): - while True: - nonce = generate_nonce() - hash1 = int(sha3.keccak_256(challenge+public_address+nonce).hexdigest(), 16) - if hash1 < difficulty: - return nonce, hash1 -``` - -Once the nonce and hash1 are found, these are used to call the mint() function of the smart contract to receive a reward of tokens. - -### Merged Mining Extension (Optional) -In order to provide support for merge mining multiple tokens, an optional merged mining extension can be implemented as part of the ERC918 standard. It is important to note that the following function will only properly work if the base contracts use tx.origin instead of msg.sender when applying rewards. If not the rewarded tokens will be sent to the calling contract and not the end user. - -``` solidity -/** - * @title ERC-918 Mineable Token Standard, optional merged mining functionality - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md - * - */ -contract ERC918Merged is AbstractERC918 { - /* - * @notice Externally facing merge function that is called by miners to validate challenge digests, calculate reward, - * populate statistics, mutate state variables and adjust the solution difficulty as required. Additionally, the - * merge function takes an array of target token addresses to be used in merged rewards. Once complete, - * a Mint event is emitted before returning a success indicator. - * - * @param _nonce the solution nonce - **/ - function merge(uint256 _nonce, address[] _mineTokens) public returns (bool) { - for (uint i = 0; i < _mineTokens.length; i++) { - address tokenAddress = _mineTokens[i]; - ERC918Interface(tokenAddress).mint(_nonce); - } - } - - /* - * @notice Externally facing merge function kept for backwards compatibility with previous definition - * - * @param _nonce the solution nonce - * @param _challenge_digest the keccak256 encoded challenge number + message sender + solution nonce - **/ - function merge(uint256 _nonce, bytes32 _challenge_digest, address[] _mineTokens) public returns (bool) { - //the challenge digest must match the expected - bytes32 digest = keccak256( abi.encodePacked(challengeNumber, msg.sender, _nonce) ); - require(digest == _challenge_digest, "Challenge digest does not match expected digest on token contract [ ERC918Merged.mint() ]"); - return merge(_nonce, _mineTokens); - } -} -``` - -### Delegated Minting Extension (Optional) -In order to facilitate a third party minting submission paradigm, such as the case of miners submitting solutions to a pool operator and/or system, a delegated minting extension can be used to allow pool accounts submit solutions on the behalf of a user, so the miner can avoid directly paying Ethereum transaction costs. This is performed by an off chain mining account packaging and signing a standardized mint solution packet and sending it to a pool or 3rd party to be submitted. - -The ERC918 Mineable Mint Packet Metadata should be prepared using following schema: -``` solidity -{ - "title": "Mineable Mint Packet Metadata", - "type": "object", - "properties": { - "nonce": { - "type": "string", - "description": "Identifies the target solution nonce", - }, - "origin": { - "type": "string", - "description": "Identifies the original user that mined the solution nonce", - }, - "signature": { - "type": "string", - "description": "The signed hash of tightly packed variables sha3('delegatedMintHashing(uint256,address)')+nonce+origin_account", - } - } -} -``` -The preparation of a mineable mint packet on a JavaScript client would appear as follows: - -``` solidity -function prepareDelegatedMintTxn(nonce, account) { - var functionSig = web3.utils.sha3("delegatedMintHashing(uint256,address)").substring(0,10) - var data = web3.utils.soliditySha3( functionSig, nonce, account.address ) - var sig = web3.eth.accounts.sign(web3.utils.toHex(data), account.privateKey ) - // prepare the mint packet - var packet = {} - packet.nonce = nonce - packet.origin = account.address - packet.signature = sig.signature - // deliver resulting JSON packet to pool or third party - var mineableMintPacket = JSON.stringify(packet, null, 4) - /* todo: send mineableMintPacket to submitter */ - ... -} -``` -Once the packet is prepared and formatted it can then be routed to a third party that will submit the transaction to the contract's delegatedMint() function, thereby paying for the transaction gas and receiving the resulting tokens. The pool/third party must then manually payback the minted tokens minus fees to the original minter. - -The following code sample exemplifies third party packet relaying: -``` solidity -//received by minter -var mineableMintPacket = ... -var packet = JSON.parse(mineableMintPacket) -erc918MineableToken.delegatedMint(packet.nonce, packet.origin, packet.signature) -``` -The Delegated Mint Extension expands upon ERC918 realized as a sub-contract: -``` js -import 'openzeppelin-solidity/contracts/contracts/cryptography/ECDSA.sol'; - -contract ERC918DelegatedMint is AbstractERC918, ECDSA { - /** - * @notice Hash (keccak256) of the payload used by delegatedMint - * @param _nonce the golden nonce - * @param _origin the original minter - * @param _signature the original minter's elliptical curve signature - */ - function delegatedMint(uint256 _nonce, address _origin, bytes _signature) public returns (bool success) { - bytes32 hashedTx = delegatedMintHashing(_nonce, _origin); - address minter = recover(hashedTx, _signature); - require(minter == _origin, "Origin minter address does not match recovered signature address [ AbstractERC918.delegatedMint() ]"); - require(minter != address(0), "Invalid minter address recovered from signature [ ERC918DelegatedMint.delegatedMint() ]"); - success = mintInternal(_nonce, minter); - } - - /** - * @notice Hash (keccak256) of the payload used by delegatedMint - * @param _nonce the golden nonce - * @param _origin the original minter - */ - function delegatedMintHashing(uint256 _nonce, address _origin) public pure returns (bytes32) { - /* "0x7b36737a": delegatedMintHashing(uint256,address) */ - return toEthSignedMessageHash(keccak256(abi.encodePacked( bytes4(0x7b36737a), _nonce, _origin))); - } -} -``` - -### Mineable Token Metadata (Optional) -In order to provide for richer and potentially mutable metadata for a particular Mineable Token, it is more viable to offer an off-chain reference to said data. This requires the implementation of a single interface method 'metadataURI()' that returns a JSON string encoded with the string fields symbol, name, description, website, image, and type. - -Solidity interface for Mineable Token Metadata: -``` solidity -/** - * @title ERC-918 Mineable Token Standard, optional metadata extension - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md - * - */ -interface ERC918Metadata is AbstractERC918 { - /** - * @notice A distinct Uniform Resource Identifier (URI) for a mineable asset. - */ - function metadataURI() external view returns (string); -} -``` - -Mineable Token Metadata JSON schema definition: -``` solidity -{ - "title": "Mineable Token Metadata", - "type": "object", - "properties": { - "symbol": { - "type": "string", - "description": "Identifies the Mineable Token's symbol", - }, - "name": { - "type": "string", - "description": "Identifies the Mineable Token's name", - }, - "description": { - "type": "string", - "description": "Identifies the Mineable Token's long description", - }, - "website": { - "type": "string", - "description": "Identifies the Mineable Token's homepage URI", - }, - "image": { - "type": "string", - "description": "Identifies the Mineable Token's image URI", - }, - "type": { - "type": "string", - "description": "Identifies the Mineable Token's hash algorithm ( ie.keccak256 ) used to encode the solution", - } - } -} -``` - -### Rationale - -The solidity keccak256 algorithm does not have to be used, but it is recommended since it is a cost effective one-way algorithm to perform in the EVM and simple to perform in solidity. The nonce is the solution that miners try to find and so it is part of the hashing algorithm. A challengeNumber is also part of the hash so that future blocks cannot be mined since it acts like a random piece of data that is not revealed until a mining round starts. The msg.sender address is part of the hash so that a nonce solution is valid only for a particular Ethereum account and so the solution is not susceptible to man-in-the-middle attacks. This also allows pools to operate without being easily cheated by the miners since pools can force miners to mine using the pool's address in the hash algorithm. - -The economics of transferring electricity and hardware into mined token assets offers a flourishing community of decentralized miners the option to be involved in the Ethereum token economy directly. By voting with hash power, an economically pegged asset to real-world resources, miners are incentivized to participate in early token trade to revamp initial costs, providing a bootstrapped stimulus mechanism between miners and early investors. - -One community concern for mined tokens has been around energy use without a function for securing a network. Although token mining does not secure a network, it serves the function of securing a community from corruption as it offers an alternative to centralized ICOs. Furthermore, an initial mining offering may last as little as a week, a day, or an hour at which point all of the tokens would have been minted. - - -### Backwards Compatibility -Earlier versions of this standard incorporated a redundant 'challenge_digest' parameter on the mint() function that hash-encoded the packed variables challengeNumber, msg.sender and nonce. It was decided that this could be removed from the standard to help minimize processing and thereby gas usage during mint operations. However, in the name of interoperability with existing mining programs and pool software the following contract can be added to the inheritance tree: - -``` solidity -/** - * @title ERC-918 Mineable Token Standard, optional backwards compatibility function - * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-918.md - * - */ -contract ERC918BackwardsCompatible is AbstractERC918 { - - /* - * @notice Externally facing mint function kept for backwards compatibility with previous mint() definition - * @param _nonce the solution nonce - * @param _challenge_digest the keccak256 encoded challenge number + message sender + solution nonce - **/ - function mint(uint256 _nonce, bytes32 _challenge_digest) public returns (bool success) { - //the challenge digest must match the expected - bytes32 digest = keccak256( abi.encodePacked(challengeNumber, msg.sender, _nonce) ); - require(digest == _challenge_digest, "Challenge digest does not match expected digest on token contract [ AbstractERC918.mint() ]"); - success = mint(_nonce); - } -} -``` - -### Test Cases -(Test cases for an implementation are mandatory for EIPs that are affecting consensus changes. Other EIPs can choose to include links to test cases if applicable.) - - -### Implementation - -Simple Example: -https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/SimpleERC918.sol - -Complex Examples: - -https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/0xdogeExample.sol -https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/0xdogeExample2.sol -https://github.com/0xbitcoin/EIP918-Mineable-Token/blob/master/contracts/0xBitcoinBase.sol - -0xBitcoin Token Contract: -https://etherscan.io/address/0xb6ed7644c69416d67b522e20bc294a9a9b405b31 - -MVI OpenCL Token Miner -https://github.com/mining-visualizer/MVis-tokenminer/releases - -PoWAdv Token Contract: -https://etherscan.io/address/0x1a136ae98b49b92841562b6574d1f3f5b0044e4c - - -### Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-918.md diff --git a/EIPS/eip-926.md b/EIPS/eip-926.md index f5f25087577ecc..f29b045edeaec5 100644 --- a/EIPS/eip-926.md +++ b/EIPS/eip-926.md @@ -1,73 +1 @@ ---- -eip: 926 -title: Address metadata registry -author: Nick Johnson -type: Standards Track -category: ERC -status: Stagnant -created: 2018-03-12 -requires: 165 ---- - -## Abstract -This EIP specifies a registry for address metadata, permitting both contracts and external accounts to supply metadata about themselves to onchain and offchain callers. This permits use-cases such as generalised authorisations, providing token acceptance settings, and claims registries. - -## Motivation -An increasing set of use cases require storage of metadata associated with an address; see for instance EIP 777 and EIP 780, and the ENS reverse registry in EIP 181. Presently each use-case defines its own specialised registry. To prevent a proliferation of special-purpose registry contracts, we instead propose a single standardised registry using an extendable architecture that allows future standards to implement their own metadata standards. - -## Specification -The metadata registry has the following interface: -```solidity -interface AddressMetadataRegistry { - function provider(address target) view returns(address); - function setProvider(address _provider); -} -``` - -`setProvider` specifies the metadata registry to be associated with the caller's address, while `provider` returns the address of the metadata registry for the supplied address. - -The metadata registry will be compiled with an agreed-upon version of Solidity and deployed using the trustless deployment mechanism to a fixed address that can be replicated across all chains. - -## Provider specification - -Providers may implement any subset of the metadata record types specified here. Where a record types specification requires a provider to provide multiple functions, the provider MUST implement either all or none of them. Providers MUST throw if called with an unsupported function ID. - -Providers have one mandatory function: - -```solidity -function supportsInterface(bytes4 interfaceID) constant returns (bool) -``` - -The `supportsInterface` function is documented in [EIP-165](./eip-165.md), and returns true if the provider implements the interface specified by the provided 4 byte identifier. An interface identifier consists of the XOR of the function signature hashes of the functions provided by that interface; in the degenerate case of single-function interfaces, it is simply equal to the signature hash of that function. If a provider returns `true` for `supportsInterface()`, it must implement the functions specified in that interface. - -`supportsInterface` must always return true for `0x01ffc9a7`, which is the interface ID of `supportsInterface` itself. - -The first argument to all provider functions MUST be the address being queried; this facilitates the creation of multi-user provider contracts. - -Currently standardised provider interfaces are specified in the table below. - -| Interface name | Interface hash | Specification | -| --- | --- | --- | - -EIPs may define new interfaces to be added to this registry. - -## Rationale -There are two obvious approaches for a generic metadata registry: the indirection approach employed here, or a generalised key/value store. While indirection incurs the cost of an additional contract call, and requires providers to change over time, it also provides for significantly enhanced flexibility over a key/value store; for that reason we selected this approach. - -## Backwards Compatibility -There are no backwards compatibility concerns. - -## Implementation -The canonical implementation of the metadata registry is as follows: -```solidity -contract AddressMetadataRegistry { - mapping(address=>address) public provider; - - function setProvider(address _provider) { - provider[msg.sender] = _provider; - } -} -``` - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-926.md diff --git a/EIPS/eip-927.md b/EIPS/eip-927.md index 5cd5674c74eeba..9da7c798b9c4e8 100644 --- a/EIPS/eip-927.md +++ b/EIPS/eip-927.md @@ -1,58 +1 @@ ---- -eip: 927 -title: Generalised authorisations -author: Nick Johnson -type: Standards Track -category: ERC -status: Stagnant -created: 2018-03-12 -requires: 926 ---- - -## Abstract -This EIP specifies a generic authorisation mechanism, which can be used to implement a variety of authorisation patterns, replacing approvals in ERC20, operators in ERC777, and bespoke authorisation patterns in a variety of other types of contract. - -## Motivation -Smart contracts commonly need to provide an interface that allows a third-party caller to perform actions on behalf of a user. The most common example of this is token authorisations/operators, but other similar situations exist throughout the ecosystem, including for instance authorising operations on ENS domains. Typically each standard reinvents this system for themselves, leading to a large number of incompatible implementations of the same basic pattern. Here, we propose a generic method usable by all such contracts. - -The pattern implemented here is inspired by [ds-auth](https://github.com/dapphub/ds-auth) and by OAuth. - -## Specification -The generalised authorisation interface is implemented as a metadata provider, as specified in EIP 926. The following mandatory function is implemented: - -```solidity -function canCall(address owner, address caller, address callee, bytes4 func) view returns(bool); -``` - -Where: - - `owner` is the owner of the resource. If approved the function call is treated as being made by this address. - - `caller` is the address making the present call. - - `callee` is the address of the contract being called. - - `func` is the 4-byte signature of the function being called. - -For example, suppose Alice authorises Bob to transfer tokens on her behalf. When Bob does so, Alice is the `owner`, Bob is the `caller`, the token contract is the `callee`, and the function signature for the transfer function is `func`. - -As this standard uses EIP 926, the authorisation flow is as follows: - - 1. The callee contract fetches the provider for the `owner` address from the metadata registry contract, which resides at a well-known address. - 2. The callee contract calls `canCall()` with the parameters described above. If the function returns false, the callee reverts execution. - -Commonly, providers will wish to supply a standardised interface for users to set and unset their own authorisations. They SHOULD implement the following interface: - -```solidity -function authoriseCaller(address owner, address caller, address callee, bytes4 func); -function revokeCaller(address owner, address caller, address callee, bytes4 func); -``` - -Arguments have the same meaning as in `canCall`. Implementing contracts MUST ensure that `msg.sender` is authorised to call `authoriseCaller` or `revokeCaller` on behalf of `owner`; this MUST always be true if `owner == msg.sender`. Implementing contracts SHOULD use the standard specified here to determine if other callers may provide authorisations as well. - -Implementing contracts SHOULD treat a `func` of 0 as authorising calls to all functions on `callee`. If `authorised` is `false` and `func` is 0, contracts need only clear any blanket authorisation; individual authorisations may remain in effect. - -## Backwards Compatibility -There are no backwards compatibility concerns. - -## Implementation -Example implementation TBD. - -## Copyright -Copyright and related rights waived via [CC0](../LICENSE.md). +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-927.md diff --git a/EIPS/eip-998.md b/EIPS/eip-998.md index b50c26153148c9..53c3eee5d5ebb3 100644 --- a/EIPS/eip-998.md +++ b/EIPS/eip-998.md @@ -1,1378 +1 @@ ---- -eip: 998 -title: Composable Non-Fungible Token -description: Extends a ERC-721 to own other ERC-721 and ERC-20 tokens. -author: Matt Lockyer , Nick Mudge , Jordan Schalm , sebastian echeverry , Zainan Victor Zhou (@xinbenlv) -discussions-to: https://ethereum-magicians.org/t/erc-998-composable-non-fungible-tokens-cnfts/387 -status: Draft -type: Standards Track -category: ERC -created: 2018-07-07 -requires: 20, 165, 721 ---- - -## Abstract - -An extension of the [ERC-721 standard](./eip-721.md) to enable ERC-721 tokens to own other ERC-721 tokens and [ERC-20](./eip-20.md) tokens. - -An extension of the [ERC-20](./eip-20.md) and `ERC-223 https://github.com/ethereum/EIPs/issues/223` standards to enable ERC-20 and `ERC-223` tokens to be owned by ERC-721 tokens. - -This specification covers four different kinds of composable tokens: - -1. [`ERC998ERC721` top-down composable tokens that receive, hold and transfer ERC-721 tokens](#erc-721-top-down-composable) -2. [`ERC998ERC20` top-down composable tokens that receive, hold and transfer ERC-20 tokens](#erc-20-top-down-composable) -3. [`ERC998ERC721` bottom-up composable tokens that attach themselves to other ERC-721 tokens.](#erc-721-bottom-up-composable) -4. [`ERC998ERC20` bottom-up composable tokens that attach themselves to ERC-721 tokens.](#erc-20-bottom-up-composable) - -which map to - -1. An `ERC998ERC721` top-down composable is an ERC-721 token with additional functionality for owning other ERC-721 tokens. -2. An `ERC998ERC20` top-down composable is an ERC-721 token with additional functionality for owning ERC-20 tokens. -3. An `ERC998ERC721` bottom-up composable is an ERC-721 token with additional functionality for being owned by an ERC-721 token. -4. An `ERC998ERC20` bottom-up composable is an ERC-20 token with additional functionality for being owned by an ERC-721 token. - -A top-down composable contract stores and keeps track of child tokens for each of its tokens. - -A bottom-up composable contract stores and keeps track of a parent token for each its tokens. - -With composable tokens it is possible to compose lists or trees of ERC-721 and ERC-20 tokens connected by ownership. Any such structure will have a single owner address at the root of the structure that is the owner of the entire composition. The entire composition can be transferred with one transaction by changing the root owner. - -Different composables, top-down and bottom-up, have their advantages and disadvantages which are explained in the [Rational section](#rationale). It is possible for a token to be one or more kinds of composable token. - -A non-fungible token is compliant and Composable of this EIP if it implements one or more of the following interfaces: - -* `ERC998ERC721TopDown` -* `ERC998ERC20TopDown` -* `ERC998ERC721BottomUp` -* `ERC998ERC20BottomUp` - -## Specification - -### ERC-721 - -`ERC998ERC721` top-down, `ERC998ERC20` top-down, and `ERC998ERC721` bottom-up composable contracts must implement the [ERC-721 interface](./eip-721.md). - -### ERC-20 - -`ERC998ERC20` bottom-up composable contracts must implement the [ERC-20 interface](./eip-20.md). - -### [ERC-165](./eip-165.md) - -The [ERC-165 standard](./eip-165.md) must be applied to each [ERC-998](./eip-998.md) interface that is used. - -### Authentication - -Authenticating whether a user or contract can execute some action works the same for both `ERC998ERC721` top-down and `ERC998ERC721` bottom-up composables. - -A `rootOwner` refers to the owner address at the top of a tree of composables and ERC-721 tokens. - -Authentication within any composable is done by finding the rootOwner and comparing it to `msg.sender`, the return result of `getApproved(tokenId)` and the return result of `isApprovedForAll(rootOwner, msg.sender)`. If a match is found then authentication passes, otherwise authentication fails and the contract throws. - -Here is an example of authentication code: - -```solidity -address rootOwner = address(rootOwnerOf(_tokenId)); -require(rootOwner == msg.sender || - isApprovedForAll(rootOwner,msg.sender) || - getApproved(tokenId) == msg.sender; -``` - -The `approve(address _approved, uint256 _tokenId)` and `getApproved(uint256 _tokenId)` ERC-721 functions are implemented specifically for the rootOwner. This enables a tree of composables to be transferred to a new rootOwner without worrying about which addresses have been approved in child composables, because any prior approves can only be used by the prior rootOwner. - -Here are example implementations: - -```solidity -function approve(address _approved, uint256 _tokenId) external { - address rootOwner = address(rootOwnerOf(_tokenId)); - require(rootOwner == msg.sender || isApprovedForAll(rootOwner,msg.sender)); - - rootOwnerAndTokenIdToApprovedAddress[rootOwner][_tokenId] = _approved; - emit Approval(rootOwner, _approved, _tokenId); -} - -function getApproved(uint256 _tokenId) public view returns (address) { - address rootOwner = address(rootOwnerOf(_tokenId)); - return rootOwnerAndTokenIdToApprovedAddress[rootOwner][_tokenId]; -} -``` - -### Traversal - -The rootOwner of a composable is gotten by calling `rootOwnerOf(uint256 _tokenId)` or `rootOwnerOfChild(address _childContract, uint256 _childTokenId)`. These functions are used by top-down and bottom-up composables to traverse up the tree of composables and ERC-721 tokens to find the rootOwner. - -`ERC998ERC721` top-down and bottom-up composables are interoperable with each other. It is possible for a top-down composable to own a bottom-up composable or for a top-down composable to own an ERC-721 token that owns a bottom-up token. In any configuration calling `rootOwnerOf(uint256 _tokenID)` on a composable will return the root owner address at the top of the ownership tree. - -It is important to get the traversal logic of `rootOwnerOf` right. The logic for `rootOwnerOf` is the same whether or not a composable is bottom-up or top-down or both. -Here is the logic: - -``` -Logic for rootOwnerOf(uint256 _tokenId) - -If the token is a bottom-up composable and has a parent token then call rootOwnerOf for the parent token. - If the call was successful then the returned address is the rootOwner. - Otherwise call rootOwnerOfChild for the parent token. - If the call was successful then the returned address is the rootOwner. - Otherwise get the owner address of the token and that is the rootOwner. -Otherwise call rootOwnerOfChild for the token - If the call was successful then the returned address is the rootOwner. - Otherwise get the owner address of the token and that is the rootOwner. -``` - -Calling `rootOwnerOfChild` for a token means the following logic: - -```solidity -// Logic for calling rootOwnerOfChild for a tokenId -address tokenOwner = ownerOf(tokenId); -address childContract = address(this); -bytes32 rootOwner = ERC998ERC721(tokenOwner).rootOwnerOfChild(childContract, tokenId); -``` - -But understand that the real call to `rootOwnerOfChild` should be made with assembly so that the code can check if the call failed and so that the `staticcall` opcode is used to ensure that no state is modified. - -Tokens/contracts that implement the above authentication and traversal functionality are "composable aware". - -### Composable Transfer Function Parameter Format - -Composable functions that make transfers follow the same parameter format: **from:to:what**. - -For example the `getChild(address _from, uint256 _tokenId, address _childContract, uint256 _childTokenId)` composable function transfers an ERC-721 token from an address to a top-down composable. The `_from` parameter is the **from**, the `_tokenId` parameter is the **to** and the `address _childContract, uint256 _childTokenId` parameters are the **what**. - -Another example is the `safeTransferChild(uint256 _fromTokenId, address _to, address _childContract, uint256 _childTokenId)` function. The `_fromTokenId` is the **from**, the `_to` is the **to** and the `address _childContract, address _childTokenId` parameters are the **what**. - -### transferFrom/safeTransferFrom Functions Do Not Transfer Tokens Owned By Tokens - -In bottom-up and top-down composable contracts the `transferFrom` and `safeTransferFrom` functions must throw if they are called directly to transfer a token that is owned by another token. - -The reason for this is that these functions do not explicitly specify which token owns a token to be transferred. [See the rational section for more information about this.](#explicit-transfer-parameters) - -`transferFrom/safeTransferFrom` functions must be used to transfer tokens that are owned by an address. - - -### ERC-721 Top-Down Composable - -ERC-721 top-down composables act as containers for ERC-721 tokens. - -ERC-721 top-down composables are ERC-721 tokens that can receive, hold and transfer ERC-721 tokens. - -There are two ways to transfer a ERC-721 token to a top-down composable: - -1. Use the `function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data)` function. The `_to` argument is the top-down composable contract address. The `bytes data` argument holds the integer value of the top-down composable tokenId that the ERC-721 token is transferred to. -2. Call `approve` in the ERC-721 token contract for the top-down composable contract. Then call `getChild` in the composable contract. - -The first ways is for ERC-721 contracts that have a `safeTransferFrom` function. The second way is for contracts that do not have this function such as cryptokitties. - -Here is an example of transferring ERC-721 token 3 from an address to top-down composable token 6: - -```solidity -uint256 tokenId = 6; -bytes memory tokenIdBytes = new bytes(32); -assembly { mstore(add(tokenIdBytes, 32), tokenId) } -ERC721(contractAddress).safeTransferFrom(userAddress, composableAddress, 3, tokenIdBytes); -``` - -Every ERC-721 top-down composable compliant contract must implement the `ERC998ERC721TopDown` interface. - -The `ERC998ERC721TopDownEnumerable` and `ERC998ERC20TopDownEnumerable` interfaces are optional. - -```solidity -pragma solidity ^0.4.24; - -/// @title `ERC998ERC721` Top-Down Composable Non-Fungible Token -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-998.md -/// Note: the ERC-165 identifier for this interface is 0xcde244d9 -interface ERC998ERC721TopDown { - - /// @dev This emits when a token receives a child token. - /// @param _from The prior owner of the token. - /// @param _toTokenId The token that receives the child token. - event ReceivedChild( - address indexed _from, - uint256 indexed _toTokenId, - address indexed _childContract, - uint256 _childTokenId - ); - - /// @dev This emits when a child token is transferred from a token to an address. - /// @param _fromTokenId The parent token that the child token is being transferred from. - /// @param _to The new owner address of the child token. - event TransferChild( - uint256 indexed _fromTokenId, - address indexed _to, - address indexed _childContract, - uint256 _childTokenId - ); - - /// @notice Get the root owner of tokenId. - /// @param _tokenId The token to query for a root owner address - /// @return rootOwner The root owner at the top of tree of tokens and ERC-998 magic value. - function rootOwnerOf(uint256 _tokenId) public view returns (bytes32 rootOwner); - - /// @notice Get the root owner of a child token. - /// @param _childContract The contract address of the child token. - /// @param _childTokenId The tokenId of the child. - /// @return rootOwner The root owner at the top of tree of tokens and ERC-998 magic value. - function rootOwnerOfChild( - address _childContract, - uint256 _childTokenId - ) - public - view - returns (bytes32 rootOwner); - - /// @notice Get the parent tokenId of a child token. - /// @param _childContract The contract address of the child token. - /// @param _childTokenId The tokenId of the child. - /// @return parentTokenOwner The parent address of the parent token and ERC-998 magic value - /// @return parentTokenId The parent tokenId of _tokenId - function ownerOfChild( - address _childContract, - uint256 _childTokenId - ) - external - view - returns ( - bytes32 parentTokenOwner, - uint256 parentTokenId - ); - - /// @notice A token receives a child token - /// @param _operator The address that caused the transfer. - /// @param _from The owner of the child token. - /// @param _childTokenId The token that is being transferred to the parent. - /// @param _data Up to the first 32 bytes contains an integer which is the receiving parent tokenId. - function onERC721Received( - address _operator, - address _from, - uint256 _childTokenId, - bytes _data - ) - external - returns(bytes4); - - /// @notice Transfer child token from top-down composable to address. - /// @param _fromTokenId The owning token to transfer from. - /// @param _to The address that receives the child token - /// @param _childContract The ERC-721 contract of the child token. - /// @param _childTokenId The tokenId of the token that is being transferred. - function transferChild( - uint256 _fromTokenId, - address _to, - address _childContract, - uint256 _childTokenId - ) - external; - - /// @notice Transfer child token from top-down composable to address. - /// @param _fromTokenId The owning token to transfer from. - /// @param _to The address that receives the child token - /// @param _childContract The ERC-721 contract of the child token. - /// @param _childTokenId The tokenId of the token that is being transferred. - function safeTransferChild( - uint256 _fromTokenId, - address _to, - address _childContract, - uint256 _childTokenId - ) - external; - - /// @notice Transfer child token from top-down composable to address. - /// @param _fromTokenId The owning token to transfer from. - /// @param _to The address that receives the child token - /// @param _childContract The ERC-721 contract of the child token. - /// @param _childTokenId The tokenId of the token that is being transferred. - /// @param _data Additional data with no specified format - function safeTransferChild( - uint256 _fromTokenId, - address _to, - address _childContract, - uint256 _childTokenId, - bytes _data - ) - external; - - /// @notice Transfer bottom-up composable child token from top-down composable to other ERC-721 token. - /// @param _fromTokenId The owning token to transfer from. - /// @param _toContract The ERC-721 contract of the receiving token - /// @param _toTokenId The receiving token - /// @param _childContract The bottom-up composable contract of the child token. - /// @param _childTokenId The token that is being transferred. - /// @param _data Additional data with no specified format - function transferChildToParent( - uint256 _fromTokenId, - address _toContract, - uint256 _toTokenId, - address _childContract, - uint256 _childTokenId, - bytes _data - ) - external; - - /// @notice Get a child token from an ERC-721 contract. - /// @param _from The address that owns the child token. - /// @param _tokenId The token that becomes the parent owner - /// @param _childContract The ERC-721 contract of the child token - /// @param _childTokenId The tokenId of the child token - function getChild( - address _from, - uint256 _tokenId, - address _childContract, - uint256 _childTokenId - ) - external; -} -``` - -#### `rootOwnerOf` 1 - -```solidity -/// @notice Get the root owner of tokenId. -/// @param _tokenId The token to query for a root owner address -/// @return rootOwner The root owner at the top of tree of tokens and ERC-998 magic value. -function rootOwnerOf(uint256 _tokenId) public view returns (bytes32 rootOwner); -``` - -This function traverses token owners until the the root owner address of `_tokenId` is found. - -The first 4 bytes of rootOwner contain the ERC-998 magic value `0xcd740db5`. The last 20 bytes contain the root owner address. - -The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a `rootOwnerOf` function. The magic value is used in such calls to ensure a valid return value is received. - -If it is unknown whether a contract has the `rootOwnerOf` function then the first four bytes of the `rootOwner` return value must be compared to `0xcd740db5`. - -`0xcd740db5` is equal to: - -```solidity -this.rootOwnerOf.selector ^ this.rootOwnerOfChild.selector ^ -this.tokenOwnerOf.selector ^ this.ownerOfChild.selector; -``` - -Here is an example of a value returned by `rootOwnerOf`. -`0xcd740db50000000000000000e5240103e1ff986a2c8ae6b6728ffe0d9a395c59` - -#### rootOwnerOfChild - -```solidity -/// @notice Get the root owner of a child token. -/// @param _childContract The contract address of the child token. -/// @param _childTokenId The tokenId of the child. -/// @return rootOwner The root owner at the top of tree of tokens and ERC-998 magic value. -function rootOwnerOfChild( - address _childContract, - uint256 _childTokenId -) - public - view - returns (bytes32 rootOwner); -``` - -This function traverses token owners until the the root owner address of the supplied child token is found. - -The first 4 bytes of rootOwner contain the ERC-998 magic value `0xcd740db5`. The last 20 bytes contain the root owner address. - -The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a `rootOwnerOf` function. The magic value is used in such calls to ensure a valid return value is received. - -If it is unknown whether a contract has the `rootOwnerOfChild` function then the first four bytes of the `rootOwner` return value must be compared to `0xcd740db5`. - -#### ownerOfChild - -```solidity -/// @notice Get the parent tokenId of a child token. -/// @param _childContract The contract address of the child token. -/// @param _childTokenId The tokenId of the child. -/// @return parentTokenOwner The parent address of the parent token and ERC-998 magic value -/// @return parentTokenId The parent tokenId of _tokenId -function ownerOfChild( - address _childContract, - uint256 _childTokenId -) - external - view - returns ( - address parentTokenOwner, - uint256 parentTokenId - ); -``` - -This function is used to get the parent tokenId of a child token and get the owner address of the parent token. - -The first 4 bytes of parentTokenOwner contain the ERC-998 magic value `0xcd740db5`. The last 20 bytes contain the parent token owner address. - -The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a `ownerOfChild` function. The magic value is used in such calls to ensure a valid return value is received. - -If it is unknown whether a contract has the `ownerOfChild` function then the first four bytes of the `parentTokenOwner` return value must be compared to `0xcd740db5`. - -#### `onERC721Received` - -```solidity -/// @notice A token receives a child token -/// @param _operator The address that caused the transfer. -/// @param _from The prior owner of the child token. -/// @param _childTokenId The token that is being transferred to the parent. -/// @param _data Up to the first 32 bytes contains an integer which is the receiving parent tokenId. -function onERC721Received( - address _operator, - address _from, - uint256 _childTokenId, - bytes _data -) - external - returns(bytes4); -``` - -This is a function defined in the ERC-721 standard. This function is called in an ERC-721 contract when `safeTransferFrom` is called. The `bytes _data` argument contains an integer value from 1 to 32 bytes long that is the parent tokenId that an ERC-721 token is transferred to. - -The `onERC721Received` function is how a top-down composable contract is notified that an ERC-721 token has been transferred to it and what tokenId in the top-down composable is the parent tokenId. - -The return value for `onERC721Received` is the magic value `0x150b7a02` which is equal to `bytes4(keccak256(abi.encodePacked("onERC721Received(address,address,uint256,bytes)")))`. - -#### transferChild - -```solidity -/// @notice Transfer child token from top-down composable to address. -/// @param _fromTokenId The owning token to transfer from. -/// @param _to The address that receives the child token -/// @param _childContract The ERC-721 contract of the child token. -/// @param _childTokenId The tokenId of the token that is being transferred. -function transferChild( - uint256 _fromTokenId, - address _to, - address _childContract, - uint256 _childTokenId -) - external; -``` - -This function authenticates `msg.sender` and transfers a child token from a top-down composable to a different address. - -This function makes this call within it: - -```solidity -ERC721(_childContract).transferFrom(this, _to, _childTokenId); -``` - -#### safeTransferChild 1 - -```solidity -/// @notice Transfer child token from top-down composable to address. -/// @param _fromTokenId The owning token to transfer from. -/// @param _to The address that receives the child token -/// @param _childContract The ERC-721 contract of the child token. -/// @param _childTokenId The tokenId of the token that is being transferred. -function safeTransferChild( - uint256 _fromTokenId, - address _to, - address _childContract, - uint256 _childTokenId -) - external; -``` - -This function authenticates `msg.sender` and transfers a child token from a top-down composable to a different address. - -This function makes this call within it: - -```solidity -ERC721(_childContract).safeTransferFrom(this, _to, _childTokenId); -``` - -#### safeTransferChild 2 - -```solidity -/// @notice Transfer child token from top-down composable to address or other top-down composable. -/// @param _fromTokenId The owning token to transfer from. -/// @param _to The address that receives the child token -/// @param _childContract The ERC721 contract of the child token. -/// @param _childTokenId The tokenId of the token that is being transferred. -/// @param _data Additional data with no specified format, can be used to specify tokenId to transfer to -function safeTransferChild( - uint256 _fromTokenId, - address _to, - address _childContract, - uint256 _childTokenId, - bytes _data -) - external; -``` - -This function authenticates `msg.sender` and transfers a child token from a top-down composable to a different address or to a different top-down composable. - -A child token is transferred to a different top-down composable if the `_to` address is a top-down composable contract and `bytes _data` is supplied an integer representing the parent tokenId. - -This function makes this call within it: - -```solidity -ERC721(_childContract).safeTransferFrom(this, _to, _childTokenId, _data); -``` - -#### transferChildToParent - -```solidity -/// @notice Transfer bottom-up composable child token from top-down composable to other ERC-721 token. -/// @param _fromTokenId The owning token to transfer from. -/// @param _toContract The ERC-721 contract of the receiving token -/// @param _toToken The receiving token -/// @param _childContract The bottom-up composable contract of the child token. -/// @param _childTokenId The token that is being transferred. -/// @param _data Additional data with no specified format -function transferChildToParent( - uint256 _fromTokenId, - address _toContract, - uint256 _toTokenId, - address _childContract, - uint256 _childTokenId, - bytes _data -) - external -``` - -This function authenticates `msg.sender` and transfers a child bottom-up composable token from a top-down composable to a different ERC-721 token. This function can only be used when the child token is a bottom-up composable token. It is designed to transfer a bottom-up composable token from a top-down composable to an ERC-721 token (bottom-up style) in one transaction. - -This function makes this call within it: - -```solidity -ERC998ERC721BottomUp(_childContract).transferToParent( - address(this), - _toContract, - _toTokenId, - _childTokenId, - _data -); -``` - -#### getChild - -```solidity -/// @notice Get a child token from an ERC-721 contract. -/// @param _from The address that owns the child token. -/// @param _tokenId The token that becomes the parent owner -/// @param _childContract The ERC-721 contract of the child token -/// @param _childTokenId The tokenId of the child token -function getChild( - address _from, - uint256 _tokenId, - address _childContract, - uint256 _childTokenId -) - external; -``` - -This function is used to transfer an ERC-721 token when its contract does not have a `safeTransferChild(uint256 _fromTokenId, address _to, address _childContract, uint256 _childTokenId, bytes _data)` function. - -A transfer with this function is done in two steps: - -1. The owner of the ERC-721 token calls `approve` or `setApprovalForAll` in the ERC-721 contract for the top-down composable contract. -2. The owner of the ERC-721 token calls `getChild` in the top-down composable contract for the ERC-721 token. - -The `getChild` function must authenticate that `msg.sender` is the owner of the ERC-721 token in the ERC-721 contract or is approved or an operator of the ERC-721 token in the ERC-721 contract. - -#### ERC-721 Top-Down Composable Enumeration - -Optional interface for top-down composable enumeration: - -```solidity -/// @dev The ERC-165 identifier for this interface is 0xa344afe4 -interface ERC998ERC721TopDownEnumerable { - - /// @notice Get the total number of child contracts with tokens that are owned by tokenId. - /// @param _tokenId The parent token of child tokens in child contracts - /// @return uint256 The total number of child contracts with tokens owned by tokenId. - function totalChildContracts(uint256 _tokenId) external view returns(uint256); - - /// @notice Get child contract by tokenId and index - /// @param _tokenId The parent token of child tokens in child contract - /// @param _index The index position of the child contract - /// @return childContract The contract found at the tokenId and index. - function childContractByIndex( - uint256 _tokenId, - uint256 _index - ) - external - view - returns (address childContract); - - /// @notice Get the total number of child tokens owned by tokenId that exist in a child contract. - /// @param _tokenId The parent token of child tokens - /// @param _childContract The child contract containing the child tokens - /// @return uint256 The total number of child tokens found in child contract that are owned by tokenId. - function totalChildTokens( - uint256 _tokenId, - address _childContract - ) - external - view - returns(uint256); - - /// @notice Get child token owned by tokenId, in child contract, at index position - /// @param _tokenId The parent token of the child token - /// @param _childContract The child contract of the child token - /// @param _index The index position of the child token. - /// @return childTokenId The child tokenId for the parent token, child token and index - function childTokenByIndex( - uint256 _tokenId, - address _childContract, - uint256 _index - ) - external - view - returns (uint256 childTokenId); -} -``` - -### ERC-20 Top-Down Composable - -ERC-20 top-down composables act as containers for ERC-20 tokens. - -ERC-20 top-down composables are ERC-721 tokens that can receive, hold and transfer ERC-20 tokens. - -There are two ways to transfer ERC-20 tokens to an ERC-20 Top-Down Composable: - -1. Use the `transfer(address _to, uint256 _value, bytes _data);` function from the `ERC-223` contract. The `_to` argument is the ERC-20 top-down composable contract address. The `_value` argument is how many ERC-20 tokens to transfer. The `bytes` argument holds the integer value of the top-down composable tokenId that receives the ERC-20 tokens. -2. Call `approve` in the ERC-20 contract for the ERC-20 top-down composable contract. Then call `getERC20(address _from, uint256 _tokenId, address _erc20Contract, uint256 _value)` from the ERC-20 top-down composable contract. - -The first way is for ERC-20 contracts that support the `ERC-223` standard. The second way is for contracts that do not. - -ERC-20 top-down composables implement the following interface: - -```solidity -/// @title `ERC998ERC20` Top-Down Composable Non-Fungible Token -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-998.md -/// Note: the ERC-165 identifier for this interface is 0x7294ffed -interface ERC998ERC20TopDown { - - /// @dev This emits when a token receives ERC-20 tokens. - /// @param _from The prior owner of the token. - /// @param _toTokenId The token that receives the ERC-20 tokens. - /// @param _erc20Contract The ERC-20 contract. - /// @param _value The number of ERC-20 tokens received. - event ReceivedERC20( - address indexed _from, - uint256 indexed _toTokenId, - address indexed _erc20Contract, - uint256 _value - ); - - /// @dev This emits when a token transfers ERC-20 tokens. - /// @param _tokenId The token that owned the ERC-20 tokens. - /// @param _to The address that receives the ERC-20 tokens. - /// @param _erc20Contract The ERC-20 contract. - /// @param _value The number of ERC-20 tokens transferred. - event TransferERC20( - uint256 indexed _fromTokenId, - address indexed _to, - address indexed _erc20Contract, - uint256 _value - ); - - /// @notice A token receives ERC-20 tokens - /// @param _from The prior owner of the ERC-20 tokens - /// @param _value The number of ERC-20 tokens received - /// @param _data Up to the first 32 bytes contains an integer which is the receiving tokenId. - function tokenFallback(address _from, uint256 _value, bytes _data) external; - - /// @notice Look up the balance of ERC-20 tokens for a specific token and ERC-20 contract - /// @param _tokenId The token that owns the ERC-20 tokens - /// @param _erc20Contract The ERC-20 contract - /// @return The number of ERC-20 tokens owned by a token from an ERC-20 contract - function balanceOfERC20( - uint256 _tokenId, - address _erc20Contract - ) - external - view - returns(uint256); - - /// @notice Transfer ERC-20 tokens to address - /// @param _tokenId The token to transfer from - /// @param _value The address to send the ERC-20 tokens to - /// @param _erc20Contract The ERC-20 contract - /// @param _value The number of ERC-20 tokens to transfer - function transferERC20( - uint256 _tokenId, - address _to, - address _erc20Contract, - uint256 _value - ) - external; - - /// @notice Transfer ERC-20 tokens to address or ERC-20 top-down composable - /// @param _tokenId The token to transfer from - /// @param _value The address to send the ERC-20 tokens to - /// @param _erc223Contract The `ERC-223` token contract - /// @param _value The number of ERC-20 tokens to transfer - /// @param _data Additional data with no specified format, can be used to specify tokenId to transfer to - function transferERC223( - uint256 _tokenId, - address _to, - address _erc223Contract, - uint256 _value, - bytes _data - ) - external; - - /// @notice Get ERC-20 tokens from ERC-20 contract. - /// @param _from The current owner address of the ERC-20 tokens that are being transferred. - /// @param _tokenId The token to transfer the ERC-20 tokens to. - /// @param _erc20Contract The ERC-20 token contract - /// @param _value The number of ERC-20 tokens to transfer - function getERC20( - address _from, - uint256 _tokenId, - address _erc20Contract, - uint256 _value - ) - external; -} -``` - -#### tokenFallback - -```solidity -/// @notice A token receives ERC-20 tokens -/// @param _from The prior owner of the ERC-20 tokens -/// @param _value The number of ERC-20 tokens received -/// @param _data Up to the first 32 bytes contains an integer which is the receiving tokenId. -function tokenFallback(address _from, uint256 _value, bytes _data) external; -``` - -This function comes from the `ERC-223` which is an extension of the ERC-20 standard. This function is called on the receiving contract from the sending contract when ERC-20 tokens are transferred. This function is how the ERC-20 top-down composable contract gets notified that one of its tokens received ERC-20 tokens. Which token received ERC-20 tokens is specified in the `_data` parameter. - -#### `balanceOfERC20` - -```solidity -/// @notice Look up the balance of ERC-20 tokens for a specific token and ERC-20 contract -/// @param _tokenId The token that owns the ERC-20 tokens -/// @param _erc20Contract The ERC-20 contract -/// @return The number of ERC-20 tokens owned by a token from an ERC-20 contract -function balanceOfERC20( - uint256 _tokenId, - address _erc20Contract -) - external - view - returns(uint256); -``` - -Gets the balance of ERC-20 tokens owned by a token from a specific ERC-20 contract. - -#### `transferERC20` - -```solidity -/// @notice Transfer ERC-20 tokens to address -/// @param _tokenId The token to transfer from -/// @param _value The address to send the ERC-20 tokens to -/// @param _erc20Contract The ERC-20 contract -/// @param _value The number of ERC-20 tokens to transfer -function transferERC20( - uint256 _tokenId, - address _to, - address _erc20Contract, - uint256 _value -) - external; -``` - -This is used to transfer ERC-20 tokens from a token to an address. This function calls `ERC20(_erc20Contract).transfer(_to, _value)`; - -This function must authenticate `msg.sender`. - -#### `transferERC223` - -```solidity - /// @notice Transfer ERC-20 tokens to address or ERC-20 top-down composable - /// @param _tokenId The token to transfer from - /// @param _value The address to send the ERC-20 tokens to - /// @param _erc223Contract The `ERC-223` token contract - /// @param _value The number of ERC-20 tokens to transfer - /// @param _data Additional data with no specified format, can be used to specify tokenId to transfer to - function transferERC223( - uint256 _tokenId, - address _to, - address _erc223Contract, - uint256 _value, - bytes _data - ) - external; -``` - -This function is from the `ERC-223`. It is used to transfer ERC-20 tokens from a token to an address or to another token by putting an integer token value in the `_data` argument. - -This function must authenticate `msg.sender`. - -#### `getERC20` - -```solidity -/// @notice Get ERC-20 tokens from ERC-20 contract. -/// @param _from The current owner address of the ERC-20 tokens that are being transferred. -/// @param _tokenId The token to transfer the ERC-20 tokens to. -/// @param _erc20Contract The ERC-20 token contract -/// @param _value The number of ERC-20 tokens to transfer -function getERC20( - address _from, - uint256 _tokenId, - address _erc20Contract, - uint256 _value -) - external; -``` - -This function is used to transfer ERC-20 tokens to an ERC-20 top-down composable when an ERC-20 contract does not have a `transferERC223(uint256 _tokenId, address _to, address _erc223Contract, uint256 _value, bytes _data)` function. - -Before this function can be used the ERC-20 top-down composable contract address must be approved in the ERC-20 contract to transfer the ERC-20 tokens. - -This function must authenticate that `msg.sender` equals `_from` or has been approved in the ERC-20 contract. - -#### ERC-20 Top-Down Composable Enumeration - -Optional interface for top-down composable enumeration: - -```solidity -/// @dev The ERC-165 identifier for this interface is 0xc5fd96cd -interface ERC998ERC20TopDownEnumerable { - - /// @notice Get the number of ERC-20 contracts that token owns ERC-20 tokens from - /// @param _tokenId The token that owns ERC-20 tokens. - /// @return uint256 The number of ERC-20 contracts - function totalERC20Contracts(uint256 _tokenId) external view returns(uint256); - - /// @notice Get an ERC-20 contract that token owns ERC-20 tokens from by index - /// @param _tokenId The token that owns ERC-20 tokens. - /// @param _index The index position of the ERC-20 contract. - /// @return address The ERC-20 contract - function erc20ContractByIndex( - uint256 _tokenId, - uint256 _index - ) - external - view - returns(address); -} -``` - -### ERC-721 Bottom-Up Composable - -ERC-721 bottom-up composables are ERC-721 tokens that attach themselves to other ERC-721 tokens. - -ERC-721 bottom-up composable contracts store the owning address of a token and the parent tokenId if any. - -```solidity -/// @title `ERC998ERC721` Bottom-Up Composable Non-Fungible Token -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-998.md -/// Note: the ERC-165 identifier for this interface is 0xa1b23002 -interface ERC998ERC721BottomUp { - - /// @dev This emits when a token is transferred to an ERC-721 token - /// @param _toContract The contract the token is transferred to - /// @param _toTokenId The token the token is transferred to - /// @param _tokenId The token that is transferred - event TransferToParent( - address indexed _toContract, - uint256 indexed _toTokenId, - uint256 _tokenId - ); - - /// @dev This emits when a token is transferred from an ERC-721 token - /// @param _fromContract The contract the token is transferred from - /// @param _fromTokenId The token the token is transferred from - /// @param _tokenId The token that is transferred - event TransferFromParent( - address indexed _fromContract, - uint256 indexed _fromTokenId, - uint256 _tokenId - ); - - /// @notice Get the root owner of tokenId. - /// @param _tokenId The token to query for a root owner address - /// @return rootOwner The root owner at the top of tree of tokens and ERC-998 magic value. - function rootOwnerOf(uint256 _tokenId) external view returns (bytes32 rootOwner); - - /// @notice Get the owner address and parent token (if there is one) of a token - /// @param _tokenId The tokenId to query. - /// @return tokenOwner The owner address of the token - /// @return parentTokenId The parent owner of the token and ERC-998 magic value - /// @return isParent True if parentTokenId is a valid parent tokenId and false if there is no parent tokenId - function tokenOwnerOf( - uint256 _tokenId - ) - external - view - returns ( - bytes32 tokenOwner, - uint256 parentTokenId, - bool isParent - ); - - /// @notice Transfer token from owner address to a token - /// @param _from The owner address - /// @param _toContract The ERC-721 contract of the receiving token - /// @param _toToken The receiving token - /// @param _data Additional data with no specified format - function transferToParent( - address _from, - address _toContract, - uint256 _toTokenId, - uint256 _tokenId, - bytes _data - ) - external; - - /// @notice Transfer token from a token to an address - /// @param _fromContract The address of the owning contract - /// @param _fromTokenId The owning token - /// @param _to The address the token is transferred to. - /// @param _tokenId The token that is transferred - /// @param _data Additional data with no specified format - function transferFromParent( - address _fromContract, - uint256 _fromTokenId, - address _to, - uint256 _tokenId, - bytes _data - ) - external; - - /// @notice Transfer a token from a token to another token - /// @param _fromContract The address of the owning contract - /// @param _fromTokenId The owning token - /// @param _toContract The ERC-721 contract of the receiving token - /// @param _toToken The receiving token - /// @param _tokenId The token that is transferred - /// @param _data Additional data with no specified format - function transferAsChild( - address _fromContract, - uint256 _fromTokenId, - address _toContract, - uint256 _toTokenId, - uint256 _tokenId, - bytes _data - ) - external; -} -``` - -#### `rootOwnerOf` - -```solidity -/// @notice Get the root owner of tokenId. -/// @param _tokenId The token to query for a root owner address -/// @return rootOwner The root owner at the top of tree of tokens and ERC-998 magic value. -function rootOwnerOf(uint256 _tokenId) public view returns (bytes32 rootOwner); -``` - -This function traverses token owners until the the root owner address of `_tokenId` is found. - -The first 4 bytes of rootOwner contain the ERC-998 magic value `0xcd740db5`. The last 20 bytes contain the root owner address. - -The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a `rootOwnerOf` function. The magic value is used in such calls to ensure a valid return value is received. - -If it is unknown whether a contract has the `rootOwnerOf` function then the first four bytes of the `rootOwner` return value must be compared to `0xcd740db5`. - -`0xcd740db5` is equal to: - -```solidity -this.rootOwnerOf.selector ^ this.rootOwnerOfChild.selector ^ -this.tokenOwnerOf.selector ^ this.ownerOfChild.selector; -``` - -Here is an example of a value returned by `rootOwnerOf`. -`0xcd740db50000000000000000e5240103e1ff986a2c8ae6b6728ffe0d9a395c59` - -#### tokenOwnerOf - -```solidity -/// @notice Get the owner address and parent token (if there is one) of a token -/// @param _tokenId The tokenId to query. -/// @return tokenOwner The owner address of the token and ERC-998 magic value. -/// @return parentTokenId The parent owner of the token -/// @return isParent True if parentTokenId is a valid parent tokenId and false if there is no parent tokenId -function tokenOwnerOf( - uint256 _tokenId -) - external - view - returns ( - bytes32 tokenOwner, - uint256 parentTokenId, - bool isParent - ); -``` - -This function is used to get the owning address and parent tokenId of a token if there is one stored in the contract. - -If `isParent` is true then `tokenOwner` is the owning ERC-721 contract address and `parentTokenId` is a valid parent tokenId. If `isParent` is false then `tokenOwner` is a user address and `parentTokenId` does not contain a valid parent tokenId and must be ignored. - -The first 4 bytes of `tokenOwner` contain the ERC-998 magic value `0xcd740db5`. The last 20 bytes contain the token owner address. - -The magic value is returned because this function may be called on contracts when it is unknown if the contracts have a `tokenOwnerOf` function. The magic value is used in such calls to ensure a valid return value is received. - -If it is unknown whether a contract has the `rootOwnerOf` function then the first four bytes of the `tokenOwner` return value must be compared to `0xcd740db5`. - -#### transferToParent - -```solidity -/// @notice Transfer token from owner address to a token -/// @param _from The owner address -/// @param _toContract The ERC-721 contract of the receiving token -/// @param _toToken The receiving token -/// @param _data Additional data with no specified format -function transferToParent( - address _from, - address _toContract, - uint256 _toTokenId, - uint256 _tokenId, - bytes _data -) - external; -``` - -This function is used to transfer a token from an address to a token. `msg.sender` must be authenticated. - -This function must check that `_toToken` exists in `_toContract` and throw if not. - -#### transferFromParent - -```solidity -/// @notice Transfer token from a token to an address -/// @param _fromContract The address of the owning contract -/// @param _fromTokenId The owning token -/// @param _to The address the token is transferred to. -/// @param _tokenId The token that is transferred -/// @param _data Additional data with no specified format -function transferFromParent( - address _fromContract, - uint256 _fromTokenId, - address _to, - uint256 _tokenId, - bytes _data -) - external; -``` - -This function is used to transfer a token from a token to an address. `msg.sender` must be authenticated. - -This function must check that `_fromContract` and `_fromTokenId` own `_tokenId` and throw not. - -#### transferAsChild - -```solidity -/// @notice Transfer a token from a token to another token -/// @param _fromContract The address of the owning contract -/// @param _fromTokenId The owning token -/// @param _toContract The ERC-721 contract of the receiving token -/// @param _toToken The receiving token -/// @param _tokenId The token that is transferred -/// @param _data Additional data with no specified format -function transferAsChild( - address _fromContract, - uint256 _fromTokenId, - address _toContract, - uint256 _toTokenId, - uint256 _tokenId, - bytes _data -) - external; -``` - -This function is used to transfer a token from a token to another token. `msg.sender` must be authenticated. - -This function must check that `_toToken` exists in `_toContract` and throw if not. - -This function must check that `_fromContract` and `_fromTokenId` own `_tokenId` and throw if not. - -#### ERC-721 Bottom-Up Composable Enumeration - -Optional interface for bottom-up composable enumeration: - -```solidity -/// @dev The ERC-165 identifier for this interface is 0x8318b539 -interface ERC998ERC721BottomUpEnumerable { - - /// @notice Get the number of ERC-721 tokens owned by parent token. - /// @param _parentContract The contract the parent ERC-721 token is from. - /// @param _parentTokenId The parent tokenId that owns tokens - // @return uint256 The number of ERC-721 tokens owned by parent token. - function totalChildTokens( - address _parentContract, - uint256 _parentTokenId - ) - external - view - returns (uint256); - - /// @notice Get a child token by index - /// @param _parentContract The contract the parent ERC-721 token is from. - /// @param _parentTokenId The parent tokenId that owns the token - /// @param _index The index position of the child token - /// @return uint256 The child tokenId owned by the parent token - function childTokenByIndex( - address _parentContract, - uint256 _parentTokenId, - uint256 _index - ) - external - view - returns (uint256); -} -``` - -### ERC-20 Bottom-Up Composable - -ERC-20 bottom-up composables are ERC-20 tokens that attach themselves to ERC-721 tokens, or are owned by a user address like standard ERC-20 tokens. - -When owned by an ERC-721 token, ERC-20 bottom-up composable contracts store the owning address of a token and the parent tokenId. ERC-20 bottom-up composables add several methods to the ERC-20 and `ERC-223` interfaces allowing for querying the balance of parent tokens, and transferring tokens to, from, and between parent tokens. - -This functionality can be implemented by adding one additional mapping to track balances of tokens, in addition to the standard mapping for tracking user address balances. - -```solidity -/// @dev This mapping tracks standard ERC20/`ERC-223` ownership, where an address owns -/// a particular amount of tokens. -mapping(address => uint) userBalances; - -/// @dev This additional mapping tracks ERC-998 ownership, where an ERC-721 token owns -/// a particular amount of tokens. This tracks contractAddres => tokenId => balance -mapping(address => mapping(uint => uint)) nftBalances; -``` - -The complete interface is below. - -```solidity -/// @title `ERC998ERC20` Bottom-Up Composable Fungible Token -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-998.md -/// Note: The ERC-165 identifier for this interface is 0xffafa991 -interface ERC998ERC20BottomUp { - - /// @dev This emits when a token is transferred to an ERC-721 token - /// @param _toContract The contract the token is transferred to - /// @param _toTokenId The token the token is transferred to - /// @param _amount The amount of tokens transferred - event TransferToParent( - address indexed _toContract, - uint256 indexed _toTokenId, - uint256 _amount - ); - - /// @dev This emits when a token is transferred from an ERC-721 token - /// @param _fromContract The contract the token is transferred from - /// @param _fromTokenId The token the token is transferred from - /// @param _amount The amount of tokens transferred - event TransferFromParent( - address indexed _fromContract, - uint256 indexed _fromTokenId, - uint256 _amount - ); - - /// @notice Get the balance of a non-fungible parent token - /// @param _tokenContract The contract tracking the parent token - /// @param _tokenId The ID of the parent token - /// @return amount The balance of the token - function balanceOfToken( - address _tokenContract, - uint256 _tokenId - ) - external - view - returns (uint256 amount); - - /// @notice Transfer tokens from owner address to a token - /// @param _from The owner address - /// @param _toContract The ERC-721 contract of the receiving token - /// @param _toToken The receiving token - /// @param _amount The amount of tokens to transfer - function transferToParent( - address _from, - address _toContract, - uint256 _toTokenId, - uint256 _amount - ) - external; - - /// @notice Transfer token from a token to an address - /// @param _fromContract The address of the owning contract - /// @param _fromTokenId The owning token - /// @param _to The address the token is transferred to - /// @param _amount The amount of tokens to transfer - function transferFromParent( - address _fromContract, - uint256 _fromTokenId, - address _to, - uint256 _amount - ) - external; - - /// @notice Transfer token from a token to an address, using `ERC-223` semantics - /// @param _fromContract The address of the owning contract - /// @param _fromTokenId The owning token - /// @param _to The address the token is transferred to - /// @param _amount The amount of tokens to transfer - /// @param _data Additional data with no specified format, can be used to specify the sender tokenId - function transferFromParentERC223( - address _fromContract, - uint256 _fromTokenId, - address _to, - uint256 _amount, - bytes _data - ) - external; - - /// @notice Transfer a token from a token to another token - /// @param _fromContract The address of the owning contract - /// @param _fromTokenId The owning token - /// @param _toContract The ERC-721 contract of the receiving token - /// @param _toToken The receiving token - /// @param _amount The amount tokens to transfer - function transferAsChild( - address _fromContract, - uint256 _fromTokenId, - address _toContract, - uint256 _toTokenId, - uint256 _amount - ) - external; -} -``` - -#### balanceOfToken - -```solidity -/// @notice Get the balance of a non-fungible parent token -/// @param _tokenContract The contract tracking the parent token -/// @param _tokenId The ID of the parent token -/// @return amount The balance of the token -function balanceOfToken( - address _tokenContract, - uint256 _tokenId -) - external - view - returns (uint256 amount); -``` - -This function returns the balance of a non-fungible token. It mirrors the standard ERC-20 method `balanceOf`, but accepts the address of the parent token's contract, and the parent token's ID. This method behaves identically to `balanceOf`, but checks for ownership by ERC-721 tokens rather than user addresses. - -#### `transferToParent` - -```solidity -/// @notice Transfer tokens from owner address to a token -/// @param _from The owner address -/// @param _toContract The ERC-721 contract of the receiving token -/// @param _toToken The receiving token -/// @param _amount The amount of tokens to transfer -function transferToParent( - address _from, - address _toContract, - uint256 _toTokenId, - uint256 _amount -) - external; -``` - -This function transfers an amount of tokens from a user address to an ERC-721 token. This function MUST ensure that the recipient contract implements ERC-721 using the ERC-165 `supportsInterface` function. This function SHOULD ensure that the recipient token actually exists, by calling `ownerOf` on the recipient token's contract, and ensuring it neither throws nor returns the zero address. This function MUST emit the `TransferToParent` event upon a successful transfer (in addition to the standard ERC-20 `Transfer` event!). This function MUST throw if the `_from` account balance does not have enough tokens to spend. - -#### `transferFromParent` - -```solidity -/// @notice Transfer token from a token to an address -/// @param _fromContract The address of the owning contract -/// @param _fromTokenId The owning token -/// @param _to The address the token is transferred to -/// @param _amount The amount of tokens to transfer -function transferFromParent( - address _fromContract, - uint256 _fromTokenId, - address _to, - uint256 _amount -) - external; -``` - -This function transfers an amount of tokens from an ERC-721 token to an address. This function MUST emit the `TransferFromParent` event upon a successful transfer (in addition to the standard ERC-20 `Transfer` event!). This function MUST throw if the balance of the sender ERC-721 token is less than the `_amount` specified. This function MUST verify that the `msg.sender` owns the sender ERC-721 token, and MUST throw otherwise. - -#### `transferFromParentERC223` - -```solidity -/// @notice Transfer token from a token to an address, using `ERC-223` semantics -/// @param _fromContract The address of the owning contract -/// @param _fromTokenId The owning token -/// @param _to The address the token is transferred to -/// @param _amount The amount of tokens to transfer -/// @param _data Additional data with no specified format, can be used to specify the sender tokenId -function transferFromParentERC223( - address _fromContract, - uint256 _fromTokenId, - address _to, - uint256 _amount, - bytes _data -) - external; -``` - -This function transfers an amount of tokens from an ERC-721 token to an address. This function has identical requirements to `transferFromParent`, except that it additionally MUST invoke `tokenFallback` on the recipient address, if the address is a contract, as specified by `ERC-223`. - -#### transferAsChild 1 - -```solidity -/// @notice Transfer a token from a token to another token -/// @param _fromContract The address of the owning contract -/// @param _fromTokenId The owning token -/// @param _toContract The ERC-721 contract of the receiving token -/// @param _toToken The receiving token -/// @param _amount The amount tokens to transfer -function transferAsChild( - address _fromContract, - uint256 _fromTokenId, - address _toContract, - uint256 _toTokenId, - uint256 _amount -) - external; -``` - -This function transfers an amount of tokens from an ERC-721 token to another ERC-721 token. This function MUST emit BOTH the `TransferFromParent` and `TransferToParent` events (in addition to the standard ERC-20 `Transfer` event!). This function MUST throw if the balance of the sender ERC-721 token is less than the `_amount` specified. This function MUST verify that the `msg.sender` owns the sender ERC-721 token, and MUST throw otherwise. This function MUST ensure that the recipient contract implements ERC-721 using the ERC-165 `supportsInterface` function. This function SHOULD ensure that the recipient token actually exists, by calling `ownerOf` on the recipient token's contract, and ensuring it neither throws nor returns the zero address. - -### Notes - -For backwards-compatibility, implementations MUST emit the standard ERC-20 `Transfer` event when a transfer occurs, regardless of whether the sender and recipient are addresses or ERC-721 tokens. In the case that either sender or recipient are tokens, the corresponding parameter in the `Transfer` event SHOULD be the contract address of the token. - -Implementations MUST implement all ERC-20 and `ERC-223` functions in addition to the functions specified in this interface. - -## Rationale - -Two different kinds of composable (top-down and bottom-up) exist to handle different use cases. A regular ERC-721 token cannot own a top-down composable, but it can own a bottom-up composable. A bottom-up composable cannot own a regular ERC-721 but a top-down composable can own a regular ERC-721 token. Having multiple kinds of composables enable different token ownership possibilities. - -### Which Kind of Composable To Use? - -If you want to transfer regular ERC-721 tokens to non-fungible tokens, then use top-down composables. - -If you want to transfer non-fungible tokens to regular ERC-721 tokens then use bottom-up composables. - -### Explicit Transfer Parameters - -Every ERC-998 transfer function includes explicit parameters to specify the prior owner and the new owner of a token. Explicitly providing **from** and **to** is done intentionally to avoid situations where tokens are transferred in unintended ways. - -Here is an example of what could occur if **from** was not explicitly provided in transfer functions: -> An exchange contract is an approved operator in a specific composable contract for user A, user B and user C. -> -> User A transfers token 1 to user B. At the same time the exchange contract transfers token 1 to user C (with the implicit intention to transfer from user A). User B gets token 1 for a minute before it gets incorrectly transferred to user C. The second transfer should have failed but it didn't because no explicit **from** was provided to ensure that token 1 came from user A. - -## Backwards Compatibility - -Composables are designed to work with ERC-721, `ERC-223` and ERC-20 tokens. - -Some older ERC-721 contracts do not have a `safeTransferFrom` function. The `getChild` function can still be used to transfer a token to an ERC-721 top-down composable. - -If an ERC-20 contract does not have the `ERC-223` function `transfer(address _to, uint _value, bytes _data)` then the `getERC20` function can still be used to transfer ERC-20 tokens to an ERC-20 top-down composable. - -## Reference Implementation - -An implementation can be found here: `https://github.com/mattlockyer/composables-998` - -## Security Considerations - -Needs discussion. - - -## Copyright - -Copyright and related rights waived via [CC0](../LICENSE.md). - - - +This file was moved to https://github.com/ethereum/ercs/blob/master/ercs/erc-998.md diff --git a/assets/eip-1175/wallet.png b/assets/eip-1175/wallet.png deleted file mode 100644 index fa58376e1058ca..00000000000000 Binary files a/assets/eip-1175/wallet.png and /dev/null differ diff --git a/assets/eip-1175/workflow.png b/assets/eip-1175/workflow.png deleted file mode 100644 index 2b552babb8c5ed..00000000000000 Binary files a/assets/eip-1175/workflow.png and /dev/null differ diff --git a/assets/eip-1207/rationale.png b/assets/eip-1207/rationale.png deleted file mode 100644 index 1f698d8a1bed99..00000000000000 Binary files a/assets/eip-1207/rationale.png and /dev/null differ diff --git a/assets/eip-1438/avatar.png b/assets/eip-1438/avatar.png deleted file mode 100644 index 44204311ed63a6..00000000000000 Binary files a/assets/eip-1438/avatar.png and /dev/null differ diff --git a/assets/eip-1438/intro.png b/assets/eip-1438/intro.png deleted file mode 100644 index 68ceaa618205fd..00000000000000 Binary files a/assets/eip-1438/intro.png and /dev/null differ diff --git a/assets/eip-1438/wallet.png b/assets/eip-1438/wallet.png deleted file mode 100644 index 735c3f1766e26a..00000000000000 Binary files a/assets/eip-1438/wallet.png and /dev/null differ diff --git a/assets/eip-1613/sequence.png b/assets/eip-1613/sequence.png deleted file mode 100644 index 2c54c37e21c474..00000000000000 Binary files a/assets/eip-1613/sequence.png and /dev/null differ diff --git a/assets/eip-1822/proxy-diagram.png b/assets/eip-1822/proxy-diagram.png deleted file mode 100644 index 26747b2b31b3c9..00000000000000 Binary files a/assets/eip-1822/proxy-diagram.png and /dev/null differ diff --git a/assets/eip-1967/Sample-proxy-on-etherscan.png b/assets/eip-1967/Sample-proxy-on-etherscan.png deleted file mode 100644 index 7239b4f0eebebd..00000000000000 Binary files a/assets/eip-1967/Sample-proxy-on-etherscan.png and /dev/null differ diff --git a/assets/eip-2266/Example.sol b/assets/eip-2266/Example.sol deleted file mode 100644 index 4b0563c22d7be1..00000000000000 --- a/assets/eip-2266/Example.sol +++ /dev/null @@ -1,455 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Copyright (c) 2019 Chris Haoyu LIN, Runchao HAN, Jiangshan YU -// ERC2266 is compatible with ERC20 standard: https://theethereum.wiki/w/index.php/ERC20_Token_Standard -// naming style follows the guide: https://solidity.readthedocs.io/en/v0.5.11/style-guide.html#naming-styles - -pragma solidity ^0.5.11; - -contract ERC20 { - function totalSupply() public view returns (uint); - function balanceOf(address tokenOwner) public view returns (uint balance); - function allowance(address tokenOwner, address spender) public view returns (uint remaining); - function transfer(address to, uint tokens) public returns (bool success); - function approve(address spender, uint tokens) public returns (bool success); - function transferFrom(address from, address to, uint tokens) public returns (bool success); - event Transfer(address indexed from, address indexed to, uint tokens); - event Approval(address indexed tokenOwner, address indexed spender, uint tokens); -} - -contract Example -{ - enum AssetState { Empty, Filled, Redeemed, Refunded } - - struct Swap { - bytes32 secretHash; - bytes32 secret; - address payable initiator; - address payable participant; - address tokenA; - address tokenB; - } - - struct InitiatorAsset { - uint256 amount; - uint256 refundTimestamp; - AssetState state; - } - - struct ParticipantAsset { - uint256 amount; - uint256 refundTimestamp; - AssetState state; - } - - struct Premium { - uint256 amount; - uint256 refundTimestamp; - AssetState state; - } - - mapping(bytes32 => Swap) public swap; - mapping(bytes32 => InitiatorAsset) public initiatorAsset; - mapping(bytes32 => ParticipantAsset) public participantAsset; - mapping(bytes32 => Premium) public premium; - - event SetUp( - bytes32 secretHash, - address initiator, - address participant, - address tokenA, - address tokenB, - uint256 initiatorAssetAmount, - uint256 participantAssetAmount, - uint256 premiumAmount - ); - - event Initiated( - uint256 initiateTimestamp, - bytes32 secretHash, - address initiator, - address participant, - address initiatorAssetToken, - uint256 initiatorAssetAmount, - uint256 initiatorAssetRefundTimestamp - ); - - event PremiumFilled( - uint256 fillPremiumTimestamp, - bytes32 secretHash, - address initiator, - address participant, - address premiumToken, - uint256 premiumAmount, - uint256 premiumRefundTimestamp - ); - - event Participated( - uint256 participateTimestamp, - bytes32 secretHash, - address initiator, - address participant, - address participantAssetToken, - uint256 participantAssetAmount, - uint256 participantAssetRefundTimestamp - ); - - event InitiatorAssetRedeemed( - uint256 redeemTimestamp, - bytes32 secretHash, - bytes32 secret, - address redeemer, - address assetToken, - uint256 amount - ); - - event ParticipantAssetRedeemed( - uint256 redeemTimestamp, - bytes32 secretHash, - bytes32 secret, - address redeemer, - address assetToken, - uint256 amount - ); - - event InitiatorAssetRefunded( - uint256 refundTimestamp, - bytes32 secretHash, - address refunder, - address assetToken, - uint256 amount - ); - - event ParticipantAssetRefunded( - uint256 refundTimestamp, - bytes32 secretHash, - address refunder, - address assetToken, - uint256 amount - ); - - event PremiumRedeemed( - uint256 redeemTimestamp, - bytes32 secretHash, - address redeemer, - address token, - uint256 amount - ); - - event PremiumRefunded( - uint256 refundTimestamp, - bytes32 secretHash, - address refunder, - address token, - uint256 amount - ); - - constructor() public {} - - modifier isInitiatorAssetEmptyState(bytes32 secretHash) { - require(initiatorAsset[secretHash].state == AssetState.Empty); - _; - } - - modifier isParticipantAssetEmptyState(bytes32 secretHash) { - require(participantAsset[secretHash].state == AssetState.Empty); - _; - } - - modifier isPremiumEmptyState(bytes32 secretHash) { - require(premium[secretHash].state == AssetState.Empty); - _; - } - - modifier canSetup(bytes32 secretHash) { - require(initiatorAsset[secretHash].state == AssetState.Empty); - require(participantAsset[secretHash].state == AssetState.Empty); - require(premium[secretHash].state == AssetState.Empty); - _; - } - - modifier canInitiate(bytes32 secretHash) { - require(swap[secretHash].initiator == msg.sender); - require(initiatorAsset[secretHash].state == AssetState.Empty); - require(ERC20(swap[secretHash].tokenA).balanceOf(msg.sender) >= initiatorAsset[secretHash].amount); - _; - } - - modifier canFillPremium(bytes32 secretHash) { - require(swap[secretHash].initiator == msg.sender); - require(premium[secretHash].state == AssetState.Empty); - require(ERC20(swap[secretHash].tokenB).balanceOf(msg.sender) >= premium[secretHash].amount); - _; - } - - modifier canParticipate(bytes32 secretHash) { - require(swap[secretHash].participant == msg.sender); - require(participantAsset[secretHash].state == AssetState.Empty); - require(premium[secretHash].state == AssetState.Filled); - require(ERC20(swap[secretHash].tokenB).balanceOf(msg.sender) >= participantAsset[secretHash].amount); - _; - } - - modifier checkRefundTimestampOverflow(uint256 refundTime) { - uint256 refundTimestamp = block.timestamp + refundTime; - require(refundTimestamp > block.timestamp, "calc refundTimestamp overflow"); - require(refundTimestamp > refundTime, "calc refundTimestamp overflow"); - _; - } - - modifier isAssetRedeemable(bytes32 secretHash, bytes32 secret) { - if (swap[secretHash].initiator == msg.sender) { - require(initiatorAsset[secretHash].state == AssetState.Filled); - require(block.timestamp <= initiatorAsset[secretHash].refundTimestamp); - } else { - require(swap[secretHash].participant == msg.sender); - require(participantAsset[secretHash].state == AssetState.Filled); - require(block.timestamp <= participantAsset[secretHash].refundTimestamp); - } - require(sha256(abi.encodePacked(secret)) == secretHash); - _; - } - - modifier isAssetRefundable(bytes32 secretHash) { - if (swap[secretHash].initiator == msg.sender) { - require(initiatorAsset[secretHash].state == AssetState.Filled); - require(block.timestamp > initiatorAsset[secretHash].refundTimestamp); - } else { - require(swap[secretHash].participant == msg.sender); - require(participantAsset[secretHash].state == AssetState.Filled); - require(block.timestamp > participantAsset[secretHash].refundTimestamp); - } - _; - } - - modifier isPremiumFilledState(bytes32 secretHash) { - require(premium[secretHash].state == AssetState.Filled); - _; - } - - // Premium is redeemable for Bob if Bob participates and redeem - // before premium's timelock expires - modifier isPremiumRedeemable(bytes32 secretHash) { - // the participant invokes this method to redeem the premium - require(swap[secretHash].participant == msg.sender); - // the premium should be deposited - require(premium[secretHash].state == AssetState.Filled); - // if Bob participates, which means participantAsset will be: Filled -> (Redeemed/Refunded) - require(participantAsset[secretHash].state == AssetState.Refunded || participantAsset[secretHash].state == AssetState.Redeemed); - // the premium timelock should not be expired - require(block.timestamp <= premium[secretHash].refundTimestamp); - _; - } - - // Premium is refundable for Alice only when Alice initiates - // but Bob does not participate after premium's timelock expires - modifier isPremiumRefundable(bytes32 secretHash) { - // the initiator invokes this method to refund the premium - require(swap[secretHash].initiator == msg.sender); - // the premium should be deposited - require(premium[secretHash].state == AssetState.Filled); - // asset2 should be empty - // which means Bob does not participate - require(participantAsset[secretHash].state == AssetState.Empty); - require(block.timestamp > premium[secretHash].refundTimestamp); - _; - } - - function setup(bytes32 secretHash, - address payable initiator, - address tokenA, - address tokenB, - uint256 initiatorAssetAmount, - address payable participant, - uint256 participantAssetAmount, - uint256 premiumAmount) - public - payable - canSetup(secretHash) - { - swap[secretHash].secretHash = secretHash; - swap[secretHash].initiator = initiator; - swap[secretHash].participant = participant; - swap[secretHash].tokenA = tokenA; - swap[secretHash].tokenB = tokenB; - initiatorAsset[secretHash].amount = initiatorAssetAmount; - initiatorAsset[secretHash].state = AssetState.Empty; - participantAsset[secretHash].amount = participantAssetAmount; - participantAsset[secretHash].state = AssetState.Empty; - premium[secretHash].amount = premiumAmount; - premium[secretHash].state = AssetState.Empty; - - emit SetUp( - secretHash, - initiator, - participant, - tokenA, - tokenB, - initiatorAssetAmount, - participantAssetAmount, - premiumAmount - ); - } - - // Initiator needs to pay for the initiatorAsset(tokenA) with initiatorAssetAmount - // Initiator will also need to call tokenA.approve(this_contract_address, initiatorAssetAmount) in advance - function initiate(bytes32 secretHash, uint256 assetRefundTime) - public - payable - canInitiate(secretHash) - checkRefundTimestampOverflow(assetRefundTime) - { - ERC20(swap[secretHash].tokenA).transferFrom(swap[secretHash].initiator, address(this), initiatorAsset[secretHash].amount); - initiatorAsset[secretHash].state = AssetState.Filled; - initiatorAsset[secretHash].refundTimestamp = block.timestamp + assetRefundTime; - - emit Initiated( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].participant, - swap[secretHash].tokenA, - initiatorAsset[secretHash].amount, - initiatorAsset[secretHash].refundTimestamp - ); - } - - // Initiator needs to pay for the premium(tokenB) with premiumAmount - // Initiator will also need to call tokenB.approve(this_contract_address, premiumAmount) in advance - function fillPremium(bytes32 secretHash, uint256 premiumRefundTime) - public - payable - canFillPremium(secretHash) - checkRefundTimestampOverflow(premiumRefundTime) - { - ERC20(swap[secretHash].tokenB).transferFrom(swap[secretHash].initiator, address(this), premium[secretHash].amount); - premium[secretHash].state = AssetState.Filled; - premium[secretHash].refundTimestamp = block.timestamp + premiumRefundTime; - - emit PremiumFilled( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].participant, - swap[secretHash].tokenB, - premium[secretHash].amount, - premium[secretHash].refundTimestamp - ); - } - - // Participant needs to pay for the participantAsset(tokenB) with participantAssetAmount - // Participant will also need to call tokenB.approve(this_contract_address, participantAssetAmount) in advance - function participate(bytes32 secretHash, uint256 assetRefundTime) - public - payable - canParticipate(secretHash) - checkRefundTimestampOverflow(assetRefundTime) - { - ERC20(swap[secretHash].tokenB).transferFrom(swap[secretHash].participant, address(this), participantAsset[secretHash].amount); - participantAsset[secretHash].state = AssetState.Filled; - participantAsset[secretHash].refundTimestamp = block.timestamp + assetRefundTime; - - emit Participated( - block.timestamp, - secretHash, - swap[secretHash].initiator, - msg.sender, - swap[secretHash].tokenB, - participantAsset[secretHash].amount, - participantAsset[secretHash].refundTimestamp - ); - } - - function redeemAsset(bytes32 secret, bytes32 secretHash) - public - isAssetRedeemable(secretHash, secret) - { - swap[secretHash].secret = secret; - if (swap[secretHash].initiator == msg.sender) { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, participantAsset[secretHash].amount); - participantAsset[secretHash].state = AssetState.Redeemed; - - emit ParticipantAssetRedeemed( - block.timestamp, - secretHash, - secret, - msg.sender, - swap[secretHash].tokenB, - participantAsset[secretHash].amount - ); - } else { - ERC20(swap[secretHash].tokenA).transfer(msg.sender, initiatorAsset[secretHash].amount); - initiatorAsset[secretHash].state = AssetState.Redeemed; - - emit InitiatorAssetRedeemed( - block.timestamp, - secretHash, - secret, - msg.sender, - swap[secretHash].tokenA, - initiatorAsset[secretHash].amount - ); - } - } - - function refundAsset(bytes32 secretHash) - public - isPremiumFilledState(secretHash) - isAssetRefundable(secretHash) - { - if (swap[secretHash].initiator == msg.sender) { - ERC20(swap[secretHash].tokenA).transfer(msg.sender, initiatorAsset[secretHash].amount); - initiatorAsset[secretHash].state = AssetState.Refunded; - - emit InitiatorAssetRefunded( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].tokenA, - initiatorAsset[secretHash].amount - ); - } else { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, participantAsset[secretHash].amount); - participantAsset[secretHash].state = AssetState.Refunded; - - emit ParticipantAssetRefunded( - block.timestamp, - secretHash, - msg.sender, - swap[secretHash].tokenB, - participantAsset[secretHash].amount - ); - } - } - - function redeemPremium(bytes32 secretHash) - public - isPremiumRedeemable(secretHash) - { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, premium[secretHash].amount); - premium[secretHash].state = AssetState.Redeemed; - - emit PremiumRefunded( - block.timestamp, - swap[secretHash].secretHash, - msg.sender, - swap[secretHash].tokenB, - premium[secretHash].amount - ); - } - - function refundPremium(bytes32 secretHash) - public - isPremiumRefundable(secretHash) - { - ERC20(swap[secretHash].tokenB).transfer(msg.sender, premium[secretHash].amount); - premium[secretHash].state = AssetState.Refunded; - - emit PremiumRefunded( - block.timestamp, - swap[secretHash].secretHash, - msg.sender, - swap[secretHash].tokenB, - premium[secretHash].amount - ); - } -} diff --git a/assets/eip-2535/Contributors.md b/assets/eip-2535/Contributors.md deleted file mode 100644 index de9097ea80fd7a..00000000000000 --- a/assets/eip-2535/Contributors.md +++ /dev/null @@ -1,79 +0,0 @@ -## Contributors - -* Andrew Redden (@androolloyd) -* Patrick Gallagher (@pi0neerpat) -* Leo Alt (@leonardoalt) -* Santiago Palladino (@spalladino) -* William Entriken (@fulldecent) -* Gonçalo Sá (@GNSPS) -* Brian Burns (@Droopy78) -* Ramesh Nair(@hiddentao) -* Jules Goddard (@JulesGoddard) -* Micah Zoltu (@MicahZoltu) -* Sam Wilson (@SamWilsn) -* William Morriss (@wjmelements) -* Zachary (@Remscar) -* Patrick Collins (@PatrickAlphaC) -* Hadrien Croubois (@Amxx) -* (@farreldarian) -* Kelvin Schoofs (@SchoofsKelvin) -* (@0xpApaSmURf) -* Nathan Sala (@nataouze) -* Anders Torbjornsen (@anders-torbjornsen) -* (@Pandapip1) -* Xavier Iturralde (@xibot) -* Coder Dan (@cinnabarhorse) -* GldnXross (@gldnxross) -* Christian Reitwiessner (@chriseth) -* Timidan (@Timidan) -* cyotee doge (@cyotee) -* Glory Praise Emmanuel (@emmaglorypraise) -* Ed Zynda (@ezynda3) -* Arthur Nesbitt (@nesbitta) -* Cliff Hall (@cliffhall) -* Tyler Scott Ward (@tylerscottward) -* Troy Murray (@DannyDesert) -* Dan Finlay (@danfinlay) -* Theodore Georgas (@tgeorgas) -* Aditya Palepu (@apalepu23) -* Ronan Sandford (@wighawag) -* Markus Waas (@gorgos) -* Blessing Emah (@BlessingEmah) -* Andrew Edwards -* Ashwin Yardi (@ashwinYardi) -* Marco Castignoli (@marcocastignoli) -* Blaine Bublitz (@phated) -* Bearded -* Nick Barry (@ItsNickBarry) -* (@Vectorized) -* Rachit Srivastava (@rachit2501) -* Neeraj Kashyap (@zomglings) -* Zac Denham (@zdenham) -* JA (@ubinatus) -* Carter Carlson (@cartercarlson) -* James Sayer (@jamessayer98) -* Arpit Temani (@temaniarpit27) -* Parv Garg (@parv3213) -* Publius (@publiuss) -* Guy Hance (@guyhance) -* Payn (@Ayuilos) -* Luis Schliesske (@gitpusha) -* Hilmar Orth (@hilmarx) -* Matthieu Marie Joseph (@Gauddel) -* David Uzochukwu (@davidpius95) -* TJ VanSlooten (@tjvsx) -* 0xFluffyBeard (@0xFluffyBeard) -* Florian Pfeiffer (@FlorianPfeifferKanaloaNetwork) -* Mick de Graaf(@MickdeGraaf) -* Alessio Delmonti (@Alexintosh) -* Neirenoir (@Neirenoir) -* Evert Kors (@Evert0x) -* Patrick Kim (@pakim249CAL) -* Ersan YAKIT (@ersanyakit) -* Matias Arazi (@MatiArazi) -* Lucas Grasso Ramos (@LucasGrasso) -* Nikolay Angelov (@NikolayAngelov) -* John Reynolds (@gweiworld) -* Viraz Malhotra (@viraj124) -* Kemal Emre Ballı (@emrbli) -* Zack Peng (@zackpeng) diff --git a/assets/eip-2535/DiamondDiagram.png b/assets/eip-2535/DiamondDiagram.png deleted file mode 100644 index 43e49ccea59995..00000000000000 Binary files a/assets/eip-2535/DiamondDiagram.png and /dev/null differ diff --git a/assets/eip-2535/diamond.svg b/assets/eip-2535/diamond.svg deleted file mode 100644 index a2880579335bca..00000000000000 --- a/assets/eip-2535/diamond.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/eip-2535/diamondstorage1.png b/assets/eip-2535/diamondstorage1.png deleted file mode 100644 index 3889d62b8cd886..00000000000000 Binary files a/assets/eip-2535/diamondstorage1.png and /dev/null differ diff --git a/assets/eip-2535/facetreuse.png b/assets/eip-2535/facetreuse.png deleted file mode 100644 index 481145ec1d9216..00000000000000 Binary files a/assets/eip-2535/facetreuse.png and /dev/null differ diff --git a/assets/eip-2535/reference/Diamond.sol b/assets/eip-2535/reference/Diamond.sol deleted file mode 100644 index f38258741ada87..00000000000000 --- a/assets/eip-2535/reference/Diamond.sol +++ /dev/null @@ -1,589 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/******************************************************************************\ -* Author: Nick Mudge , Twitter/Github: @mudgen -* EIP-2535 Diamonds -/******************************************************************************/ - -// NOTE: -// To see the various things in this file in their proper directory structure -// please download the zip archive version of this reference implementation. -// The zip archive also includes a deployment script and tests. - -interface IDiamond { - enum FacetCutAction {Add, Replace, Remove} - // Add=0, Replace=1, Remove=2 - - struct FacetCut { - address facetAddress; - FacetCutAction action; - bytes4[] functionSelectors; - } - - event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); -} - -interface IDiamondCut is IDiamond { - /// @notice Add/replace/remove any number of functions and optionally execute - /// a function with delegatecall - /// @param _diamondCut Contains the facet addresses and function selectors - /// @param _init The address of the contract or facet to execute _calldata - /// @param _calldata A function call, including function selector and arguments - /// _calldata is executed with delegatecall on _init - function diamondCut( - FacetCut[] calldata _diamondCut, - address _init, - bytes calldata _calldata - ) external; -} - -/////////////////////////////////////////////// -// LibDiamond -// LibDiamond defines the diamond storage that is used by this reference -// implementation. -// LibDiamond contains internal functions and no external functions. -// LibDiamond internal functions are used by DiamondCutFacet, -// DiamondLoupeFacet and the diamond proxy contract (the Diamond contract). - -error NoSelectorsGivenToAdd(); -error NotContractOwner(address _user, address _contractOwner); -error NoSelectorsProvidedForFacetForCut(address _facetAddress); -error CannotAddSelectorsToZeroAddress(bytes4[] _selectors); -error NoBytecodeAtAddress(address _contractAddress, string _message); -error IncorrectFacetCutAction(uint8 _action); -error CannotAddFunctionToDiamondThatAlreadyExists(bytes4 _selector); -error CannotReplaceFunctionsFromFacetWithZeroAddress(bytes4[] _selectors); -error CannotReplaceImmutableFunction(bytes4 _selector); -error CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet(bytes4 _selector); -error CannotReplaceFunctionThatDoesNotExists(bytes4 _selector); -error RemoveFacetAddressMustBeZeroAddress(address _facetAddress); -error CannotRemoveFunctionThatDoesNotExist(bytes4 _selector); -error CannotRemoveImmutableFunction(bytes4 _selector); -error InitializationFunctionReverted(address _initializationContractAddress, bytes _calldata); - -library LibDiamond { - bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); - - struct FacetAddressAndSelectorPosition { - address facetAddress; - uint16 selectorPosition; - } - - struct DiamondStorage { - // function selector => facet address and selector position in selectors array - mapping(bytes4 => FacetAddressAndSelectorPosition) facetAddressAndSelectorPosition; - bytes4[] selectors; - mapping(bytes4 => bool) supportedInterfaces; - // owner of the contract - address contractOwner; - } - - function diamondStorage() internal pure returns (DiamondStorage storage ds) { - bytes32 position = DIAMOND_STORAGE_POSITION; - assembly { - ds.slot := position - } - } - - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - function setContractOwner(address _newOwner) internal { - DiamondStorage storage ds = diamondStorage(); - address previousOwner = ds.contractOwner; - ds.contractOwner = _newOwner; - emit OwnershipTransferred(previousOwner, _newOwner); - } - - function contractOwner() internal view returns (address contractOwner_) { - contractOwner_ = diamondStorage().contractOwner; - } - - function enforceIsContractOwner() internal view { - if(msg.sender != diamondStorage().contractOwner) { - revert NotContractOwner(msg.sender, diamondStorage().contractOwner); - } - } - - event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); - - // Internal function version of diamondCut - function diamondCut( - IDiamondCut.FacetCut[] memory _diamondCut, - address _init, - bytes memory _calldata - ) internal { - for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { - bytes4[] memory functionSelectors = _diamondCut[facetIndex].functionSelectors; - address facetAddress = _diamondCut[facetIndex].facetAddress; - if(functionSelectors.length == 0) { - revert NoSelectorsProvidedForFacetForCut(facetAddress); - } - IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; - if (action == IDiamond.FacetCutAction.Add) { - addFunctions(facetAddress, functionSelectors); - } else if (action == IDiamond.FacetCutAction.Replace) { - replaceFunctions(facetAddress, functionSelectors); - } else if (action == IDiamond.FacetCutAction.Remove) { - removeFunctions(facetAddress, functionSelectors); - } else { - revert IncorrectFacetCutAction(uint8(action)); - } - } - emit DiamondCut(_diamondCut, _init, _calldata); - initializeDiamondCut(_init, _calldata); - } - - function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { - if(_facetAddress == address(0)) { - revert CannotAddSelectorsToZeroAddress(_functionSelectors); - } - DiamondStorage storage ds = diamondStorage(); - uint16 selectorCount = uint16(ds.selectors.length); - enforceHasContractCode(_facetAddress, "LibDiamondCut: Add facet has no code"); - for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { - bytes4 selector = _functionSelectors[selectorIndex]; - address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress; - if(oldFacetAddress != address(0)) { - revert CannotAddFunctionToDiamondThatAlreadyExists(selector); - } - ds.facetAddressAndSelectorPosition[selector] = FacetAddressAndSelectorPosition(_facetAddress, selectorCount); - ds.selectors.push(selector); - selectorCount++; - } - } - - function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { - DiamondStorage storage ds = diamondStorage(); - if(_facetAddress == address(0)) { - revert CannotReplaceFunctionsFromFacetWithZeroAddress(_functionSelectors); - } - enforceHasContractCode(_facetAddress, "LibDiamondCut: Replace facet has no code"); - for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { - bytes4 selector = _functionSelectors[selectorIndex]; - address oldFacetAddress = ds.facetAddressAndSelectorPosition[selector].facetAddress; - // can't replace immutable functions -- functions defined directly in the diamond in this case - if(oldFacetAddress == address(this)) { - revert CannotReplaceImmutableFunction(selector); - } - if(oldFacetAddress == _facetAddress) { - revert CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet(selector); - } - if(oldFacetAddress == address(0)) { - revert CannotReplaceFunctionThatDoesNotExists(selector); - } - // replace old facet address - ds.facetAddressAndSelectorPosition[selector].facetAddress = _facetAddress; - } - } - - function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { - DiamondStorage storage ds = diamondStorage(); - uint256 selectorCount = ds.selectors.length; - if(_facetAddress != address(0)) { - revert RemoveFacetAddressMustBeZeroAddress(_facetAddress); - } - for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { - bytes4 selector = _functionSelectors[selectorIndex]; - FacetAddressAndSelectorPosition memory oldFacetAddressAndSelectorPosition = ds.facetAddressAndSelectorPosition[selector]; - if(oldFacetAddressAndSelectorPosition.facetAddress == address(0)) { - revert CannotRemoveFunctionThatDoesNotExist(selector); - } - - - // can't remove immutable functions -- functions defined directly in the diamond - if(oldFacetAddressAndSelectorPosition.facetAddress == address(this)) { - revert CannotRemoveImmutableFunction(selector); - } - // replace selector with last selector - selectorCount--; - if (oldFacetAddressAndSelectorPosition.selectorPosition != selectorCount) { - bytes4 lastSelector = ds.selectors[selectorCount]; - ds.selectors[oldFacetAddressAndSelectorPosition.selectorPosition] = lastSelector; - ds.facetAddressAndSelectorPosition[lastSelector].selectorPosition = oldFacetAddressAndSelectorPosition.selectorPosition; - } - // delete last selector - ds.selectors.pop(); - delete ds.facetAddressAndSelectorPosition[selector]; - } - } - - function initializeDiamondCut(address _init, bytes memory _calldata) internal { - if (_init == address(0)) { - return; - } - enforceHasContractCode(_init, "LibDiamondCut: _init address has no code"); - (bool success, bytes memory error) = _init.delegatecall(_calldata); - if (!success) { - if (error.length > 0) { - // bubble up error - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(error) - revert(add(32, error), returndata_size) - } - } else { - revert InitializationFunctionReverted(_init, _calldata); - } - } - } - - function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { - uint256 contractSize; - assembly { - contractSize := extcodesize(_contract) - } - if(contractSize == 0) { - revert NoBytecodeAtAddress(_contract, _errorMessage); - } - } -} - -/////////////////////////////////////////////// -// These facets are added to the diamond. -/////////////////////////////////////////////// - -contract DiamondCutFacet is IDiamondCut { - /// @notice Add/replace/remove any number of functions and optionally execute - /// a function with delegatecall - /// @param _diamondCut Contains the facet addresses and function selectors - /// @param _init The address of the contract or facet to execute _calldata - /// @param _calldata A function call, including function selector and arguments - /// _calldata is executed with delegatecall on _init - function diamondCut( - FacetCut[] calldata _diamondCut, - address _init, - bytes calldata _calldata - ) external override { - LibDiamond.enforceIsContractOwner(); - LibDiamond.diamondCut(_diamondCut, _init, _calldata); - } -} - -// The functions in DiamondLoupeFacet MUST be added to a diamond. -// The EIP-2535 Diamond standard requires these functions. - -interface IERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceId The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -// A loupe is a small magnifying glass used to look at diamonds. -// These functions look at diamonds -interface IDiamondLoupe { - /// These functions are expected to be called frequently - /// by tools. - - struct Facet { - address facetAddress; - bytes4[] functionSelectors; - } - - /// @notice Gets all facet addresses and their four byte function selectors. - /// @return facets_ Facet - function facets() external view returns (Facet[] memory facets_); - - /// @notice Gets all the function selectors supported by a specific facet. - /// @param _facet The facet address. - /// @return facetFunctionSelectors_ - function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory facetFunctionSelectors_); - - /// @notice Get all the facet addresses used by a diamond. - /// @return facetAddresses_ - function facetAddresses() external view returns (address[] memory facetAddresses_); - - /// @notice Gets the facet that supports the given selector. - /// @dev If facet is not found return address(0). - /// @param _functionSelector The function selector. - /// @return facetAddress_ The facet address. - function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_); -} - -contract DiamondLoupeFacet is IDiamondLoupe, IERC165 { - // Diamond Loupe Functions - //////////////////////////////////////////////////////////////////// - /// These functions are expected to be called frequently by tools. - // - // struct Facet { - // address facetAddress; - // bytes4[] functionSelectors; - // } - /// @notice Gets all facets and their selectors. - /// @return facets_ Facet - function facets() external override view returns (Facet[] memory facets_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - uint256 selectorCount = ds.selectors.length; - // create an array set to the maximum size possible - facets_ = new Facet[](selectorCount); - // create an array for counting the number of selectors for each facet - uint16[] memory numFacetSelectors = new uint16[](selectorCount); - // total number of facets - uint256 numFacets; - // loop through function selectors - for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { - bytes4 selector = ds.selectors[selectorIndex]; - address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; - bool continueLoop = false; - // find the functionSelectors array for selector and add selector to it - for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { - if (facets_[facetIndex].facetAddress == facetAddress_) { - facets_[facetIndex].functionSelectors[numFacetSelectors[facetIndex]] = selector; - numFacetSelectors[facetIndex]++; - continueLoop = true; - break; - } - } - // if functionSelectors array exists for selector then continue loop - if (continueLoop) { - continueLoop = false; - continue; - } - // create a new functionSelectors array for selector - facets_[numFacets].facetAddress = facetAddress_; - facets_[numFacets].functionSelectors = new bytes4[](selectorCount); - facets_[numFacets].functionSelectors[0] = selector; - numFacetSelectors[numFacets] = 1; - numFacets++; - } - for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { - uint256 numSelectors = numFacetSelectors[facetIndex]; - bytes4[] memory selectors = facets_[facetIndex].functionSelectors; - // setting the number of selectors - assembly { - mstore(selectors, numSelectors) - } - } - // setting the number of facets - assembly { - mstore(facets_, numFacets) - } - } - - /// @notice Gets all the function selectors supported by a specific facet. - /// @param _facet The facet address. - /// @return _facetFunctionSelectors The selectors associated with a facet address. - function facetFunctionSelectors(address _facet) external override view returns (bytes4[] memory _facetFunctionSelectors) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - uint256 selectorCount = ds.selectors.length; - uint256 numSelectors; - _facetFunctionSelectors = new bytes4[](selectorCount); - // loop through function selectors - for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { - bytes4 selector = ds.selectors[selectorIndex]; - address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; - if (_facet == facetAddress_) { - _facetFunctionSelectors[numSelectors] = selector; - numSelectors++; - } - } - // Set the number of selectors in the array - assembly { - mstore(_facetFunctionSelectors, numSelectors) - } - } - - /// @notice Get all the facet addresses used by a diamond. - /// @return facetAddresses_ - function facetAddresses() external override view returns (address[] memory facetAddresses_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - uint256 selectorCount = ds.selectors.length; - // create an array set to the maximum size possible - facetAddresses_ = new address[](selectorCount); - uint256 numFacets; - // loop through function selectors - for (uint256 selectorIndex; selectorIndex < selectorCount; selectorIndex++) { - bytes4 selector = ds.selectors[selectorIndex]; - address facetAddress_ = ds.facetAddressAndSelectorPosition[selector].facetAddress; - bool continueLoop = false; - // see if we have collected the address already and break out of loop if we have - for (uint256 facetIndex; facetIndex < numFacets; facetIndex++) { - if (facetAddress_ == facetAddresses_[facetIndex]) { - continueLoop = true; - break; - } - } - // continue loop if we already have the address - if (continueLoop) { - continueLoop = false; - continue; - } - // include address - facetAddresses_[numFacets] = facetAddress_; - numFacets++; - } - // Set the number of facet addresses in the array - assembly { - mstore(facetAddresses_, numFacets) - } - } - - /// @notice Gets the facet address that supports the given selector. - /// @dev If facet is not found return address(0). - /// @param _functionSelector The function selector. - /// @return facetAddress_ The facet address. - function facetAddress(bytes4 _functionSelector) external override view returns (address facetAddress_) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - facetAddress_ = ds.facetAddressAndSelectorPosition[_functionSelector].facetAddress; - } - - // This implements ERC-165. - function supportsInterface(bytes4 _interfaceId) external override view returns (bool) { - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - return ds.supportedInterfaces[_interfaceId]; - } -} - -/// @title ERC-173 Contract Ownership Standard -/// Note: the ERC-165 identifier for this interface is 0x7f5828d0 -/* is ERC165 */ -interface IERC173 { - /// @dev This emits when ownership of a contract changes. - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - /// @notice Get the address of the owner - /// @return owner_ The address of the owner. - function owner() external view returns (address owner_); - - /// @notice Set the address of the new owner of the contract - /// @dev Set _newOwner to address(0) to renounce any ownership. - /// @param _newOwner The address of the new owner of the contract - function transferOwnership(address _newOwner) external; -} - -contract OwnershipFacet is IERC173 { - function transferOwnership(address _newOwner) external override { - LibDiamond.enforceIsContractOwner(); - LibDiamond.setContractOwner(_newOwner); - } - - function owner() external override view returns (address owner_) { - owner_ = LibDiamond.contractOwner(); - } -} -/////////////////////////////////////////////// - -/////////////////////////////////////////////// -// DiamondInit -// This contract and function are used to initialize state variables and/or do other actions -// when the `diamondCut` function is called. -// It is expected that this contract is customized if you want to deploy your diamond -// with data from a deployment script. Use the init function to initialize state variables -// of your diamond. Add parameters to the init funciton if you need to. -// DiamondInit can be used during deployment or for upgrades. - -// Adding parameters to the `init` or other functions you add here can make a single deployed -// DiamondInit contract reusable accross upgrades, and can be used for multiple diamonds. - -contract DiamondInit { - - // You can add parameters to this function in order to pass in - // data to set your own state variables - function init() external { - // adding ERC165 data - LibDiamond.DiamondStorage storage ds = LibDiamond.diamondStorage(); - ds.supportedInterfaces[type(IERC165).interfaceId] = true; - ds.supportedInterfaces[type(IDiamondCut).interfaceId] = true; - ds.supportedInterfaces[type(IDiamondLoupe).interfaceId] = true; - ds.supportedInterfaces[type(IERC173).interfaceId] = true; - - // add your own state variables - // EIP-2535 specifies that the `diamondCut` function takes two optional - // arguments: address _init and bytes calldata _calldata - // These arguments are used to execute an arbitrary function using delegatecall - // in order to set state variables in the diamond during deployment or an upgrade - // More info in the EIP2535 Diamonds standard. - } -} - -/////////////////////////////////////////////// -// DiamondMultiInit -// This version of DiamondInit can be used to execute multiple initialization functions. -// It is expected that this contract is customized if you want to deploy or upgrade your diamond with it. - -error AddressAndCalldataLengthDoNotMatch(uint256 _addressesLength, uint256 _calldataLength); - -contract DiamondMultiInit { - - // This function is provided in the third parameter of the `diamondCut` function. - // The `diamondCut` function executes this function to execute multiple initializer functions for a single upgrade. - - function multiInit(address[] calldata _addresses, bytes[] calldata _calldata) external { - if(_addresses.length != _calldata.length) { - revert AddressAndCalldataLengthDoNotMatch(_addresses.length, _calldata.length); - } - for(uint i; i < _addresses.length; i++) { - LibDiamond.initializeDiamondCut(_addresses[i], _calldata[i]); - } - } -} - - -/////////////////////////////////////////////// -// Diamond -// The diamond proxy contract. - - -// When no function exists for function called -error FunctionNotFound(bytes4 _functionSelector); - -// This is used in diamond constructor -// more arguments are added to this struct -// this avoids stack too deep errors -struct DiamondArgs { - address owner; - address init; - bytes initCalldata; -} - -contract Diamond { - - // Remember to add the loupe functions from DiamondLoupeFacet to the diamond. - // The loupe functions are required by the EIP2535 Diamonds standard - - constructor(IDiamondCut.FacetCut[] memory _diamondCut, DiamondArgs memory _args) payable { - LibDiamond.setContractOwner(_args.owner); - LibDiamond.diamondCut(_diamondCut, _args.init, _args.initCalldata); - - // Code can be added here to perform actions and set state variables. - } - - // Find facet for function that is called and execute the - // function if a facet is found and return any value. - fallback() external payable { - LibDiamond.DiamondStorage storage ds; - bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; - // get diamond storage - assembly { - ds.slot := position - } - // get facet from function selector - address facet = ds.facetAddressAndSelectorPosition[msg.sig].facetAddress; - if(facet == address(0)) { - revert FunctionNotFound(msg.sig); - } - // Execute external function from facet using delegatecall and return any value. - assembly { - // copy function selector and any arguments - calldatacopy(0, 0, calldatasize()) - // execute function call using the facet - let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) - // get any return value - returndatacopy(0, 0, returndatasize()) - // return any return value or error back to the caller - switch result - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) - } - } - } - - receive() external payable {} -} diff --git a/assets/eip-2535/reference/EIP2535-Diamonds-Reference-Implementation.zip b/assets/eip-2535/reference/EIP2535-Diamonds-Reference-Implementation.zip deleted file mode 100644 index 9be228ed4d37c2..00000000000000 Binary files a/assets/eip-2535/reference/EIP2535-Diamonds-Reference-Implementation.zip and /dev/null differ diff --git a/assets/eip-2535/storage-examples/AppStorage.sol b/assets/eip-2535/storage-examples/AppStorage.sol deleted file mode 100644 index 69ca46e65a9101..00000000000000 --- a/assets/eip-2535/storage-examples/AppStorage.sol +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Diamond Storage is particularly good for isolating or compartmenting state variables to specific -// facets or functionality. This is great for creating modular facets that can be understood as their -// own units and be added to diamonds. A diamond with a lot of functionality is well organized and -// understandable if each of its facets can be understood in isolation. Diamond Storage helps make that -// possible. - -// However, you may want to share state variables specific to your application with facets that are specific -// to your application. It can get somewhat tedious to call a `diamondStorage()` function in every function -// that you want to access state variables. - -// `AppStorage` is a specialized version of Diamond Storage. It is a more convenient way to access -// application specific state variables that are shared among facets. - -// The pattern works in the following way: - -// 1. Define a struct called AppStorage that contains all the state variables specific to your application -// and that you plan to share with different facets. Store AppStorage in a file. Any of your facets can -// now import this file to access the state variables. - -struct AppStorage { - uint256 secondVar; - uint256 firstVar; - uint256 lastVar; - // add other state variables ... -} - - -// 2. In a facet that imports the AppStorage struct declare an AppStorage state variable called `s`. -// This should be the only state variable declared in the facet. - -// 3. In your facet you can now access all the state variables in AppStorage by prepending state variables -// with `s.`. Here is example code: - - -// import { AppStorage } from "./LibAppStorage.sol"; - -contract AFacet { - AppStorage internal s; - - function sumVariables() external { - s.lastVar = s.firstVar + s.secondVar; - } - - function getFirsVar() external view returns (uint256) { - return s.firstVar; - } - - function setLastVar(uint256 _newValue) external { - s.lastVar = _newValue; - } -} - -// Sharing AppStorage in another facet: - -// import { AppStorage } from "./LibAppStorage.sol"; - -contract SomeOtherFacet { - AppStorage internal s; - - function getLargerVar() external view returns (uint256) { - uint256 firstVar = s.firstVar; - uint256 secondVar = s.secondVar; - if(firstVar > secondVar) { - return firstVar; - } - else { - return secondVar; - } - } -} - -// Using the 's.' prefix to access AppStorage is a nice convention because it makes state variables -// concise, easy to access, and it distinguishes state variables from local variables and prevents -// name clashes/shadowing with local variables and function names. It helps identify and make -// explicit state variables in a convenient and concise way. AppStorage can be used in regualar -// contracts as well as proxy contracts, diamonds, implementation contracts, Solidity libraries and -// facets. - -// Since `AppStorage s` is the first and only state variable declared in facets its position in -// contract storage is `0`. This fact can be used to access AppStorage in Solidity libraries using -// diamond storage access. Here's an example of that: - -library LibAppStorage { - function appStorage() internal pure returns (AppStorage storage ds) { - assembly { ds.slot := 0 } - } - - function someFunction() internal { - AppStorage storage s = appStorage(); - s.firstVar = 8; - //... do more stuff - } -} - -// `AppStorage s` can be declared as the one and only state variable in facets or it can be declared in a -// contract that facets inherit. - -// AppStorage won't work if state variables are declared outside of AppStorage and outside of Diamond Storage. -// It is a common error for a facet to inherit a contract that declares state variables outside AppStorage and -// Diamond Storage. This causes a misalignment of state variables. - -// One downside is that state variables can't be declared public in structs so getter functions can't -// automatically be created this way. But it can be nice to make your own getter functions for -// state variables because it is explicit. - -// The rules for upgrading AppStorage are the same for Diamond Storage. These rules can be found at -// the end of the file ./DiamondStorage.sol \ No newline at end of file diff --git a/assets/eip-2535/storage-examples/DiamondStorage.sol b/assets/eip-2535/storage-examples/DiamondStorage.sol deleted file mode 100644 index b6fe0228f3a275..00000000000000 --- a/assets/eip-2535/storage-examples/DiamondStorage.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Diamond storage is a contract storage strategy that is used in proxy contracts and diamonds. - -// It greatly simplifies organizing and using state variables in proxy contracts and diamonds. - -// Diamond storage relies on Solidity structs that contain sets of state variables. - -// A struct can be defined with state variables and then used in a particular position in contract -// storage. The position can be determined by a hash of a unique string or other data. The string -// acts like a namespace for the struct. For example a diamond storage string for a struct could -// be 'com.mycompany.projectx.mystruct'. That will look familiar to you if you have used programming -// languages that use namespaces. - -// Namespaces are used in some programming languages to package data and code together as separate -// reusable units. Diamond storage packages sets of state variables as separate, reusable data units -// in contract storage. - -// Let's look at a simple example of diamond storage: - -library LibERC721 { - bytes32 constant ERC721_POSITION = keccak256("erc721.storage"); - - // Instead of using a hash of a string other schemes can be used to create positions in contract storage. - // Here is a scheme that could be used: - // - // bytes32 constant ERC721_POSITION = - // keccak256(abi.encodePacked( - // ERC721.interfaceId, - // ERC721.name - // )); - - struct ERC721Storage { - // tokenId => owner - mapping (uint256 => address) tokenIdToOwner; - // owner => count of tokens owned - mapping (address => uint256) ownerToNFTokenCount; - - string name; - string symbol; - } - - // Return ERC721 storage struct for reading and writing - function getStorage() internal pure returns (ERC721Storage storage storageStruct) { - bytes32 position = ERC721_POSITION; - assembly { - storageStruct.slot := position - } - } - - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - // This is a very simplified implementation. - // It does not include all necessary validation of input. - // It is used to show diamond storage. - function transferFrom(address _from, address _to, uint256 _tokenId) internal { - ERC721Storage storage erc721Storage = LibERC721.getStorage(); - address tokenOwner = erc721Storage.tokenIdToOwner[_tokenId]; - require(tokenOwner == _from); - erc721Storage.tokenIdToOwner[_tokenId] = _to; - erc721Storage.ownerToNFTokenCount[_from]--; - erc721Storage.ownerToNFTokenCount[_to]++; - emit Transfer(_from, _to, _tokenId); - } -} - -// Note that this is not a full or correct ERC721 implementation. -// This is an example of using diamond storage. - -// Note that the ERC721.name and ERC721.symbol storage variables would probably be set -// in an `init` function at deployment time or during an upgrade. - - -// Shows use of LibERC721 and diamond storage -contract ERC721Facet { - - function name() external view returns (string memory name_) { - name_ = LibERC721.getStorage().name; - } - - function symbol() external view returns (string memory symbol_) { - symbol_ = LibERC721.getStorage().symbol; - } - - function transferFrom(address _from, address _to, uint256 _tokenId) external { - LibERC721.transferFrom(_from, _to, _tokenId); - } - -} - -// Here we show how we can share state variables and internal functions between facets by -// using Solidity libraries. Sharing internal functions between facets can also be done by -// inheriting contracts that contain internal functions. -contract ERC721BatchTransferFacet { - - function batchTransferFrom(address _from, address _to, uint256[] calldata _tokenIds) external { - for(uint256 i; i < _tokenIds.length; i++) { - LibERC721.transferFrom(_from, _to, _tokenIds[i]); - } - } -} - -// HOW TO UPGRADE DIAMOND STORAGE -//-------------------------------------------- - -// It is important not to corrupt state variables during an upgrade. It is easy to handle state -// variables correctly in upgrades. - -// Here's some things that can be done: - -// 1. To add new state variables to an AppStorage struct or a Diamond Storage struct, add them -// to the end of the struct. - -// 2. New state variables can be added to the ends of structs that are stored in mappings. - -// 3. The names of state variables can be changed, but that might be confusing if different -// facets are using different names for the same storage locations. - -// Do not do the following: - -// 1. If you are using AppStorage then do not declare and use state variables outside the -// AppStorage struct. Except Diamond Storage can be used. Diamond Storage and AppStorage -// can be used together. - -// 2. Do not add new state variables to the beginning or middle of structs. Doing this -// makes the new state variable overwrite existing state variable data and all state -// variables after the new state variable reference the wrong storage location. - -// 3. Do not put structs directly in structs unless you don’t plan on ever adding more state -// variables to the inner structs. You won't be able to add new state variables to inner -// structs in upgrades. - -// 4. Do not add new state variables to structs that are used in arrays. - -// 5. When using Diamond Storage do not use the same namespace string for different structs. -// This is obvious. Two different structs at the same location will overwrite each other. - -// 6. Do not allow any facet to be able to call `selfdestruct`. This is easy. Simply don’t -// allow the `selfdestruct` command to exist in any facet source code and don’t allow -// that command to be called via a delegatecall. Because `selfdestruct` could delete a -// facet that is used by a diamond, or `selfdestruct` could be used to delete a diamond -// proxy contract. - -// A trick to use inner structs and still enable them to be extended is to put them in mappings. -// A struct stored in a mapping can be extended in upgrades. You could use a value like 0 defined -// with a constant like INNER_STRUCT. Put your structs in mappings and then access them with the -// INNER_STRUCT constant. Example: MyStruct storage mystruct = storage.mystruct[INNER_STRUCT]; - -// Note that any Solidity data type can be used in Diamond Storage or AppStorage structs. It is -// just that structs directly in structs and structs that are used in arrays can’t be extended -// with more state variables in the future. That could be fine in some cases. - -// These rules will make sense if you understand how Solidity assigns storage locations to state -// variables. I recommend reading and understanding this section of the Solidity documentation: -// 'Layout of State Variables in Storage' diff --git a/assets/eip-2615/concept.png b/assets/eip-2615/concept.png deleted file mode 100644 index 31984685570070..00000000000000 Binary files a/assets/eip-2615/concept.png and /dev/null differ diff --git a/assets/eip-2615/mortgage-sequential.jpg b/assets/eip-2615/mortgage-sequential.jpg deleted file mode 100644 index 738c8f4cbbd66b..00000000000000 Binary files a/assets/eip-2615/mortgage-sequential.jpg and /dev/null differ diff --git a/assets/eip-2615/rental-sequential.jpg b/assets/eip-2615/rental-sequential.jpg deleted file mode 100644 index 462f6553db5742..00000000000000 Binary files a/assets/eip-2615/rental-sequential.jpg and /dev/null differ diff --git a/assets/eip-2678/package.spec.json b/assets/eip-2678/package.spec.json deleted file mode 100644 index f78790e505751c..00000000000000 --- a/assets/eip-2678/package.spec.json +++ /dev/null @@ -1,483 +0,0 @@ -{ - "title": "Package Manifest", - "description": "EthPM Manifest Specification", - "type": "object", - "required": [ - "manifest" - ], - "version": "3", - "not": { - "required": ["manifest_version"] - }, - "properties": { - "manifest": { - "type": "string", - "title": "Manifest", - "description": "EthPM Manifest Version", - "default": "ethpm/3", - "enum": ["ethpm/3"] - }, - "name": { - "$ref": "#/definitions/PackageName" - }, - "version": { - "title": "Package Version", - "description": "The version of the package that this release is for", - "type": "string" - }, - "meta": { - "$ref": "#/definitions/PackageMeta" - }, - "sources": { - "title": "Sources", - "description": "The source files included in this release", - "type": "object", - "patternProperties": { - ".*": { - "$ref": "#/definitions/Source" - } - } - }, - "compilers": { - "title": "Compilers", - "description": "The compiler versions used in this release", - "type": "array", - "items": { - "$ref": "#/definitions/CompilerInformation" - } - }, - "contractTypes": { - "title": "Contract Types", - "description": "The contract types included in this release", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/ContractTypeName" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/ContractType" - } - } - }, - "deployments": { - "title": "Deployments", - "description": "The deployed contract instances in this release", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/BlockchainURI" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/Deployment" - } - } - }, - "buildDependencies": { - "title": "Build Dependencies", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/PackageName" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/ContentURI" - } - } - } - }, - "definitions": { - "Source": { - "title": "Source", - "description": "Information about a source file included in this package", - "type": "object", - "anyOf": [ - {"required": ["content"]}, - {"required": ["urls"]} - ], - "properties": { - "checksum": { - "$ref": "#/definitions/ChecksumObject" - }, - "urls": { - "title": "URLs", - "description": "Array of urls that resolve to the source file", - "type": "array", - "items": { - "$ref": "#/definitions/ContentURI" - } - }, - "content": { - "title": "Inlined source code", - "type": "string" - }, - "installPath": { - "title": "Target file path to install source file", - "type": "string", - "pattern": "^\\.\\/.*$" - }, - "type": { - "title": "Type", - "description": "File type of this source", - "type": "string" - }, - "license": { - "title": "License", - "description": "The license associated with the source file", - "type": "string" - } - } - }, - "PackageMeta": { - "title": "Package Meta", - "description": "Metadata about the package", - "type": "object", - "properties": { - "authors": { - "title": "Authors", - "description": "Authors of this package", - "type": "array", - "items": { - "type": "string" - } - }, - "license": { - "title": "License", - "description": "The license that this package and its source are released under", - "type": "string" - }, - "description": { - "title": "Description", - "description": "Description of this package", - "type": "string" - }, - "keywords": { - "title": "Keywords", - "description": "Keywords that apply to this package", - "type": "array", - "items": { - "type": "string" - } - }, - "links": { - "title": "Links", - "descriptions": "URIs for resources related to this package", - "type": "object", - "additionalProperties": { - "type": "string", - "format": "uri" - } - } - } - }, - "PackageName": { - "title": "Package Name", - "description": "The name of the package that this release is for", - "type": "string", - "pattern": "^[a-z][-a-z0-9]{0,255}$" - }, - "ContractType": { - "title": "Contract Type", - "description": "Data for a contract type included in this package", - "type": "object", - "properties":{ - "contractName": { - "$ref": "#/definitions/ContractTypeName" - }, - "sourceId": { - "title": "Source ID", - "description": "The source ID that corresponds to this contract type", - "type": "string" - }, - "deploymentBytecode": { - "$ref": "#/definitions/BytecodeObject" - }, - "runtimeBytecode": { - "$ref": "#/definitions/BytecodeObject" - }, - "abi": { - "title": "ABI", - "description": "The ABI for this contract type", - "type": "array" - }, - "devdoc": { - "title": "Devdoc", - "description": "The dev-doc for this contract", - "type": "object" - }, - "userdoc": { - "title": "Userdoc", - "description": "The user-doc for this contract", - "type": "object" - } - } - }, - "ContractInstance": { - "title": "Contract Instance", - "description": "Data for a deployed instance of a contract", - "type": "object", - "required": [ - "contractType", - "address" - ], - "properties": { - "contractType": { - "anyOf": [ - {"$ref": "#/definitions/ContractTypeName"}, - {"$ref": "#/definitions/NestedContractTypeName"} - ] - }, - "address": { - "$ref": "#/definitions/Address" - }, - "transaction": { - "$ref": "#/definitions/TransactionHash" - }, - "block": { - "$ref": "#/definitions/BlockHash" - }, - "runtimeBytecode": { - "$ref": "#/definitions/BytecodeObject" - }, - "linkDependencies": { - "title": "Link Dependencies", - "description": "The values for the link references found within this contract instances runtime bytecode", - "type": "array", - "items": { - "$ref": "#/definitions/LinkValue" - } - } - } - }, - "ContractTypeName": { - "title": "Contract Type Name", - "description": "The name of the contract type", - "type": "string", - "pattern": "^(?:[a-z][-a-z0-9]{0,255}\\:)?[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256}])?$" - }, - "ByteString": { - "title": "Byte String", - "description": "0x-prefixed hexadecimal string representing bytes", - "type": "string", - "pattern": "^0x([0-9a-fA-F]{2})*$" - }, - "BytecodeObject": { - "title": "Bytecode Object", - "type": "object", - "anyOf": [ - {"required": ["bytecode"]}, - {"required": ["linkDependencies"]} - ], - "properties": { - "bytecode": { - "$ref": "#/definitions/ByteString" - }, - "linkReferences": { - "type": "array", - "items": { - "$ref": "#/definitions/LinkReference" - } - }, - "linkDependencies": { - "type": "array", - "items": { - "$ref": "#/definitions/LinkValue" - } - } - } - }, - "ChecksumObject": { - "title": "Checksum Object", - "description": "Checksum information about the contents of a source file", - "type": "object", - "required": [ - "hash", - "algorithm" - ], - "properties": { - "hash": { - "type": "string" - }, - "algorithm": { - "type": "string" - } - } - }, - "LinkReference": { - "title": "Link Reference", - "description": "A defined location in some bytecode which requires linking", - "type": "object", - "required": [ - "offsets", - "length", - "name" - ], - "properties": { - "offsets": { - "type": "array", - "items": { - "type": "integer", - "minimum": 0 - } - }, - "length": { - "type": "integer", - "minimum": 1 - }, - "name": { - "anyOf": [ - {"$ref": "#/definitions/ContractTypeName"}, - {"$ref": "#/definitions/NestedContractTypeName"} - ] - } - } - }, - "LinkValue": { - "title": "Link Value", - "description": "A value for an individual link reference in a contract's bytecode", - "type": "object", - "required": [ - "offsets", - "type", - "value" - ], - "properties": { - "offsets": { - "type": "array", - "items": { - "type": "integer", - "minimum": 0 - } - }, - "type": { - "description": "The type of link value", - "type": "string" - }, - "value": { - "description": "The value for the link reference" - } - }, - "oneOf": [{ - "properties": { - "type": { - "enum": ["literal"] - }, - "value": { - "$ref": "#/definitions/ByteString" - } - } - }, { - "properties": { - "type": { - "enum": ["reference"] - }, - "value": { - "anyOf": [ - {"$ref": "#/definitions/ContractInstanceName"}, - {"$ref": "#/definitions/NestedContractInstanceName"} - ] - } - } - }] - }, - "ContractInstanceName": { - "title": "Contract Instance Name", - "description": "The name of the deployed contract instance", - "type": "string", - "pattern": "^[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256})?$" - }, - "Deployment": { - "title": "Deployment", - "type": "object", - "propertyNames": { - "$ref": "#/definitions/ContractInstanceName" - }, - "patternProperties": { - "": { - "$ref": "#/definitions/ContractInstance" - } - } - }, - "NestedContractTypeName": { - "title": "Nested Contract Type Name", - "description": "Name of a nested contract type from somewhere down the dependency tree", - "type": "string", - "pattern": "^(?:[a-z][-a-z0-9]{0,255}\\:)+[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256})?$" - }, - "NestedContractInstanceName": { - "title": "Nested Contract Instance Name", - "description": "Name of a nested contract instance from somewhere down the dependency tree", - "type": "string", - "pattern": "^(?:[a-z][-a-z0-9]{0,255}\\:)+[a-zA-Z_$][-a-zA-Z0-9_$]{0,255}(?:[-a-zA-Z0-9]{1,256})?$" - }, - "CompilerInformation": { - "title": "Compiler Information", - "description": "Information about the software that was used to compile a contract type or deployment", - "type": "object", - "required": [ - "name", - "version" - ], - "properties": { - "name": { - "description": "The name of the compiler", - "type": "string" - }, - "version": { - "description": "The version string for the compiler", - "type": "string" - }, - "settings": { - "description": "The settings used for compilation", - "type": "object" - }, - "contractTypes": { - "description": "The contract types that targeted this compiler.", - "type": "array", - "items": { - "$ref": "#/definitions/ContractTypeName" - } - } - } - }, - "Address": { - "title": "Address", - "description": "A 0x-prefixed Ethereum address", - "allOf": [ - { "$ref": "#/definitions/ByteString" }, - { "minLength": 42, "maxLength": 42 } - ] - }, - "TransactionHash": { - "title": "Transaction Hash", - "description": "A 0x-prefixed Ethereum transaction hash", - "allOf": [ - { "$ref": "#/definitions/ByteString" }, - { "minLength": 66, "maxLength": 66 } - ] - }, - "BlockHash": { - "title": "Block Hash", - "description": "An Ethereum block hash", - "allOf": [ - { "$ref": "#/definitions/ByteString" }, - { "minLength": 66, "maxLength": 66 } - ] - }, - "ContentURI": { - "title": "Content URI", - "description": "An content addressable URI", - "type": "string", - "format": "uri" - }, - "BlockchainURI": { - "title": "Blockchain URI", - "description": "An BIP122 URI", - "type": "string", - "pattern": "^blockchain\\://[0-9a-fA-F]{64}\\/block\\/[0-9a-fA-F]{64}$" - } - }, - "dependencies": { - "name": ["version"], - "version": ["name"] - } -} diff --git a/assets/eip-2680/sha256-384-512.pdf b/assets/eip-2680/sha256-384-512.pdf deleted file mode 100644 index 0524e81837999e..00000000000000 Binary files a/assets/eip-2680/sha256-384-512.pdf and /dev/null differ diff --git a/assets/eip-2771/example-flow.png b/assets/eip-2771/example-flow.png deleted file mode 100644 index b122417d247253..00000000000000 Binary files a/assets/eip-2771/example-flow.png and /dev/null differ diff --git a/assets/eip-2848/presentation.pdf b/assets/eip-2848/presentation.pdf deleted file mode 100644 index 99446f47c51c24..00000000000000 Binary files a/assets/eip-2848/presentation.pdf and /dev/null differ diff --git a/assets/eip-2917/erc-reward-formula.png b/assets/eip-2917/erc-reward-formula.png deleted file mode 100644 index a50f3876039785..00000000000000 Binary files a/assets/eip-2917/erc-reward-formula.png and /dev/null differ diff --git a/assets/eip-2980/Finma-ICO-Guidelines.pdf b/assets/eip-2980/Finma-ICO-Guidelines.pdf deleted file mode 100644 index 2c8ba8deb21737..00000000000000 Binary files a/assets/eip-2980/Finma-ICO-Guidelines.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-AMLA.pdf b/assets/eip-2980/Swiss-Confederation-AMLA.pdf deleted file mode 100644 index 831c89cd56fbd2..00000000000000 Binary files a/assets/eip-2980/Swiss-Confederation-AMLA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-BA.pdf b/assets/eip-2980/Swiss-Confederation-BA.pdf deleted file mode 100644 index 231a55142294d4..00000000000000 Binary files a/assets/eip-2980/Swiss-Confederation-BA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-CISA.pdf b/assets/eip-2980/Swiss-Confederation-CISA.pdf deleted file mode 100644 index 3f870033977d53..00000000000000 Binary files a/assets/eip-2980/Swiss-Confederation-CISA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-FINIA.pdf b/assets/eip-2980/Swiss-Confederation-FINIA.pdf deleted file mode 100644 index e070984443c663..00000000000000 Binary files a/assets/eip-2980/Swiss-Confederation-FINIA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-FINSA.pdf b/assets/eip-2980/Swiss-Confederation-FINSA.pdf deleted file mode 100644 index 154aec36be4883..00000000000000 Binary files a/assets/eip-2980/Swiss-Confederation-FINSA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-FMIA.pdf b/assets/eip-2980/Swiss-Confederation-FMIA.pdf deleted file mode 100644 index 959c239d590714..00000000000000 Binary files a/assets/eip-2980/Swiss-Confederation-FMIA.pdf and /dev/null differ diff --git a/assets/eip-2980/Swiss-Confederation-SESTA.pdf b/assets/eip-2980/Swiss-Confederation-SESTA.pdf deleted file mode 100644 index b83062d57211f6..00000000000000 Binary files a/assets/eip-2980/Swiss-Confederation-SESTA.pdf and /dev/null differ diff --git a/assets/eip-3005/meta-txs-directly-to-token-smart-contract.png b/assets/eip-3005/meta-txs-directly-to-token-smart-contract.png deleted file mode 100644 index 2c792f1429aa58..00000000000000 Binary files a/assets/eip-3005/meta-txs-directly-to-token-smart-contract.png and /dev/null differ diff --git a/assets/eip-3448/MetaProxyFactory.sol b/assets/eip-3448/MetaProxyFactory.sol deleted file mode 100644 index e7c3679124098b..00000000000000 --- a/assets/eip-3448/MetaProxyFactory.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.7.6; - -contract MetaProxyFactory { - /// @dev Creates a new proxy for `targetContract` with metadata from calldata. - /// Copies everything from calldata except the first 4 bytes. - /// @return addr A non-zero address if successful. - function _metaProxyFromCalldata (address targetContract) internal returns (address addr) { - // the following assembly code (init code + contract code) constructs a metaproxy. - assembly { - // load free memory pointer as per solidity convention - let start := mload(64) - // copy - let ptr := start - // deploy code (11 bytes) + first part of the proxy (21 bytes) - mstore(ptr, 0x600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73) - ptr := add(ptr, 32) - - // store the address of the contract to be called - mstore(ptr, shl(96, targetContract)) - // 20 bytes - ptr := add(ptr, 20) - - // the remaining proxy code... - mstore(ptr, 0x5af43d3d93803e603457fd5bf300000000000000000000000000000000000000) - // ...13 bytes - ptr := add(ptr, 13) - - // now calculdate the size and copy the metadata - // - 4 bytes function signature - let size := sub(calldatasize(), 4) - // copy - calldatacopy(ptr, 4, size) - ptr := add(ptr, size) - // store the size of the metadata at the end of the bytecode - mstore(ptr, size) - ptr := add(ptr, 32) - - // The size is deploy code + contract code + calldatasize - 4 + 32. - addr := create(0, start, sub(ptr, start)) - } - } - - /// @dev Creates a proxy for `targetContract` with metadata from `metadata`. - /// @return A non-zero address if successful. - function _metaProxyFromBytes (address targetContract, bytes memory metadata) internal returns (address) { - uint256 ptr; - assembly { - ptr := add(metadata, 32) - } - return _metaProxyFromMemory(targetContract, ptr, metadata.length); - } - - /// @dev Creates a new proxy for `targetContract` with metadata from memory starting at `offset` and `length` bytes. - /// @return addr A non-zero address if successful. - function _metaProxyFromMemory (address targetContract, uint256 offset, uint256 length) internal returns (address addr) { - // the following assembly code (init code + contract code) constructs a metaproxy. - assembly { - // load free memory pointer as per solidity convention - let start := mload(64) - // keep a copy - let ptr := start - // deploy code (11 bytes) + first part of the proxy (21 bytes) - mstore(ptr, 0x600b380380600b3d393df3363d3d373d3d3d3d60368038038091363936013d73) - ptr := add(ptr, 32) - - // store the address of the contract to be called - mstore(ptr, shl(96, targetContract)) - // 20 bytes - ptr := add(ptr, 20) - - // the remaining proxy code... - mstore(ptr, 0x5af43d3d93803e603457fd5bf300000000000000000000000000000000000000) - // ...13 bytes - ptr := add(ptr, 13) - - // copy the metadata - { - for { let i := 0 } lt(i, length) { i := add(i, 32) } { - mstore(add(ptr, i), mload(add(offset, i))) - } - } - ptr := add(ptr, length) - // store the size of the metadata at the end of the bytecode - mstore(ptr, length) - ptr := add(ptr, 32) - - // The size is deploy code + contract code + calldatasize - 4 + 32. - addr := create(0, start, sub(ptr, start)) - } - } -} diff --git a/assets/eip-3448/MetaProxyTest.sol b/assets/eip-3448/MetaProxyTest.sol deleted file mode 100644 index dd175548856c59..00000000000000 --- a/assets/eip-3448/MetaProxyTest.sol +++ /dev/null @@ -1,190 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.7.6; - -import './MetaProxyFactory.sol'; - -/// @notice This contract includes test cases for the MetaProxy standard. -contract MetaProxyTest is MetaProxyFactory { - uint256 public someValue; - - event SomeEvent( - address a, - uint256 b, - uint256[] c - ); - event SomeData(bytes data); - - /// @notice One-time initializer. - function init () external payable { - require(someValue == 0); - - (, uint256 b, ) = MetaProxyTest(this).getMetadataViaCall(); - require(b > 0); - someValue = b; - } - - /// @notice MetaProxy construction via abi encoded bytes. - /// Arguments are reversed for testing purposes. - function createFromBytes ( - uint256[] calldata c, - uint256 b, - address a - ) external payable returns (address proxy) { - // creates a new proxy where the metadata is the result of abi.encode() - proxy = MetaProxyFactory._metaProxyFromBytes(address(this), abi.encode(a, b, c)); - require(proxy != address(0)); - // optional one-time setup, a constructor() substitute - MetaProxyTest(proxy).init{ value: msg.value }(); - } - - /// @notice MetaProxy construction via calldata. - function createFromCalldata ( - address a, - uint256 b, - uint256[] calldata c - ) external payable returns (address proxy) { - // creates a new proxy where the metadata is everything after the 4th byte from calldata. - proxy = MetaProxyFactory._metaProxyFromCalldata(address(this)); - require(proxy != address(0)); - // optional one-time setup, a constructor() substitute - MetaProxyTest(proxy).init{ value: msg.value }(); - } - - /// @notice Returns the metadata of this (MetaProxy) contract. - /// Only relevant with contracts created via the MetaProxy standard. - /// @dev This function is aimed to be invoked with- & without a call. - function getMetadataWithoutCall () public pure returns ( - address a, - uint256 b, - uint256[] memory c - ) { - bytes memory data; - assembly { - let posOfMetadataSize := sub(calldatasize(), 32) - let size := calldataload(posOfMetadataSize) - let dataPtr := sub(posOfMetadataSize, size) - data := mload(64) - // increment free memory pointer by metadata size + 32 bytes (length) - mstore(64, add(data, add(size, 32))) - mstore(data, size) - let memPtr := add(data, 32) - calldatacopy(memPtr, dataPtr, size) - } - return abi.decode(data, (address, uint256, uint256[])); - } - - /// @notice Returns the metadata of this (MetaProxy) contract. - /// Only relevant with contracts created via the MetaProxy standard. - /// @dev This function is aimed to to be invoked via a call. - function getMetadataViaCall () public pure returns ( - address a, - uint256 b, - uint256[] memory c - ) { - assembly { - let posOfMetadataSize := sub(calldatasize(), 32) - let size := calldataload(posOfMetadataSize) - let dataPtr := sub(posOfMetadataSize, size) - calldatacopy(0, dataPtr, size) - return(0, size) - } - } - - /// @notice Runs all test cases - function testAll () external payable { - (address a, uint256 b, uint256[] memory c) = abc(); - MetaProxyTest self = MetaProxyTest(address(this)); - - { - address proxy = self.createFromCalldata(a, b, c); - testProxy(proxy); - } - { - address proxy = self.createFromBytes(c, b, a); - testProxy(proxy); - } - } - - function abc () public returns (address a, uint256 b, uint256[] memory c) { - a = address(this); - b = 0xc0ffe; - c = new uint256[](9); - } - - function testProxy (address _proxy) public { - require(_proxy != address(0)); - - (address a, uint256 b, uint256[] memory c) = abc(); - MetaProxyTest proxy = MetaProxyTest(_proxy); - - { - (address x, uint256 y, uint256[] memory z) = proxy.getMetadataViaCall(); - require(a == x && b == y && keccak256(abi.encode(c)) == keccak256(abi.encode(z))); - } - { - (address x, uint256 y, uint256[] memory z) = proxy.getMetadataWithoutCall(); - require(a == x && b == y && keccak256(abi.encode(c)) == keccak256(abi.encode(z))); - } - - require(proxy.someValue() == b); - require(proxy.testReturnSingle() == b); - - bytes memory _bytes = hex'68656c6c6f20776f726c64'; - (uint256 x, uint256[] memory y) = proxy.testReturnMulti(_bytes, uint160(address(this)) + b); - require(x == b); - require(y.length == c.length); - - (bool success, bytes memory returnData) = _proxy.call(abi.encodeWithSignature('testRevert(string)', _bytes)); - require(success == false); - require(keccak256(returnData) == keccak256(abi.encodeWithSignature('Error(string)', _bytes))); - } - - function testReturnSingle () public returns (uint256) { - ( - address a, - uint256 b, - uint256[] memory c - ) = MetaProxyTest(this).getMetadataViaCall(); - - require(a == msg.sender); - require(b == someValue); - require(c.length == 9); - - emit SomeEvent(a, b, c); - - return b; - } - - function testReturnMulti (bytes memory data, uint256 xyz) public returns (uint256, uint256[] memory) { - ( - address a, - uint256 b, - uint256[] memory c - ) = getMetadataWithoutCall(); - - require(a == msg.sender); - require(b == someValue); - require(c.length == 9); - require(xyz == uint160(a) + b); - - bytes memory expected = hex'68656c6c6f20776f726c64'; - require(data.length == expected.length); - for (uint256 i = 0; i < expected.length; i++) { - require(data[i] == expected[i]); - } - - emit SomeEvent(a, b, c); - emit SomeData(data); - - return (b, c); - } - - function testRevert (string memory data) public { - (address a,,) = getMetadataWithoutCall(); - - // should evaluate to `true` - if (a != address(0)) { - revert(data); - } - } -} diff --git a/assets/eip-3450/lagrange.gif b/assets/eip-3450/lagrange.gif deleted file mode 100644 index de7268b7e1d5d0..00000000000000 Binary files a/assets/eip-3450/lagrange.gif and /dev/null differ diff --git a/assets/eip-3450/wordlist.txt b/assets/eip-3450/wordlist.txt deleted file mode 100644 index 942040ed50f720..00000000000000 --- a/assets/eip-3450/wordlist.txt +++ /dev/null @@ -1,2048 +0,0 @@ -abandon -ability -able -about -above -absent -absorb -abstract -absurd -abuse -access -accident -account -accuse -achieve -acid -acoustic -acquire -across -act -action -actor -actress -actual -adapt -add -addict -address -adjust -admit -adult -advance -advice -aerobic -affair -afford -afraid -again -age -agent -agree -ahead -aim -air -airport -aisle -alarm -album -alcohol -alert -alien -all -alley -allow -almost -alone -alpha -already -also -alter -always -amateur -amazing -among -amount -amused -analyst -anchor -ancient -anger -angle -angry -animal -ankle -announce -annual -another -answer -antenna -antique -anxiety -any -apart -apology -appear -apple -approve -april -arch -arctic -area -arena -argue -arm -armed -armor -army -around -arrange -arrest -arrive -arrow -art -artefact -artist -artwork -ask -aspect -assault -asset -assist -assume -asthma -athlete -atom -attack -attend -attitude -attract -auction -audit -august -aunt -author -auto -autumn -average -avocado -avoid -awake -aware -away -awesome -awful -awkward -axis -baby -bachelor -bacon -badge -bag -balance -balcony -ball -bamboo -banana -banner -bar -barely -bargain -barrel -base -basic -basket -battle -beach -bean -beauty -because -become -beef -before -begin -behave -behind -believe -below -belt -bench -benefit -best -betray -better -between -beyond -bicycle -bid -bike -bind -biology -bird -birth -bitter -black -blade -blame -blanket -blast -bleak -bless -blind -blood -blossom -blouse -blue -blur -blush -board -boat -body -boil -bomb -bone -bonus -book -boost -border -boring -borrow -boss -bottom -bounce -box -boy -bracket -brain -brand -brass -brave -bread -breeze -brick -bridge -brief -bright -bring -brisk -broccoli -broken -bronze -broom -brother -brown -brush -bubble -buddy -budget -buffalo -build -bulb -bulk -bullet -bundle -bunker -burden -burger -burst -bus -business -busy -butter -buyer -buzz -cabbage -cabin -cable -cactus -cage -cake -call -calm -camera -camp -can -canal -cancel -candy -cannon -canoe -canvas -canyon -capable -capital -captain -car -carbon -card -cargo -carpet -carry -cart -case -cash -casino -castle -casual -cat -catalog -catch -category -cattle -caught -cause -caution -cave -ceiling -celery -cement -census -century -cereal -certain -chair -chalk -champion -change -chaos -chapter -charge -chase -chat -cheap -check -cheese -chef -cherry -chest -chicken -chief -child -chimney -choice -choose -chronic -chuckle -chunk -churn -cigar -cinnamon -circle -citizen -city -civil -claim -clap -clarify -claw -clay -clean -clerk -clever -click -client -cliff -climb -clinic -clip -clock -clog -close -cloth -cloud -clown -club -clump -cluster -clutch -coach -coast -coconut -code -coffee -coil -coin -collect -color -column -combine -come -comfort -comic -common -company -concert -conduct -confirm -congress -connect -consider -control -convince -cook -cool -copper -copy -coral -core -corn -correct -cost -cotton -couch -country -couple -course -cousin -cover -coyote -crack -cradle -craft -cram -crane -crash -crater -crawl -crazy -cream -credit -creek -crew -cricket -crime -crisp -critic -crop -cross -crouch -crowd -crucial -cruel -cruise -crumble -crunch -crush -cry -crystal -cube -culture -cup -cupboard -curious -current -curtain -curve -cushion -custom -cute -cycle -dad -damage -damp -dance -danger -daring -dash -daughter -dawn -day -deal -debate -debris -decade -december -decide -decline -decorate -decrease -deer -defense -define -defy -degree -delay -deliver -demand -demise -denial -dentist -deny -depart -depend -deposit -depth -deputy -derive -describe -desert -design -desk -despair -destroy -detail -detect -develop -device -devote -diagram -dial -diamond -diary -dice -diesel -diet -differ -digital -dignity -dilemma -dinner -dinosaur -direct -dirt -disagree -discover -disease -dish -dismiss -disorder -display -distance -divert -divide -divorce -dizzy -doctor -document -dog -doll -dolphin -domain -donate -donkey -donor -door -dose -double -dove -draft -dragon -drama -drastic -draw -dream -dress -drift -drill -drink -drip -drive -drop -drum -dry -duck -dumb -dune -during -dust -dutch -duty -dwarf -dynamic -eager -eagle -early -earn -earth -easily -east -easy -echo -ecology -economy -edge -edit -educate -effort -egg -eight -either -elbow -elder -electric -elegant -element -elephant -elevator -elite -else -embark -embody -embrace -emerge -emotion -employ -empower -empty -enable -enact -end -endless -endorse -enemy -energy -enforce -engage -engine -enhance -enjoy -enlist -enough -enrich -enroll -ensure -enter -entire -entry -envelope -episode -equal -equip -era -erase -erode -erosion -error -erupt -escape -essay -essence -estate -eternal -ethics -evidence -evil -evoke -evolve -exact -example -excess -exchange -excite -exclude -excuse -execute -exercise -exhaust -exhibit -exile -exist -exit -exotic -expand -expect -expire -explain -expose -express -extend -extra -eye -eyebrow -fabric -face -faculty -fade -faint -faith -fall -false -fame -family -famous -fan -fancy -fantasy -farm -fashion -fat -fatal -father -fatigue -fault -favorite -feature -february -federal -fee -feed -feel -female -fence -festival -fetch -fever -few -fiber -fiction -field -figure -file -film -filter -final -find -fine -finger -finish -fire -firm -first -fiscal -fish -fit -fitness -fix -flag -flame -flash -flat -flavor -flee -flight -flip -float -flock -floor -flower -fluid -flush -fly -foam -focus -fog -foil -fold -follow -food -foot -force -forest -forget -fork -fortune -forum -forward -fossil -foster -found -fox -fragile -frame -frequent -fresh -friend -fringe -frog -front -frost -frown -frozen -fruit -fuel -fun -funny -furnace -fury -future -gadget -gain -galaxy -gallery -game -gap -garage -garbage -garden -garlic -garment -gas -gasp -gate -gather -gauge -gaze -general -genius -genre -gentle -genuine -gesture -ghost -giant -gift -giggle -ginger -giraffe -girl -give -glad -glance -glare -glass -glide -glimpse -globe -gloom -glory -glove -glow -glue -goat -goddess -gold -good -goose -gorilla -gospel -gossip -govern -gown -grab -grace -grain -grant -grape -grass -gravity -great -green -grid -grief -grit -grocery -group -grow -grunt -guard -guess -guide -guilt -guitar -gun -gym -habit -hair -half -hammer -hamster -hand -happy -harbor -hard -harsh -harvest -hat -have -hawk -hazard -head -health -heart -heavy -hedgehog -height -hello -helmet -help -hen -hero -hidden -high -hill -hint -hip -hire -history -hobby -hockey -hold -hole -holiday -hollow -home -honey -hood -hope -horn -horror -horse -hospital -host -hotel -hour -hover -hub -huge -human -humble -humor -hundred -hungry -hunt -hurdle -hurry -hurt -husband -hybrid -ice -icon -idea -identify -idle -ignore -ill -illegal -illness -image -imitate -immense -immune -impact -impose -improve -impulse -inch -include -income -increase -index -indicate -indoor -industry -infant -inflict -inform -inhale -inherit -initial -inject -injury -inmate -inner -innocent -input -inquiry -insane -insect -inside -inspire -install -intact -interest -into -invest -invite -involve -iron -island -isolate -issue -item -ivory -jacket -jaguar -jar -jazz -jealous -jeans -jelly -jewel -job -join -joke -journey -joy -judge -juice -jump -jungle -junior -junk -just -kangaroo -keen -keep -ketchup -key -kick -kid -kidney -kind -kingdom -kiss -kit -kitchen -kite -kitten -kiwi -knee -knife -knock -know -lab -label -labor -ladder -lady -lake -lamp -language -laptop -large -later -latin -laugh -laundry -lava -law -lawn -lawsuit -layer -lazy -leader -leaf -learn -leave -lecture -left -leg -legal -legend -leisure -lemon -lend -length -lens -leopard -lesson -letter -level -liar -liberty -library -license -life -lift -light -like -limb -limit -link -lion -liquid -list -little -live -lizard -load -loan -lobster -local -lock -logic -lonely -long -loop -lottery -loud -lounge -love -loyal -lucky -luggage -lumber -lunar -lunch -luxury -lyrics -machine -mad -magic -magnet -maid -mail -main -major -make -mammal -man -manage -mandate -mango -mansion -manual -maple -marble -march -margin -marine -market -marriage -mask -mass -master -match -material -math -matrix -matter -maximum -maze -meadow -mean -measure -meat -mechanic -medal -media -melody -melt -member -memory -mention -menu -mercy -merge -merit -merry -mesh -message -metal -method -middle -midnight -milk -million -mimic -mind -minimum -minor -minute -miracle -mirror -misery -miss -mistake -mix -mixed -mixture -mobile -model -modify -mom -moment -monitor -monkey -monster -month -moon -moral -more -morning -mosquito -mother -motion -motor -mountain -mouse -move -movie -much -muffin -mule -multiply -muscle -museum -mushroom -music -must -mutual -myself -mystery -myth -naive -name -napkin -narrow -nasty -nation -nature -near -neck -need -negative -neglect -neither -nephew -nerve -nest -net -network -neutral -never -news -next -nice -night -noble -noise -nominee -noodle -normal -north -nose -notable -note -nothing -notice -novel -now -nuclear -number -nurse -nut -oak -obey -object -oblige -obscure -observe -obtain -obvious -occur -ocean -october -odor -off -offer -office -often -oil -okay -old -olive -olympic -omit -once -one -onion -online -only -open -opera -opinion -oppose -option -orange -orbit -orchard -order -ordinary -organ -orient -original -orphan -ostrich -other -outdoor -outer -output -outside -oval -oven -over -own -owner -oxygen -oyster -ozone -pact -paddle -page -pair -palace -palm -panda -panel -panic -panther -paper -parade -parent -park -parrot -party -pass -patch -path -patient -patrol -pattern -pause -pave -payment -peace -peanut -pear -peasant -pelican -pen -penalty -pencil -people -pepper -perfect -permit -person -pet -phone -photo -phrase -physical -piano -picnic -picture -piece -pig -pigeon -pill -pilot -pink -pioneer -pipe -pistol -pitch -pizza -place -planet -plastic -plate -play -please -pledge -pluck -plug -plunge -poem -poet -point -polar -pole -police -pond -pony -pool -popular -portion -position -possible -post -potato -pottery -poverty -powder -power -practice -praise -predict -prefer -prepare -present -pretty -prevent -price -pride -primary -print -priority -prison -private -prize -problem -process -produce -profit -program -project -promote -proof -property -prosper -protect -proud -provide -public -pudding -pull -pulp -pulse -pumpkin -punch -pupil -puppy -purchase -purity -purpose -purse -push -put -puzzle -pyramid -quality -quantum -quarter -question -quick -quit -quiz -quote -rabbit -raccoon -race -rack -radar -radio -rail -rain -raise -rally -ramp -ranch -random -range -rapid -rare -rate -rather -raven -raw -razor -ready -real -reason -rebel -rebuild -recall -receive -recipe -record -recycle -reduce -reflect -reform -refuse -region -regret -regular -reject -relax -release -relief -rely -remain -remember -remind -remove -render -renew -rent -reopen -repair -repeat -replace -report -require -rescue -resemble -resist -resource -response -result -retire -retreat -return -reunion -reveal -review -reward -rhythm -rib -ribbon -rice -rich -ride -ridge -rifle -right -rigid -ring -riot -ripple -risk -ritual -rival -river -road -roast -robot -robust -rocket -romance -roof -rookie -room -rose -rotate -rough -round -route -royal -rubber -rude -rug -rule -run -runway -rural -sad -saddle -sadness -safe -sail -salad -salmon -salon -salt -salute -same -sample -sand -satisfy -satoshi -sauce -sausage -save -say -scale -scan -scare -scatter -scene -scheme -school -science -scissors -scorpion -scout -scrap -screen -script -scrub -sea -search -season -seat -second -secret -section -security -seed -seek -segment -select -sell -seminar -senior -sense -sentence -series -service -session -settle -setup -seven -shadow -shaft -shallow -share -shed -shell -sheriff -shield -shift -shine -ship -shiver -shock -shoe -shoot -shop -short -shoulder -shove -shrimp -shrug -shuffle -shy -sibling -sick -side -siege -sight -sign -silent -silk -silly -silver -similar -simple -since -sing -siren -sister -situate -six -size -skate -sketch -ski -skill -skin -skirt -skull -slab -slam -sleep -slender -slice -slide -slight -slim -slogan -slot -slow -slush -small -smart -smile -smoke -smooth -snack -snake -snap -sniff -snow -soap -soccer -social -sock -soda -soft -solar -soldier -solid -solution -solve -someone -song -soon -sorry -sort -soul -sound -soup -source -south -space -spare -spatial -spawn -speak -special -speed -spell -spend -sphere -spice -spider -spike -spin -spirit -split -spoil -sponsor -spoon -sport -spot -spray -spread -spring -spy -square -squeeze -squirrel -stable -stadium -staff -stage -stairs -stamp -stand -start -state -stay -steak -steel -stem -step -stereo -stick -still -sting -stock -stomach -stone -stool -story -stove -strategy -street -strike -strong -struggle -student -stuff -stumble -style -subject -submit -subway -success -such -sudden -suffer -sugar -suggest -suit -summer -sun -sunny -sunset -super -supply -supreme -sure -surface -surge -surprise -surround -survey -suspect -sustain -swallow -swamp -swap -swarm -swear -sweet -swift -swim -swing -switch -sword -symbol -symptom -syrup -system -table -tackle -tag -tail -talent -talk -tank -tape -target -task -taste -tattoo -taxi -teach -team -tell -ten -tenant -tennis -tent -term -test -text -thank -that -theme -then -theory -there -they -thing -this -thought -three -thrive -throw -thumb -thunder -ticket -tide -tiger -tilt -timber -time -tiny -tip -tired -tissue -title -toast -tobacco -today -toddler -toe -together -toilet -token -tomato -tomorrow -tone -tongue -tonight -tool -tooth -top -topic -topple -torch -tornado -tortoise -toss -total -tourist -toward -tower -town -toy -track -trade -traffic -tragic -train -transfer -trap -trash -travel -tray -treat -tree -trend -trial -tribe -trick -trigger -trim -trip -trophy -trouble -truck -true -truly -trumpet -trust -truth -try -tube -tuition -tumble -tuna -tunnel -turkey -turn -turtle -twelve -twenty -twice -twin -twist -two -type -typical -ugly -umbrella -unable -unaware -uncle -uncover -under -undo -unfair -unfold -unhappy -uniform -unique -unit -universe -unknown -unlock -until -unusual -unveil -update -upgrade -uphold -upon -upper -upset -urban -urge -usage -use -used -useful -useless -usual -utility -vacant -vacuum -vague -valid -valley -valve -van -vanish -vapor -various -vast -vault -vehicle -velvet -vendor -venture -venue -verb -verify -version -very -vessel -veteran -viable -vibrant -vicious -victory -video -view -village -vintage -violin -virtual -virus -visa -visit -visual -vital -vivid -vocal -voice -void -volcano -volume -vote -voyage -wage -wagon -wait -walk -wall -walnut -want -warfare -warm -warrior -wash -wasp -waste -water -wave -way -wealth -weapon -wear -weasel -weather -web -wedding -weekend -weird -welcome -west -wet -whale -what -wheat -wheel -when -where -whip -whisper -wide -width -wife -wild -will -win -window -wine -wing -wink -winner -winter -wire -wisdom -wise -wish -witness -wolf -woman -wonder -wood -wool -word -work -world -worry -worth -wrap -wreck -wrestle -wrist -write -wrong -yard -year -yellow -you -young -youth -zebra -zero -zone -zoo diff --git a/assets/eip-3475/ERC3475.sol b/assets/eip-3475/ERC3475.sol deleted file mode 100644 index 43448e132e1999..00000000000000 --- a/assets/eip-3475/ERC3475.sol +++ /dev/null @@ -1,433 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./interfaces/IERC3475.sol"; - -contract ERC3475 is IERC3475 { - /** - * @notice this Struct is representing the Nonce properties as an object - * - */ - struct Nonce { - // stores the values corresponding to the dates (issuance and maturity date). - mapping(uint256 => IERC3475.Values) _values; - // storing the issuance of the issued bonds with their balances. - mapping(address => uint256) _balances; - // defines the mapping for amount of bonds that can be delegated by Owner => operator address. - mapping(address => mapping(address => uint256)) _allowances; - - // Overall supplies of this nonce - uint256 _activeSupply; - uint256 _burnedSupply; - uint256 _redeemedSupply; - } - - /** - * @notice this Struct is representing the Class properties as an object - * and can be retrieved by the classId - */ - struct Class { - mapping(uint256 => IERC3475.Values) _values; - mapping(uint256 => IERC3475.Metadata) _nonceMetadata; - mapping(uint256 => Nonce) nonces; - } - - mapping(address => mapping(address => bool)) operatorApprovals; - - // from classId given - mapping(uint256 => Class) internal _classes; - mapping(uint256 => IERC3475.Metadata) _classMetadata; - - /** - * @notice Here the constructor is just to initialize a class and nonce, - * in practice, you will have a function to create a new class and nonce - * to be deployed during the initial deployment cycle - */ - constructor() { - // define "symbol of the given class"; - _classMetadata[0].title = "symbol"; - _classMetadata[0]._type = "string"; - _classMetadata[0].description = "symbol of the class"; - // define "period of the class"; - _classMetadata[5].title = "period"; - _classMetadata[5]._type = "int"; - _classMetadata[5].description = "value (in months) about maturity time"; - - - - - // describing the symbol of the different class - _classes[0]._values[0].stringValue = "DBIT Fix 6M"; - _classes[1]._values[0].stringValue = "DBIT Fix test Instantaneous"; - - - // define the maturity time period (for the test class). - _classes[0]._values[5].uintValue = 10; - _classes[1]._values[5].uintValue = 1; - - // write the time of maturity to nonce values, in other implementation, a create nonce function can be added - _classes[0].nonces[0]._values[0].uintValue = block.timestamp + 180 days; - _classes[0].nonces[1]._values[0].uintValue = block.timestamp + 181 days; - _classes[0].nonces[2]._values[0].uintValue = block.timestamp + 182 days; - - // test for review the instantaneous class - _classes[1].nonces[0]._values[0].uintValue = block.timestamp + 1; - _classes[1].nonces[1]._values[0].uintValue = block.timestamp + 2; - _classes[1].nonces[2]._values[0].uintValue = block.timestamp + 3; - - // define metadata explaining "maturity of the nonce"; - _classes[0]._nonceMetadata[0].title = "maturity"; - _classes[0]._nonceMetadata[0]._type = "int"; - _classes[0]._nonceMetadata[0].description = "maturity date in integer"; - - _classes[1]._nonceMetadata[0].title = "maturity"; - _classes[1]._nonceMetadata[0]._type = "int"; - _classes[1]._nonceMetadata[0].description = "maturity date in integer"; - - // initializing all of the nonces for issued bonds - _classes[0].nonces[0]._values[0].boolValue = true; - _classes[0].nonces[1]._values[0].boolValue = true; - _classes[0].nonces[2]._values[0].boolValue = true; - } - - // Writable functions. - function transferFrom( - address _from, - address _to, - Transaction[] calldata _transactions - ) public virtual override { - require( - _from != address(0), - "ERC3475: can't transfer from the zero address" - ); - require( - _to != address(0), - "ERC3475:use burn() instead" - ); - require( - msg.sender == _from || - isApprovedFor(_from, msg.sender), - "ERC3475:caller-not-owner-or-approved" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - _transferFrom(_from, _to, _transactions[i]); - } - emit Transfer(msg.sender, _from, _to, _transactions); - } - - function transferAllowanceFrom( - address _from, - address _to, - Transaction[] calldata _transactions - ) public virtual override { - require( - _from != address(0), - "ERC3475: can't transfer allowed amt from zero address" - ); - require( - _to != address(0), - "ERC3475: use burn() instead" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - require( - _transactions[i]._amount <= allowance(_from, msg.sender, _transactions[i].classId, _transactions[i].nonceId), - "ERC3475:caller-not-owner-or-approved" - ); - _transferAllowanceFrom(msg.sender, _from, _to, _transactions[i]); - } - emit Transfer(msg.sender, _from, _to, _transactions); - } - - function issue(address _to, Transaction[] calldata _transactions) - external - virtual - override - { - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - require( - _to != address(0), - "ERC3475: can't issue to the zero address" - ); - _issue(_to, _transactions[i]); - } - emit Issue(msg.sender, _to, _transactions); - } - - function redeem(address _from, Transaction[] calldata _transactions) - external - virtual - override - { - require( - _from != address(0), - "ERC3475: can't redeem from the zero address" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - (, uint256 progressRemaining) = getProgress( - _transactions[i].classId, - _transactions[i].nonceId - ); - require( - progressRemaining == 0, - "ERC3475 Error: Not redeemable" - ); - _redeem(_from, _transactions[i]); - } - emit Redeem(msg.sender, _from, _transactions); - } - - function burn(address _from, Transaction[] calldata _transactions) - external - virtual - override - { - require( - _from != address(0), - "ERC3475: can't burn from the zero address" - ); - require( - msg.sender == _from || - isApprovedFor(_from, msg.sender), - "ERC3475: caller-not-owner-or-approved" - ); - uint256 len = _transactions.length; - for (uint256 i = 0; i < len; i++) { - _burn(_from, _transactions[i]); - } - emit Burn(msg.sender, _from, _transactions); - } - - function approve(address _spender, Transaction[] calldata _transactions) - external - virtual - override - { - for (uint256 i = 0; i < _transactions.length; i++) { - _classes[_transactions[i].classId] - .nonces[_transactions[i].nonceId] - ._allowances[msg.sender][_spender] = _transactions[i]._amount; - } - } - - function setApprovalFor( - address operator, - bool approved - ) public virtual override { - operatorApprovals[msg.sender][operator] = approved; - emit ApprovalFor(msg.sender, operator, approved); - } - - // READABLES - function totalSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return (activeSupply(classId, nonceId) + - burnedSupply(classId, nonceId) + - redeemedSupply(classId, nonceId) - ); - } - - function activeSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return _classes[classId].nonces[nonceId]._activeSupply; - } - - function burnedSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return _classes[classId].nonces[nonceId]._burnedSupply; - } - - function redeemedSupply(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256) - { - return _classes[classId].nonces[nonceId]._redeemedSupply; - } - - function balanceOf( - address account, - uint256 classId, - uint256 nonceId - ) public view override returns (uint256) { - require( - account != address(0), - "ERC3475: balance query for the zero address" - ); - return _classes[classId].nonces[nonceId]._balances[account]; - } - - function classMetadata(uint256 metadataId) - external - view - override - returns (Metadata memory) { - return (_classMetadata[metadataId]); - } - - function nonceMetadata(uint256 classId, uint256 metadataId) - external - view - override - returns (Metadata memory) { - return (_classes[classId]._nonceMetadata[metadataId]); - } - - function classValues(uint256 classId, uint256 metadataId) - external - view - override - returns (Values memory) { - return (_classes[classId]._values[metadataId]); - } - - function nonceValues(uint256 classId, uint256 nonceId, uint256 metadataId) - external - view - override - returns (Values memory) { - return (_classes[classId].nonces[nonceId]._values[metadataId]); - } - - /** determines the progress till the redemption of the bonds is valid (based on the type of bonds class). - * @notice ProgressAchieved and `progressRemaining` is abstract. - For e.g. we are giving time passed and time remaining. - */ - function getProgress(uint256 classId, uint256 nonceId) - public - view - override - returns (uint256 progressAchieved, uint256 progressRemaining){ - uint256 issuanceDate = _classes[classId].nonces[nonceId]._values[0].uintValue; - uint256 maturityDate = issuanceDate + _classes[classId].nonces[nonceId]._values[5].uintValue; - - // check whether the bond is being already initialized: - progressAchieved = block.timestamp - issuanceDate; - progressRemaining = block.timestamp < maturityDate - ? maturityDate - block.timestamp - : 0; - } - /** - gets the allowance of the bonds identified by (classId,nonceId) held by _owner to be spend by spender. - */ - function allowance( - address _owner, - address spender, - uint256 classId, - uint256 nonceId - ) public view virtual override returns (uint256) { - return _classes[classId].nonces[nonceId]._allowances[_owner][spender]; - } - - /** - checks the status of approval to transfer the ownership of bonds by _owner to operator. - */ - function isApprovedFor( - address _owner, - address operator - ) public view virtual override returns (bool) { - return operatorApprovals[_owner][operator]; - } - - // INTERNALS - function _transferFrom( - address _from, - address _to, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not enough bond to transfer" - ); - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._balances[_to] += _transaction._amount; - } - - function _transferAllowanceFrom( - address _operator, - address _from, - address _to, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not allowed amount" - ); - // reducing the allowance and decreasing accordingly. - nonce._allowances[_from][_operator] -= _transaction._amount; - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._balances[_to] += _transaction._amount; - } - - function _issue( - address _to, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - - //transfer balance - nonce._balances[_to] += _transaction._amount; - nonce._activeSupply += _transaction._amount; - } - - - function _redeem( - address _from, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - // verify whether _amount of bonds to be redeemed are sufficient available for the given nonce of the bonds - - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not enough bond to transfer" - ); - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._activeSupply -= _transaction._amount; - nonce._redeemedSupply += _transaction._amount; - } - - - function _burn( - address _from, - IERC3475.Transaction calldata _transaction - ) private { - Nonce storage nonce = _classes[_transaction.classId].nonces[_transaction.nonceId]; - // verify whether _amount of bonds to be burned are sfficient available for the given nonce of the bonds - require( - nonce._balances[_from] >= _transaction._amount, - "ERC3475: not enough bond to transfer" - ); - - //transfer balance - nonce._balances[_from] -= _transaction._amount; - nonce._activeSupply -= _transaction._amount; - nonce._burnedSupply += _transaction._amount; - } - -} diff --git a/assets/eip-3475/ERC3475.test.ts b/assets/eip-3475/ERC3475.test.ts deleted file mode 100644 index cf94602b1e2016..00000000000000 --- a/assets/eip-3475/ERC3475.test.ts +++ /dev/null @@ -1,333 +0,0 @@ -// @notice : run the typechain generate commance into the smrt contract repository (truffle in our case), after the contracts are compiled. -import { ERC3475Instance } from "../types/truffle-contracts"; - -function sleep(ms: any) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); - -} - - - - -const Bond = artifacts.require("ERC3475"); - -contract('Bond', async (accounts: string[]) => { - - let bondContract: ERC3475Instance; - const lender = accounts[1]; - const operator = accounts[2]; - const secondaryMarketBuyer = accounts[3]; - const secondaryMarketBuyer2 = accounts[4]; - const spender = accounts[5]; - - const DBITClassId: number = 0; - const firstNonceId: number = 0; - - interface _transaction { - classId: string | number | BN; - nonceId: string | number | BN; - amount: string | number | BN; - } - - before('testing', async () => { - bondContract = await Bond.deployed(); - - }) - - it('should issue bonds to a lender', async () => { - let _transactionIssuer: _transaction[] - = - [{ - classId: DBITClassId, - nonceId: firstNonceId, - amount: 7000 - }]; - - await bondContract.issue(lender, _transactionIssuer, { from: accounts[0] }) - await bondContract.issue(lender, _transactionIssuer, { from: accounts[0] }) - const balance = (await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber() - const activeSupply = (await bondContract.activeSupply(DBITClassId, firstNonceId)).toNumber() - assert.equal(balance, 14000); - assert.equal(activeSupply, 14000); - }) - it('lender should be able to transfer bonds to another address', async () => { - - const transferBonds: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 2000 - }]; - await bondContract.transferFrom(lender, secondaryMarketBuyer, transferBonds, { from: lender }) - - const lenderBalance = (await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber() - const secondaryBuyerBalance = (await bondContract.balanceOf(secondaryMarketBuyer, DBITClassId, firstNonceId)).toNumber() - const activeSupply = (await bondContract.activeSupply(DBITClassId, firstNonceId)).toNumber() - - assert.equal(lenderBalance, 12000); - assert.equal(secondaryBuyerBalance, 2000); - assert.equal(activeSupply, 14000); - }) - it('operator should be able to manipulate bonds after approval', async () => { - const transactionApproval: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 2000 - }]; - - await bondContract.setApprovalFor(operator, true, { from: lender }) - const isApproved = await bondContract.isApprovedFor(lender, operator); - assert.isTrue(isApproved); - await bondContract.transferFrom(lender, secondaryMarketBuyer2, transactionApproval, { from: operator }) - expect((await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber()).to.equal(10000); - expect((await bondContract.balanceOf(secondaryMarketBuyer2, DBITClassId, firstNonceId)).toNumber()).to.equal(2000); - - }) - - it('lender should redeem bonds when conditions are met', async () => { - const redemptionTransaction: _transaction[] = [ - { - classId: 1, - nonceId: 1, - amount: 2000 - - }, - ]; - await bondContract.issue(accounts[2],redemptionTransaction, {from: accounts[2]}); - assert.equal((await bondContract.balanceOf(accounts[2], 1, 1)).toNumber(), 2000); - // adding delay for passing the redemption time period. - await sleep(7000); - - await bondContract.redeem(accounts[2], redemptionTransaction, {from:accounts[2]}); - - assert.equal((await bondContract.balanceOf(accounts[2], DBITClassId, firstNonceId)).toNumber(), 0); - }) - - - it('lender should not be able to redeem bonds when conditions are not met', async () => { - const redemptionTransaction: _transaction[] = [ - - { - classId: 0, - nonceId: 0, - amount: 2000 - - }, - - ]; - - await bondContract.issue(accounts[2],redemptionTransaction, {from: accounts[2]}); - assert.equal((await bondContract.balanceOf(accounts[2], 0, 0)).toNumber(), 2000); - try { - await bondContract.redeem(accounts[2], redemptionTransaction, {from:accounts[2]}); - } - catch(e:any){ - assert.isTrue(true); - } - - }) - //////////////////// UNIT TESTS ////////////////////////////// - - it('should transfer bonds from an caller address to another', async () => { - const transactionTransfer: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }]; - await bondContract.issue(lender, transactionTransfer, { from: lender }); - const tx = (await bondContract.transferFrom(lender, secondaryMarketBuyer, transactionTransfer, {from:lender})).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should issue bonds to a given address', async () => { - - const transactionIssue: _transaction[] = [ - { - classId: 1, - nonceId: firstNonceId, - amount: 500 - - } - ]; - const tx = (await bondContract.issue(lender, transactionIssue)).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should redeem bonds from a given address', async () => { - const transactionRedeem: _transaction[] = [ - { - classId: 1, - nonceId: firstNonceId, - amount: 500 - - }]; - await bondContract.issue(lender, transactionRedeem, {from: lender}); - sleep(7000); - - const tx = (await bondContract.redeem(lender, transactionRedeem, {from:lender})).tx; - - console.log(tx) - assert.isString(tx); - }) - - it('should burn bonds from a given address', async () => { - const transactionRedeem: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }]; - - await bondContract.issue(lender, transactionRedeem, {from: lender}); - const tx = (await bondContract.burn(lender, transactionRedeem, {from:lender})).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should approve spender to manage a given amount of bonds from the caller address', async () => { - const transactionApprove: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }]; - - await bondContract.issue(lender, transactionApprove, {from: lender}); - const tx = (await bondContract.approve(spender, transactionApprove)).tx; - console.log(tx) - assert.isString(tx); - }) - - it('setApprovalFor (called by bond owner) should be able to give operator permissions to manage bonds for given classId', async () => { - const tx = (await bondContract.setApprovalFor(operator, true, { from: lender })).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should batch approve', async () => { - - const transactionApprove: _transaction[] = [ - { - classId: DBITClassId, - nonceId: firstNonceId, - amount: 500 - }, - { classId: 1, nonceId: 0, amount: 900 } - - ]; - - - await await bondContract.issue(spender,transactionApprove, {from:spender}); - const tx = (await bondContract.approve(spender, transactionApprove, {from:spender})).tx; - console.log(tx) - assert.isString(tx); - }) - - it('should return the active supply', async () => { - const activeSupply = (await bondContract.activeSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(activeSupply) - assert.isNumber(activeSupply); - }) - - it('should return the redeemed supply', async () => { - const redeemedSupply = (await bondContract.redeemedSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(redeemedSupply) - assert.isNumber(redeemedSupply); - }) - - it('should return the burned supply', async () => { - const burnedSupply = (await bondContract.burnedSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(burnedSupply) - assert.isNumber(burnedSupply); - }) - - it('should return the total supply', async () => { - const totalSupply = (await bondContract.totalSupply(DBITClassId, firstNonceId)).toNumber(); - console.log(totalSupply) - assert.isNumber(totalSupply); - }) - - it('should return the balanceOf a bond of a given address', async () => { - const balanceOf = (await bondContract.balanceOf(lender, DBITClassId, firstNonceId)).toNumber(); - console.log(balanceOf) - assert.isNumber(balanceOf); - }) - - it('should return the symbol of a class of bond', async () => { - let metadataId = 0; - const symbol: { - stringValue: string; - uintValue: BN; - addressValue: string; - boolValue: boolean; - } = (await bondContract.classValues(DBITClassId, metadataId)); - console.log(JSON.stringify(symbol)); - assert.isString(symbol.stringValue); - }) - - it('should return the Values for given bond class', async () => { - const metadataId = 0; - - let _transactionIssuer: _transaction[] - = - [{ - classId: DBITClassId, - nonceId: firstNonceId, - amount: 7000 - }]; - - await bondContract.issue(lender, _transactionIssuer, { from: accounts[0] }) - const valuesClass = (await bondContract.classValues(DBITClassId, metadataId)); - console.log("class infos: ", JSON.stringify(valuesClass)); - assert.isString(valuesClass.toString()); - }) - - it('should return the infos of a nonce for given bond class', async () => { - const metadataId = 0; - const infos = (await bondContract.nonceValues(DBITClassId, firstNonceId, metadataId)); - console.log("nonce infos: ", JSON.stringify(infos)) - assert.isString(infos.toString()); - }) - - it('should return if an operator is approved on a class and nonce given for an address', async () => { - const isApproved = (await bondContract.isApprovedFor(lender, operator)); - console.log("operator is Approved? : ", isApproved) - assert.isBoolean(isApproved); - }) - - it('should return if its redeemable', async () => { - let _transactionIssuer: _transaction[] - = - [{ - classId: 1, - nonceId: 1, - amount: 7000 - }]; - - await bondContract.issue(accounts[1], _transactionIssuer, { from: accounts[1] }) - const getProgress = await bondContract.getProgress(1,1); - console.log("is Redeemable? : ", getProgress[1].toNumber() >= 0) - assert.isNumber(getProgress[1].toNumber()); - - }) - - it('should set allowance of a spender', async () => { - - const allowance = (await bondContract.allowance(lender, spender, DBITClassId, firstNonceId, {from:lender})).toNumber(); - console.log("allowance : ", allowance) - assert.isNumber(allowance); - }) - - it('should return if operator is approved for', async () => { - const isApproved = (await bondContract.isApprovedFor(lender, operator)); - console.log("operator is Approved? : ", isApproved) - assert.isTrue(isApproved); - }) - -}); diff --git a/assets/eip-3475/Metadata.md b/assets/eip-3475/Metadata.md deleted file mode 100644 index ac7c36f3ae9a5e..00000000000000 --- a/assets/eip-3475/Metadata.md +++ /dev/null @@ -1,92 +0,0 @@ -# Metadata standards - - -This documentation consists of various JSON schemas (examples or standards) that can be referenced by the reader of this EIP for implementing EIP-3475 bonds storage. - -## 1. Description metadata: - -```json -[ - { - "title": "defining the title information", - "_type": "explaining the type of the title information added", - "description": "little description about the information stored in the bond", - } -] -``` - -Example: adding details in bonds describing the local jurisdiction of the bonds where it's issued: - -```json -{ -"title": "localisation", -"_type": "string", -"description": "jurisdiction law codes compatibility" -"values": ["fr ", "de", "ch"] -} -``` -The 'values' field defined above can also be ISO codes or other hex standard representation. -## 2. Nonce metadata: - -- **Information defining the state of the bond** - -```json -[ - { - "title": "maturity", - "_type": "uint", - "description": "Lorem ipsum...", - "values": [0, 0, 0] - } -] -``` - - -## 3. Class metadata: - -```json -[ - { - "title": "symbol", - "_type": "string", - "description": "Lorem ipsum...", - "values": ["Class symbol 1", "Class symbol 2", "Class symbol 3"], - }, - { - "title": "issuer", - "_type": "string", - "description": "Lorem ipsum...", - "values": ["Issuer name 1", "Issuer name 2", "Issuer name 3"], - }, - - { - "title": "issuer_address", - "_type": "address", - "description": "Lorem ipsum...", - "values":["Address 1.", "Address 2", "Address 3"] - }, - - { - "title": "class_type", - "_type": "string", - "description": "Lorem ipsum...", - "values": ["Class Type 1", "Class Type 2", "Class Type 3"] - }, - - { - "title": "token_address", - "_type": "address", - "description": "Lorem ipsum...", - "values":["Address 1.", "Address 2", "Address 3"] - }, - - { - "title": "period", - "_type": "uint", - "description": "Lorem ipsum...", - "values": [0, 0, 0] - } -] -``` -## Examples of other standards: - - ISO-20022 standard is the recently adopted standard by banks for communicating financial operators (Banks, trading intermediaries, underwriters) that also include bond operations. diff --git a/assets/eip-3475/interfaces/IERC3475.sol b/assets/eip-3475/interfaces/IERC3475.sol deleted file mode 100644 index 9fa2b1b44c2c5e..00000000000000 --- a/assets/eip-3475/interfaces/IERC3475.sol +++ /dev/null @@ -1,218 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - - -pragma solidity ^0.8.0; - - -interface IERC3475 { - // STRUCTURE - /** - * @dev Values structure of the Metadata - */ - struct Values { - string stringValue; - uint uintValue; - address addressValue; - bool boolValue; - } - /** - * @dev structure allows to define particular bond metadata (ie the values in the class as well as nonce inputs). - * @notice 'title' defining the title information, - * @notice '_type' explaining the data type of the title information added (eg int, bool, address), - * @notice 'description' explains little description about the information stored in the bond", - */ - struct Metadata { - string title; - string _type; - string description; - } - /** - * @dev structure that defines the parameters for specific issuance of bonds and amount which are to be transferred/issued/given allowance, etc. - * @notice this structure is used to streamline the input parameters for functions of this standard with that of other Token standards like ERC20. - * @classId is the class id of the bond. - * @nonceId is the nonce id of the given bond class. This param is for distinctions of the issuing conditions of the bond. - * @amount is the amount of the bond that will be transferred. - */ - struct Transaction { - uint256 classId; - uint256 nonceId; - uint256 _amount; - } - - // WRITABLES - /** - * @dev allows the transfer of a bond from one address to another (either single or in batches). - * @param _from is the address of the holder whose balance is about to decrease. - * @param _to is the address of the recipient whose balance is about to increase. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be transferred}. - */ - function transferFrom(address _from, address _to, Transaction[] calldata _transactions) external; - /** - * @dev allows the transfer of allowance from one address to another (either single or in batches). - * @param _from is the address of the holder whose balance about to decrease. - * @param _to is the address of the recipient whose balance is about to increased. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be allowed to transferred}. - */ - function transferAllowanceFrom(address _from, address _to, Transaction[] calldata _transactions) external; - /** - * @dev allows issuing of any number of bond types to an address(either single/batched issuance). - * The calling of this function needs to be restricted to bond issuer contract. - * @param _to is the address to which the bond will be issued. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be issued for given whitelisted bond}. - */ - function issue(address _to, Transaction[] calldata _transactions) external; - /** - * @dev allows redemption of any number of bond types from an address. - * The calling of this function needs to be restricted to bond issuer contract. - * @param _from is the address _from which the bond will be redeemed. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be redeemed for given whitelisted bond}. - */ - function redeem(address _from, Transaction[] calldata _transactions) external; - - /** - * @dev allows the transfer of any number of bond types from an address to another. - * The calling of this function needs to be restricted to bond issuer contract. - * @param _from is the address of the holder whose balance about to decrees. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be redeemed for given whitelisted bond}. - */ - function burn(address _from, Transaction[] calldata _transactions) external; - - /** - * @dev Allows _spender to withdraw from your account multiple times, up to the amount. - * @notice If this function is called again, it overwrites the current allowance with amount. - * @param _spender is the address the caller approve for his bonds. - * @param _transactions is the object defining {class,nonce and amount of the bonds to be approved for given whitelisted bond}. - */ - function approve(address _spender, Transaction[] calldata _transactions) external; - - /** - * @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. - * @dev MUST emit the ApprovalForAll event on success. - * @param _operator Address to add to the set of authorized operators - * @param _approved "True" if the operator is approved, "False" to revoke approval. - */ - function setApprovalFor(address _operator, bool _approved) external; - - // READABLES - - /** - * @dev Returns the total supply of the bond in question. - */ - function totalSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the redeemed supply of the bond in question. - */ - function redeemedSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the active supply of the bond in question. - */ - function activeSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the burned supply of the bond in question. - */ - function burnedSupply(uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the balance of the giving bond classId and bond nonce. - */ - function balanceOf(address _account, uint256 classId, uint256 nonceId) external view returns (uint256); - - /** - * @dev Returns the JSON metadata of the classes. - * The metadata SHOULD follow a set of structure explained later in eip-3475.md - * @param metadataId is the index corresponding to the class parameter that you want to return from mapping. - */ - function classMetadata(uint256 metadataId) external view returns ( Metadata memory); - - /** - * @dev Returns the JSON metadata of the Values of the nonces in the corresponding class. - * @param classId is the specific classId of which you want to find the metadata of the corresponding nonce. - * @param metadataId is the index corresponding to the class parameter that you want to return from mapping. - * @notice The metadata SHOULD follow a set of structure explained later in metadata section. - */ - function nonceMetadata(uint256 classId, uint256 metadataId) external view returns ( Metadata memory); - - /** - * @dev Returns the values of the given classId. - * @param classId is the specific classId of which we want to return the parameter. - * @param metadataId is the index corresponding to the class parameter that you want to return from mapping. - * the metadata SHOULD follow a set of structures explained in eip-3475.md - */ - function classValues(uint256 classId, uint256 metadataId) external view returns ( Values memory); - - /** - * @dev Returns the values of given nonceId. - * @param metadataId index number of structure as explained in the metadata section in EIP-3475. - * @param classId is the class of bonds for which you determine the nonce. - * @param nonceId is the nonce for which you return the value struct info. - * Returns the values object corresponding to the given value. - */ - function nonceValues(uint256 classId, uint256 nonceId, uint256 metadataId) external view returns ( Values memory); - - /** - * @dev Returns the information about the progress needed to redeem the bond identified by classId and nonceId. - * @notice Every bond contract can have its own logic concerning the progress definition. - * @param classId The class of bonds. - * @param nonceId is the nonce of bonds for finding the progress. - * Returns progressAchieved is the current progress achieved. - * Returns progressRemaining is the remaining progress. - */ - function getProgress(uint256 classId, uint256 nonceId) external view returns (uint256 progressAchieved, uint256 progressRemaining); - - /** - * @notice Returns the amount that spender is still allowed to withdraw from _owner (for given classId and nonceId issuance) - * @param _owner is the address whose owner allocates some amount to the _spender address. - * @param classId is the classId of the bond. - * @param nonceId is the nonce corresponding to the class for which you are approving the spending of total amount of bonds. - */ - function allowance(address _owner, address _spender, uint256 classId, uint256 nonceId) external view returns (uint256); - /** - * @notice Queries the approval status of an operator for bonds (for all classes and nonce issuances of owner). - * @param _owner is the current holder of the bonds for all classes/nonces. - * @param _operator is the address with access to the bonds of _owner for transferring. - * Returns "true" if the operator is approved, "false" if not. - */ - function isApprovedFor(address _owner, address _operator) external view returns (bool); - - // EVENTS - /** - * @notice MUST trigger when tokens are transferred, including zero value transfers. - * e.g: - emit Transfer(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef, 0x492Af743654549b12b1B807a9E0e8F397E44236E,0x3d03B6C79B75eE7aB35298878D05fe36DC1fEf, [IERC3475.Transaction(1,14,500)]) - means that operator 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef wants to transfer 500 bonds of class 1 , Nonce 14 of owner 0x492Af743654549b12b1B807a9E0e8F397E44236E to address 0x3d03B6C79B75eE7aB35298878D05fe36DC1fEf. - */ - event Transfer(address indexed _operator, address indexed _from, address indexed _to, Transaction[] _transactions); - /** - * @notice MUST trigger when tokens are issued - * @notice Issue MUST trigger when Bonds are issued. This SHOULD not include zero value Issuing. - * @dev This SHOULD not include zero value issuing. - * @dev Issue MUST be triggered when the operator (i.e Bank address) contract issues bonds to the given entity. - eg: emit Issue(_operator, 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,[IERC3475.Transaction(1,14,500)]); - issue by address(operator) 500 Bonds(nonce14,class 0) to address 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. - */ - event Issue(address indexed _operator, address indexed _to, Transaction[] _transactions); - /** - * @notice MUST trigger when tokens are redeemed. - * @notice Redeem MUST trigger when Bonds are redeemed. This SHOULD not include zero value redemption. - * eg: emit Redeem(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,0x492Af743654549b12b1B807a9E0e8F397E44236E,[IERC3475.Transaction(1,14,500)]); - * this emit event when 5000 bonds of class 1, nonce 14 owned by address 0x492Af743654549b12b1B807a9E0e8F397E44236E are being redeemed by 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. - */ - event Redeem(address indexed _operator, address indexed _from, Transaction[] _transactions); - /** - * @notice MUST trigger when tokens are burned - * @dev `Burn` MUST trigger when the bonds are being redeemed via staking (or being invalidated) by the bank contract. - * @dev `Burn` MUST trigger when Bonds are burned. This SHOULD not include zero value burning - * @notice emit Burn(0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef,0x492Af743654549b12b1B807a9E0e8F397E44236E,[IERC3475.Transaction(1,14,500)]); - * emits event when 5000 bonds of owner 0x492Af743654549b12b1B807a9E0e8F397E44236E of type (class 1, nonce 14) are burned by operator 0x2d03B6C79B75eE7aB35298878D05fe36DC1fE8Ef. - */ - event Burn(address indexed _operator, address indexed _from, Transaction[] _transactions); - /** - * @dev MUST emit when approval for a second party/operator address to manage all bonds from a classId given for an owner address is enabled or disabled (absence of an event assumes disabled). - * @dev its emitted when address(_owner) approves the address(_operator) to transfer his bonds. - * @notice Approval MUST trigger when bond holders are approving an _operator. This SHOULD not include zero value approval. - */ - event ApprovalFor(address indexed _owner, address indexed _operator, bool _approved); -} diff --git a/assets/eip-3525/README.md b/assets/eip-3525/README.md deleted file mode 100644 index a2c57b6d76006d..00000000000000 --- a/assets/eip-3525/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# EIP-3525 - -## Demonstration only - -The code included in this directory is ONLY for the purpose of demonstrating how to implement this proposal, it is not a full-featured implementation ready for production. - -So please DO NOT use the code here for purposes other than study. diff --git a/assets/eip-3525/contracts/ERC3525.sol b/assets/eip-3525/contracts/ERC3525.sol deleted file mode 100644 index b955f6949d408c..00000000000000 --- a/assets/eip-3525/contracts/ERC3525.sol +++ /dev/null @@ -1,223 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "ERC721Enumerable.sol"; -import "./interface/IERC3525.sol"; -import "./interface/IERC3525Metadata.sol"; -import "./interface/IERC3525Receiver.sol"; - -contract ERC3525 is IERC3525, IERC3525Metadata, ERC721Enumerable { - using Address for address; - using Strings for uint256; - - struct ApproveData { - address[] approvals; - mapping(address => uint256) allowances; - } - - /// @dev tokenId => values - mapping(uint256 => uint256) internal _values; - - /// @dev tokenId => operator => units - mapping(uint256 => ApproveData) private _approvedValues; - - /// @dev tokenId => slot - mapping(uint256 => uint256) internal _slots; - - string private _name; - string private _symbol; - uint8 private _decimals; - - constructor( string memory name_, string memory symbol_, uint8 decimals_) ERC721(name_, symbol_) { - _decimals = decimals_; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721Enumerable) returns (bool) - { - return - interfaceId == type(IERC3525).interfaceId || - interfaceId == type(IERC3525Metadata).interfaceId || - super.supportsInterface(interfaceId); - } - - function valueDecimals() public view virtual override returns (uint8) { - return _decimals; - } - - function balanceOf(uint256 tokenId_) public view virtual override returns (uint256) - { - require( _exists(tokenId_), "ERC3525: balance query for nonexistent token"); - return _values[tokenId_]; - } - - function slotOf(uint256 tokenId_) public view virtual override returns (uint256) - { - require(_exists(tokenId_), "ERC3525: slot query for nonexistent token"); - return _slots[tokenId_]; - } - - function contractURI() public view virtual override returns (string memory) - { - string memory baseURI = _baseURI(); - return - bytes(baseURI).length > 0 - ? string( abi.encodePacked( baseURI, "contract/", Strings.toHexString(uint256(uint160(address(this)))))) : ""; - } - - function slotURI(uint256 slot_) public view virtual override returns (string memory) - { - string memory baseURI = _baseURI(); - return - bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, "slot/", slot_.toString())) : ""; - } - - function approve( uint256 tokenId_, address to_, uint256 value_) external payable virtual override { - address owner = ERC721.ownerOf(tokenId_); - require(to_ != owner, "ERC3525: approval to current owner"); - - require( - ERC721._isApprovedOrOwner(_msgSender(), tokenId_), - "ERC3525: approve caller is not owner nor approved for all" - ); - - _approveValue(tokenId_, to_, value_); - } - - function allowance(uint256 tokenId_, address operator_) public view virtual override returns (uint256) - { - return _approvedValues[tokenId_].allowances[operator_]; - } - - function transferFrom( uint256 fromTokenId_, address to_, uint256 value_) public payable virtual override returns (uint256) { - _spendAllowance(_msgSender(), fromTokenId_, value_); - - uint256 newTokenId = _getNewTokenId(fromTokenId_); - _mint(to_, newTokenId, _slots[fromTokenId_]); - _transfer(fromTokenId_, newTokenId, value_); - - return newTokenId; - } - - function transferFrom( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) public payable virtual override { - _spendAllowance(_msgSender(), fromTokenId_, value_); - - _transfer(fromTokenId_, toTokenId_, value_); - } - - function _mint( address to_, uint256 tokenId_, uint256 slot_) private { - ERC721._mint(to_, tokenId_); - _slots[tokenId_] = slot_; - emit SlotChanged(tokenId_, 0, slot_); - } - - function _mintValue( address to_, uint256 tokenId_, uint256 slot_, uint256 value_) internal virtual { - require(to_ != address(0), "ERC3525: mint to the zero address"); - require(tokenId_ != 0, "ERC3525: cannot mint zero tokenId"); - require(!_exists(tokenId_), "ERC3525: token already minted"); - - _mint(to_, tokenId_, slot_); - - _beforeValueTransfer(address(0), to_, 0, tokenId_, slot_, value_); - _values[tokenId_] = value_; - _afterValueTransfer(address(0), to_, 0, tokenId_, slot_, value_); - - emit TransferValue(0, tokenId_, value_); - } - - function _burn(uint256 tokenId_) internal virtual override { - address owner = ERC721.ownerOf(tokenId_); - ERC721._burn(tokenId_); - - uint256 slot = _slots[tokenId_]; - uint256 value = _values[tokenId_]; - - _beforeValueTransfer(owner, address(0), tokenId_, 0, slot, value); - delete _slots[tokenId_]; - delete _values[tokenId_]; - _afterValueTransfer(owner, address(0), tokenId_, 0, slot, value); - - emit TransferValue(tokenId_, 0, value); - emit SlotChanged(tokenId_, slot, 0); - } - - function _transfer( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_) internal virtual { - require( _exists(fromTokenId_), - "ERC35255: transfer from nonexistent token"); - require(_exists(toTokenId_), "ERC35255: transfer to nonexistent token"); - - require( _values[fromTokenId_] >= value_, - "ERC3525: transfer amount exceeds balance"); - require( _slots[fromTokenId_] == _slots[toTokenId_], - "ERC3535: transfer to token with different slot"); - - address from = ERC721.ownerOf(fromTokenId_); - address to = ERC721.ownerOf(toTokenId_); - _beforeValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); - - _values[fromTokenId_] -= value_; - _values[toTokenId_] += value_; - - _afterValueTransfer(from, to, fromTokenId_, toTokenId_, _slots[fromTokenId_], value_); - - emit TransferValue(fromTokenId_, toTokenId_, value_); - } - - function _spendAllowance( address operator_, uint256 tokenId_, uint256 value_) internal virtual { - uint256 currentAllowance = ERC3525.allowance(tokenId_, operator_); - if ( !_isApprovedOrOwner(operator_, tokenId_) && currentAllowance != type(uint256).max) { - require( currentAllowance >= value_, "ERC3525: insufficient allowance"); - _approveValue(tokenId_, operator_, currentAllowance - value_); - } - } - - function _approveValue( uint256 tokenId_, address to_, uint256 value_) internal virtual { - ApproveData storage approveData = _approvedValues[tokenId_]; - approveData.approvals.push(to_); - approveData.allowances[to_] = value_; - - emit ApprovalValue(tokenId_, to_, value_); - } - - function _getNewTokenId(uint256 fromTokenId_) internal virtual returns (uint256) - { - return ERC721Enumerable.totalSupply() + 1; - } - - function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override { - //clear approve data - uint256 length = _approvedValues[tokenId].approvals.length; - for (uint256 i = 0; i < length; i++) { - address approval = _approvedValues[tokenId].approvals[i]; - delete _approvedValues[tokenId].allowances[approval]; - } - delete _approvedValues[tokenId].approvals; - } - - function _afterTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {} - - function _checkOnERC3525Received( uint256 fromTokenId_, uint256 toTokenId_, uint256 value_, bytes memory data_) private returns (bool) { - address to = ERC721.ownerOf((toTokenId_)); - if (to.isContract() && IERC165(to).supportsInterface(type(IERC3525Receiver).interfaceId)) { - try - IERC3525Receiver(to).onERC3525Received( _msgSender(), fromTokenId_, toTokenId_, value_, data_) - returns (bytes4 retval) { - return retval == IERC3525Receiver.onERC3525Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert( "ERC3525: transfer to non ERC3525Receiver implementer"); - } else { - // solhint-disable-next-line - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - function _beforeValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, uint256 value_) internal virtual {} - - function _afterValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, uint256 value_) internal virtual {} -} diff --git a/assets/eip-3525/contracts/ERC3525Example.sol b/assets/eip-3525/contracts/ERC3525Example.sol deleted file mode 100644 index 16df657cc46cba..00000000000000 --- a/assets/eip-3525/contracts/ERC3525Example.sol +++ /dev/null @@ -1,154 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "./ERC3525.sol"; -import "StringConvertor.sol"; -import "base64.sol"; - -/** - * This is a demo contract for how to generate slot - */ -contract ERC3525Example is ERC3525 { - using StringConvertor for uint256; - - /** - * @notice Properties of the slot, which determine the value of slot. - */ - struct SlotDetail { - string name; - string description; - string image; - address underlying; - uint8 vestingType; - uint32 maturity; - uint32 term; - } - - // slot => slotDetail - mapping(uint256 => SlotDetail) private _slotDetails; - - uint256 constant _externalMintMaxId = 1000000000; - - constructor( string memory name_, string memory symbol_, uint8 decimals_) ERC3525(name_, symbol_, decimals_) {} - - function mint( string memory slotName_, string memory slotDescription_, string memory slotImage_, - uint256 tokenId_, address underlying_, uint8 vestingType_, uint32 maturity_, uint32 term_, uint256 value_) public { - require(tokenId_ < _externalMintMaxId, "ERC3525: tokenId is too large"); - uint256 slot = _getSlot(underlying_, vestingType_, maturity_, term_); - _slotDetails[slot] = SlotDetail({ - name: slotName_, - description: slotDescription_, - image: slotImage_, - underlying: underlying_, - vestingType: vestingType_, - maturity: maturity_, - term: term_ - }); - - ERC3525._mintValue(_msgSender(), tokenId_, slot, value_); - } - - function getSlotDetail(uint256 slot_) public view returns (SlotDetail memory) { - return _slotDetails[slot_]; - } - - function _getNewTokenId(uint256 fromTokenId_) internal virtual override returns (uint256) { - return 1000000000 + fromTokenId_; - } - - /** - * @dev Generate the value of slot by utilizing keccak256 algorithm to calculate the hash - * value of multi properties. - */ - function _getSlot( address underlying_, uint8 vestingType_, uint32 maturity_, uint32 term_) internal pure virtual returns (uint256 slot_) { - return - uint256( - keccak256( - abi.encodePacked( - underlying_, - vestingType_, - maturity_, - term_ - ) - ) - ); - } - - function slotURI(uint256 slot_) public view virtual override returns (string memory) { - return - string( - abi.encodePacked( - /* solhint-disable */ - "data:application/json;base64,", - Base64.encode( - abi.encodePacked( - '{"name":"', - _slotDetails[slot_].name, - '","description":"', - _slotDetails[slot_].description, - '","image":"', - _slotDetails[slot_].image, - '","properties":', - _slotProperties(slot_), - "}" - ) - ) - /* solhint-enable */ - ) - ); - } - - /** - * @dev Generate the content of the `properties` field of `slotURI`. - */ - function _slotProperties(uint256 slot_) internal view returns (string memory) { - SlotDetail storage slotDetail = _slotDetails[slot_]; - return - string( - /* solhint-disable */ - abi.encodePacked( - "[", - abi.encodePacked( - '{"name":"underlying",', - '"description":"Address of the underlying token locked in this contract.",', - '"value":"', - Strings.toHexString( - uint256(uint160(slotDetail.underlying)) - ), - '",', - '"order":1,', - '"display_type":"string"},' - ), - abi.encodePacked( - '{"name":"vesting_type",', - '"description":"Vesting type that represents the releasing mode of underlying assets.",', - '"value":', - uint256(slotDetail.vestingType).toString(), - ",", - '"order":2,', - '"display_type":"number"},' - ), - abi.encodePacked( - '{"name":"maturity",', - '"description":"Maturity that all underlying assets would be completely released.",', - '"value":', - uint256(slotDetail.maturity).toString(), - ",", - '"order":3,', - '"display_type":"date"},' - ), - abi.encodePacked( - '{"name":"term",', - '"description":"The length of the locking period (in seconds)",', - '"value":', - uint256(slotDetail.term).toString(), - ",", - '"order":4,', - '"display_type":"number"}' - ), - "]" - ) - /* solhint-enable */ - ); - } -} diff --git a/assets/eip-3525/contracts/ERC3525SlotApprovable.sol b/assets/eip-3525/contracts/ERC3525SlotApprovable.sol deleted file mode 100644 index 871df345cbc05f..00000000000000 --- a/assets/eip-3525/contracts/ERC3525SlotApprovable.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC3525.sol"; -import "./interface/IERC3525SlotApprovable.sol"; - -abstract contract ERC3525SlotApprovable is ERC3525, IERC3525SlotApprovable { - // @dev owner => slot => operator => approved - mapping(address => mapping(uint256 => mapping(address => bool))) - private _slotApprovals; - - function setApprovalForSlot( address owner_, uint256 slot_, address operator_, bool approved_) external payable virtual override { - require( - _msgSender() == owner_ || isApprovedForAll(owner_, _msgSender()), - "ERC3525SlotApprovable: caller is not owner nor approved for all" - ); - _setApprovalForSlot(owner_, slot_, operator_, approved_); - } - - function isApprovedForSlot( address owner_, uint256 slot_, address operator_) public view virtual override returns (bool) { - return _slotApprovals[owner_][slot_][operator_]; - } - - function approve(address to_, uint256 tokenId_) public virtual override(IERC721, ERC721) { - address owner = ERC721.ownerOf(tokenId_); - uint256 slot = ERC3525.slotOf(tokenId_); - require(to_ != owner, "ERC3525: approval to current owner"); - - require( - _msgSender() == owner || - ERC721.isApprovedForAll(owner, _msgSender()) || - ERC3525SlotApprovable.isApprovedForSlot( - owner, - slot, - _msgSender() - ), - "ERC3525: caller is not owner nor approved" - ); - - _approve(to_, tokenId_); - } - - function approve(uint256 tokenId_, address to_, uint256 value_) external payable virtual override(IERC3525, ERC3525) { - address owner = ERC721.ownerOf(tokenId_); - require(to_ != owner, "ERC3525: approval to current owner"); - - require( - _isApprovedOrOwner(_msgSender(), tokenId_), - "ERC3525: caller is not owner nor approved" - ); - - _approveValue(tokenId_, to_, value_); - } - - function _setApprovalForSlot( address owner_, uint256 slot_, address operator_, bool approved_) internal virtual { - require(owner_ != operator_, "ERC3525SlotApprovable: approve to owner"); - _slotApprovals[owner_][slot_][operator_] = approved_; - emit ApprovalForSlot(owner_, slot_, operator_, approved_); - } - - function _isApprovedOrOwner(address operator_, uint256 tokenId_) internal view virtual override returns (bool) { - require( - _exists(tokenId_), - "ERC3525: operator query for nonexistent token" - ); - address owner = ERC721.ownerOf(tokenId_); - uint256 slot = ERC3525.slotOf(tokenId_); - return (operator_ == owner || - getApproved(tokenId_) == operator_ || - ERC721.isApprovedForAll(owner, operator_) || - ERC3525SlotApprovable.isApprovedForSlot(owner, slot, operator_)); - } -} diff --git a/assets/eip-3525/contracts/ERC3525SlotEnumerable.sol b/assets/eip-3525/contracts/ERC3525SlotEnumerable.sol deleted file mode 100644 index 7be37111419f89..00000000000000 --- a/assets/eip-3525/contracts/ERC3525SlotEnumerable.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC3525.sol"; -import "./interface/IERC3525SlotEnumerable.sol"; - -abstract contract ERC3525SlotEnumerable is ERC3525, IERC3525SlotEnumerable { - struct SlotData { - uint256 slot; - uint256[] slotTokens; - // mapping(uint256 => uint256) slotTokensIndex; - } - - // slot => tokenId => index - mapping(uint256 => mapping(uint256 => uint256)) private _slotTokensIndex; - - SlotData[] private _allSlots; - - // slot => index - mapping(uint256 => uint256) private _allSlotsIndex; - - function slotCount() public view virtual override returns (uint256) { - return _allSlots.length; - } - - function slotByIndex(uint256 index_) public view virtual override returns (uint256) { - require( - index_ < ERC3525SlotEnumerable.slotCount(), - "ERC3525SlotEnumerable: slot index out of bounds" - ); - return _allSlots[index_].slot; - } - - function _slotExists(uint256 slot_) internal view virtual returns (bool) { - return - _allSlots.length != 0 && - _allSlots[_allSlotsIndex[slot_]].slot == slot_; - } - - function tokenSupplyInSlot(uint256 slot_) public view virtual override returns (uint256) { - if (!_slotExists(slot_)) { - return 0; - } - return _allSlots[_allSlotsIndex[slot_]].slotTokens.length; - } - - function tokenInSlotByIndex(uint256 slot_, uint256 index_) public view virtual override returns (uint256) { - require( - index_ < ERC3525SlotEnumerable.tokenSupplyInSlot(slot_), - "ERC3525SlotEnumerable: slot token index out of bounds" - ); - return _allSlots[_allSlotsIndex[slot_]].slotTokens[index_]; - } - - function _tokenExistsInSlot(uint256 slot_, uint256 tokenId_) private view returns (bool) { - SlotData storage slotData = _allSlots[_allSlotsIndex[slot_]]; - return - slotData.slotTokens.length > 0 && - slotData.slotTokens[_slotTokensIndex[slot_][tokenId_]] == tokenId_; - } - - function _beforeValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, - uint256 value_) internal virtual override { - if (from_ == address(0) && fromTokenId_ == 0 && !_slotExists(slot_)) { - SlotData memory slotData = SlotData({ - slot: slot_, - slotTokens: new uint256[](0) - }); - _addSlotToAllSlotsEnumeration(slotData); - } - - //Shh - currently unused - to_; - toTokenId_; - value_; - } - - function _afterValueTransfer( address from_, address to_, uint256 fromTokenId_, uint256 toTokenId_, uint256 slot_, - uint256 value_) internal virtual override { - if ( - from_ == address(0) && - fromTokenId_ == 0 && - !_tokenExistsInSlot(slot_, toTokenId_) - ) { - _addTokenToSlotEnumeration(slot_, toTokenId_); - } else if ( - to_ == address(0) && - toTokenId_ == 0 && - _tokenExistsInSlot(slot_, fromTokenId_) - ) { - _removeTokenFromSlotEnumeration(slot_, fromTokenId_); - } - - //Shh - currently unused - value_; - } - - function _addSlotToAllSlotsEnumeration(SlotData memory slotData) private { - _allSlotsIndex[slotData.slot] = _allSlots.length; - _allSlots.push(slotData); - } - - function _addTokenToSlotEnumeration(uint256 slot_, uint256 tokenId_) private { - SlotData storage slotData = _allSlots[_allSlotsIndex[slot_]]; - _slotTokensIndex[slot_][tokenId_] = slotData.slotTokens.length; - slotData.slotTokens.push(tokenId_); - } - - function _removeTokenFromSlotEnumeration(uint256 slot_, uint256 tokenId_) private { - SlotData storage slotData = _allSlots[_allSlotsIndex[slot_]]; - uint256 lastTokenIndex = slotData.slotTokens.length - 1; - uint256 lastTokenId = slotData.slotTokens[lastTokenIndex]; - uint256 tokenIndex = slotData.slotTokens[tokenId_]; - - slotData.slotTokens[tokenIndex] = lastTokenId; - _slotTokensIndex[slot_][lastTokenId] = tokenIndex; - - delete _slotTokensIndex[slot_][tokenId_]; - slotData.slotTokens.pop(); - } -} diff --git a/assets/eip-3525/contracts/interface/IERC3525.sol b/assets/eip-3525/contracts/interface/IERC3525.sol deleted file mode 100644 index d62af2492dbdeb..00000000000000 --- a/assets/eip-3525/contracts/interface/IERC3525.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "IERC165.sol"; -import "IERC721.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard - * @dev See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xc97ae3d5. - */ -interface IERC3525 is IERC165, IERC721 { - /** - * @dev MUST emit when value of a token is transferred to another token with the same slot, - * including zero value transfers (_value == 0) as well as transfers when tokens are created - * (`_fromTokenId` == 0) or destroyed (`_toTokenId` == 0). - * @param _fromTokenId The token id to transfer value from - * @param _toTokenId The token id to transfer value to - * @param _value The transferred value - */ - event TransferValue( uint256 indexed _fromTokenId, uint256 indexed _toTokenId, uint256 _value); - - /** - * @dev MUST emits when the approval value of a token is set or changed. - * @param _tokenId The token to approve - * @param _operator The operator to approve for - * @param _value The maximum value that `_operator` is allowed to manage - */ - event ApprovalValue( uint256 indexed _tokenId, address indexed _operator, uint256 _value); - - /** - * @dev MUST emit when the slot of a token is set or changed. - * @param _tokenId The token of which slot is set or changed - * @param _oldSlot The previous slot of the token - * @param _newSlot The updated slot of the token - */ - event SlotChanged( uint256 indexed _tokenId, uint256 indexed _oldSlot, uint256 indexed _newSlot); - - /** - * @notice Get the number of decimals the token uses for value - e.g. 6, means the user - * representation of the value of a token can be calculated by dividing it by 1,000,000. - * Considering the compatibility with third-party wallets, this function is defined as - * `valueDecimals()` instead of `decimals()` to avoid conflict with ERC20 tokens. - * @return The number of decimals for value - */ - function valueDecimals() external view returns (uint8); - - /** - * @notice Get the value of a token. - * @param _tokenId The token for which to query the balance - * @return The value of `_tokenId` - */ - function balanceOf(uint256 _tokenId) external view returns (uint256); - - /** - * @notice Get the slot of a token. - * @param _tokenId The identifier for a token - * @return The slot of the token - */ - function slotOf(uint256 _tokenId) external view returns (uint256); - - /** - * @notice Allow an operator to manage the value of a token, up to the `_value` amount. - * @dev MUST revert unless caller is the current owner, an authorized operator, or the approved - * address for `_tokenId`. - * MUST emit ApprovalValue event. - * @param _tokenId The token to approve - * @param _operator The operator to be approved - * @param _value The maximum value of `_toTokenId` that `_operator` is allowed to manage - */ - function approve( uint256 _tokenId, address _operator, uint256 _value) external payable; - - /** - * @notice Get the maximum value of a token that an operator is allowed to manage. - * @param _tokenId The token for which to query the allowance - * @param _operator The address of an operator - * @return The current approval value of `_tokenId` that `_operator` is allowed to manage - */ - function allowance(uint256 _tokenId, address _operator) external view returns (uint256); - - /** - * @notice Transfer value from a specified token to another specified token with the same slot. - * @dev Caller MUST be the current owner, an authorized operator or an operator who has been - * approved the whole `_fromTokenId` or part of it. - * MUST revert if `_fromTokenId` or `_toTokenId` is zero token id or does not exist. - * MUST revert if slots of `_fromTokenId` and `_toTokenId` do not match. - * MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the - * operator. - * MUST emit `TransferValue` event. - * @param _fromTokenId The token to transfer value from - * @param _toTokenId The token to transfer value to - * @param _value The transferred value - */ - function transferFrom( uint256 _fromTokenId, uint256 _toTokenId, uint256 _value) external payable; - - /** - * @notice Transfer value from a specified token to an address. The caller should confirm that - * `_to` is capable of receiving ERC3525 tokens. - * @dev This function MUST create a new ERC3525 token with the same slot for `_to` to receive - * the transferred value. - * MUST revert if `_fromTokenId` is zero token id or does not exist. - * MUST revert if `_to` is zero address. - * MUST revert if `_value` exceeds the balance of `_fromTokenId` or its allowance to the - * operator. - * MUST emit `Transfer` and `TransferValue` events. - * @param _fromTokenId The token to transfer value from - * @param _to The address to transfer value to - * @param _value The transferred value - * @return ID of the new token created for `_to` which receives the transferred value - */ - function transferFrom( uint256 _fromTokenId, address _to, uint256 _value) external payable returns (uint256); -} diff --git a/assets/eip-3525/contracts/interface/IERC3525Metadata.sol b/assets/eip-3525/contracts/interface/IERC3525Metadata.sol deleted file mode 100644 index 6d90fbaf855cdf..00000000000000 --- a/assets/eip-3525/contracts/interface/IERC3525Metadata.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC3525.sol"; -import "IERC721Metadata.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for metadata - * @dev Interfaces for any contract that wants to support query of the Uniform Resource Identifier - * (URI) for the ERC3525 contract as well as a specified slot. - * Because of the higher reliability of data stored in smart contracts compared to data stored in - * centralized systems, it is recommended that metadata, including `contractURI`, `slotURI` and - * `tokenURI`, be directly returned in JSON format, instead of being returned with a url pointing - * to any resource stored in a centralized system. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xe1600902. - */ -interface IERC3525Metadata is IERC3525, IERC721Metadata { - /** - * @notice Returns the Uniform Resource Identifier (URI) for the current ERC3525 contract. - * @dev This function SHOULD return the URI for this contract in JSON format, starting with - * header `data:application/json;`. - * See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for contract URI. - * @return The JSON formatted URI of the current ERC3525 contract - */ - function contractURI() external view returns (string memory); - - /** - * @notice Returns the Uniform Resource Identifier (URI) for the specified slot. - * @dev This function SHOULD return the URI for `_slot` in JSON format, starting with header - * `data:application/json;`. - * See https://eips.ethereum.org/EIPS/eip-3525 for the JSON schema for slot URI. - * @return The JSON formatted URI of `_slot` - */ - function slotURI(uint256 _slot) external view returns (string memory); -} diff --git a/assets/eip-3525/contracts/interface/IERC3525Receiver.sol b/assets/eip-3525/contracts/interface/IERC3525Receiver.sol deleted file mode 100644 index 86e0d6171071e2..00000000000000 --- a/assets/eip-3525/contracts/interface/IERC3525Receiver.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/** - * @title ERC3525 token receiver interface - * @dev Interface for any contract that wants to support safeTransfers from ERC3525 contracts. - * Note: the ERC-165 identifier for this interface is 0x009ce20b. - */ -interface IERC3525Receiver { - /** - * @notice Handle the receipt of an ERC3525 token value. - * @dev An ERC3525 smart contract MUST call this function on the recipient contract after a - * value transfer (i.e. `safeTransferFrom(uint256,uint256,uint256,bytes)`). - * MUST return 0x009ce20b (i.e. `bytes4(keccak256('onERC3525Received(address,uint256,uint256, - * uint256,bytes)'))`) if the transfer is accepted. - * MUST revert or return any value other than 0x009ce20b if the transfer is rejected. - * @param _operator The address which triggered the transfer - * @param _fromTokenId The token id to transfer value from - * @param _toTokenId The token id to transfer value to - * @param _value The transferred value - * @param _data Additional data with no specified format - * @return `bytes4(keccak256('onERC3525Received(address,uint256,uint256,uint256,bytes)'))` - * unless the transfer is rejected. - */ - function onERC3525Received(address _operator, uint256 _fromTokenId, uint256 _toTokenId, uint256 _value, bytes calldata _data) external returns (bytes4); -} \ No newline at end of file diff --git a/assets/eip-3525/contracts/interface/IERC3525SlotApprovable.sol b/assets/eip-3525/contracts/interface/IERC3525SlotApprovable.sol deleted file mode 100644 index 1bfea7034cbdfc..00000000000000 --- a/assets/eip-3525/contracts/interface/IERC3525SlotApprovable.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC3525.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for approval of slot level - * @dev Interfaces for any contract that wants to support approval of slot level, which allows an - * operator to manage one's tokens with the same slot. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0xb688be58. - */ -interface IERC3525SlotApprovable is IERC3525 { - /** - * @dev MUST emits when an operator is approved or disapproved to manage all of `_owner`'s - * tokens with the same slot. - * @param _owner The address whose tokens are approved - * @param _slot The slot to approve, all of `_owner`'s tokens with this slot are approved - * @param _operator The operator being approved or disapproved - * @param _approved Identify if `_operator` is approved or disapproved - */ - event ApprovalForSlot( address indexed _owner, uint256 indexed _slot, address indexed _operator, bool _approved); - - /** - * @notice Approve or disapprove an operator to manage all of `_owner`'s tokens with the - * specified slot. - * @dev Caller SHOULD be `_owner` or an operator who has been authorized through - * `setApprovalForAll`. - * MUST emit ApprovalSlot event. - * @param _owner The address that owns the ERC3525 tokens - * @param _slot The slot of tokens being queried approval of - * @param _operator The address for whom to query approval - * @param _approved Identify if `_operator` would be approved or disapproved - */ - function setApprovalForSlot( address _owner, uint256 _slot, address _operator, bool _approved) external payable; - - /** - * @notice Query if `_operator` is authorized to manage all of `_owner`'s tokens with the - * specified slot. - * @param _owner The address that owns the ERC3525 tokens - * @param _slot The slot of tokens being queried approval of - * @param _operator The address for whom to query approval - * @return True if `_operator` is authorized to manage all of `_owner`'s tokens with `_slot`, - * false otherwise. - */ - function isApprovedForSlot( address _owner, uint256 _slot, address _operator) external view returns (bool); -} diff --git a/assets/eip-3525/contracts/interface/IERC3525SlotEnumerable.sol b/assets/eip-3525/contracts/interface/IERC3525SlotEnumerable.sol deleted file mode 100644 index f10086f1b8be8d..00000000000000 --- a/assets/eip-3525/contracts/interface/IERC3525SlotEnumerable.sol +++ /dev/null @@ -1,42 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC3525.sol"; -import "IERC721Enumerable.sol"; - -/** - * @title ERC-3525 Semi-Fungible Token Standard, optional extension for slot enumeration - * @dev Interfaces for any contract that wants to support enumeration of slots as well as tokens - * with the same slot. - * See https://eips.ethereum.org/EIPS/eip-3525 - * Note: the ERC-165 identifier for this interface is 0x3b741b9e. - */ -interface IERC3525SlotEnumerable is IERC3525, IERC721Enumerable { - /** - * @notice Get the total amount of slots stored by the contract. - * @return The total amount of slots - */ - function slotCount() external view returns (uint256); - - /** - * @notice Get the slot at the specified index of all slots stored by the contract. - * @param _index The index in the slot list - * @return The slot at `index` of all slots. - */ - function slotByIndex(uint256 _index) external view returns (uint256); - - /** - * @notice Get the total amount of tokens with the same slot. - * @param _slot The slot to query token supply for - * @return The total amount of tokens with the specified `_slot` - */ - function tokenSupplyInSlot(uint256 _slot) external view returns (uint256); - - /** - * @notice Get the token at the specified index of all tokens with the same slot. - * @param _slot The slot to query tokens with - * @param _index The index in the token list of the slot - * @return The token ID at `_index` of all tokens with `_slot` - */ - function tokenInSlotByIndex(uint256 _slot, uint256 _index) external view returns (uint256); -} diff --git a/assets/eip-3525/test/test.ts b/assets/eip-3525/test/test.ts deleted file mode 100644 index 4ca5f3aa37bab5..00000000000000 --- a/assets/eip-3525/test/test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { BigNumber } from "ethers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { ERC3525Example } from "../typechain"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; - -const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; -const ZERO_TOKEN_ID = 0; - -let owner: SignerWithAddress, - approval: SignerWithAddress, - to: SignerWithAddress; -let token: ERC3525Example; -let snapshotId: any; -const fromTokenId = 35251; -const toTokenId = 35252; -const fromValue = 10000000000; -const approveValue = 5000000000; -const transferValue = 3000000000; -const slotDetails = { - name: "Test Slot", - description: "Test Slot Description", - image: "https://example.com/slot/test_slot.png", - underlying: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", - vestingType: 1, - maturity: 1658989800, - term: 2592000, - value: fromValue, -}; - -describe("ERC3525", function () { - before(async () => { - const signers = await ethers.getSigners(); - owner = signers[0]; - approval = signers[1]; - to = signers[2]; - const ERC3525Factory = await ethers.getContractFactory("ERC3525Example"); - token = (await ERC3525Factory.deploy( - "TST", - "Test 3525", - 18 - )) as ERC3525Example; - await token.deployed(); - - await token.mint( - slotDetails.name, - slotDetails.description, - slotDetails.image, - fromTokenId, - slotDetails.underlying, - slotDetails.vestingType, - slotDetails.maturity, - slotDetails.term, - slotDetails.value - ); - - await token.mint( - slotDetails.name, - slotDetails.description, - slotDetails.image, - toTokenId, - slotDetails.underlying, - slotDetails.vestingType, - slotDetails.maturity, - slotDetails.term, - 0 - ); - }); - - beforeEach(async function () { - snapshotId = await ethers.provider.send("evm_snapshot", []); - }); - - afterEach(async function () { - await ethers.provider.send("evm_revert", [snapshotId]); - }); - - describe("ERC3525 Example", function () { - it("approve value should be success", async () => { - await token["approve(uint256,address,uint256)"]( - fromTokenId, - approval.address, - approveValue - ); - - expect(await token.allowance(fromTokenId, approval.address)).to.eq( - approveValue - ); - }); - - it("transfer value to id should be success", async () => { - expect( - await token["transferFrom(uint256,uint256,uint256)"]( - fromTokenId, - toTokenId, - transferValue - ) - ); - expect(await token["balanceOf(uint256)"](fromTokenId)).to.eq( - fromValue - transferValue - ); - expect(await token["balanceOf(uint256)"](toTokenId)).to.eq(transferValue); - }); - - it("transfer value to address should be success", async () => { - expect( - await token["transferFrom(uint256,address,uint256)"]( - fromTokenId, - to.address, - transferValue - ) - ); - expect(await token["balanceOf(uint256)"](fromTokenId)).to.eq( - fromValue - transferValue - ); - const newTokenId = 1000000000 + fromTokenId; - expect(await token["balanceOf(uint256)"](newTokenId)).to.eq( - transferValue - ); - }); - - it("approved value should be correct after transfer value to id", async () => { - await token["approve(uint256,address,uint256)"]( - fromTokenId, - approval.address, - approveValue - ); - const approvalToken = token.connect(approval); - await approvalToken["transferFrom(uint256,uint256,uint256)"]( - fromTokenId, - toTokenId, - transferValue - ); - expect(await token.allowance(fromTokenId, approval.address)).to.eq( - approveValue - transferValue - ); - }); - }); -}); diff --git a/assets/eip-3643/ONCHAINID/IClaimIssuer.sol b/assets/eip-3643/ONCHAINID/IClaimIssuer.sol deleted file mode 100644 index bcdb4b0bd76d30..00000000000000 --- a/assets/eip-3643/ONCHAINID/IClaimIssuer.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -import "./IIdentity.sol"; - -interface IClaimIssuer is IIdentity { - - /** - * @dev Emitted when a claim is revoked. - * - * Specification: MUST be triggered when revoking a claim. - */ - event ClaimRevoked(bytes indexed signature); - - /** - * @dev Revoke a claim previously issued, the claim is no longer considered as valid after revocation. - * @notice will fetch the claim from the identity contract (unsafe). - * @param _claimId the id of the claim - * @param _identity the address of the identity contract - * @return isRevoked true when the claim is revoked - */ - function revokeClaim(bytes32 _claimId, address _identity) external returns(bool); - - /** - * @dev Revoke a claim previously issued, the claim is no longer considered as valid after revocation. - * @param signature the signature of the claim - */ - function revokeClaimBySignature(bytes calldata signature) external; - - /** - * @dev Returns revocation status of a claim. - * @param _sig the signature of the claim - * @return isRevoked true if the claim is revoked and false otherwise - */ - function isClaimRevoked(bytes calldata _sig) external view returns (bool); - - /** - * @dev Checks if a claim is valid. - * @param _identity the identity contract related to the claim - * @param claimTopic the claim topic of the claim - * @param sig the signature of the claim - * @param data the data field of the claim - * @return claimValid true if the claim is valid, false otherwise - */ - function isClaimValid( - IIdentity _identity, - uint256 claimTopic, - bytes calldata sig, - bytes calldata data) - external view returns (bool); - - /** - * @dev returns the address that signed the given data - * @param sig the signature of the data - * @param dataHash the data that was signed - * returns the address that signed dataHash and created the signature sig - */ - function getRecoveredAddress(bytes calldata sig, bytes32 dataHash) external pure returns (address); -} diff --git a/assets/eip-3643/ONCHAINID/IERC734.sol b/assets/eip-3643/ONCHAINID/IERC734.sol deleted file mode 100644 index 99aa31f9fb9291..00000000000000 --- a/assets/eip-3643/ONCHAINID/IERC734.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -/** - * @dev interface of the ERC734 (Key Holder) standard as defined in the EIP. - */ -interface IERC734 { - - /** - * @dev Emitted when an execution request was approved. - * - * Specification: MUST be triggered when approve was successfully called. - */ - event Approved(uint256 indexed executionId, bool approved); - - /** - * @dev Emitted when an execute operation was approved and successfully performed. - * - * Specification: MUST be triggered when approve was called and the execution was successfully approved. - */ - event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); - - /** - * @dev Emitted when an execution request was performed via `execute`. - * - * Specification: MUST be triggered when execute was successfully called. - */ - event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); - - /** - * @dev Emitted when an execute operation was called and failed - * - * Specification: MUST be triggered when execute call failed - */ - event ExecutionFailed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); - - /** - * @dev Emitted when a key was added to the Identity. - * - * Specification: MUST be triggered when addKey was successfully called. - */ - event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); - - /** - * @dev Emitted when a key was removed from the Identity. - * - * Specification: MUST be triggered when removeKey was successfully called. - */ - event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); - - /** - * @dev Adds a _key to the identity. The _purpose specifies the purpose of the key. - * - * Triggers Event: `KeyAdded` - * - * Specification: MUST only be done by keys of purpose 1, or the identity - * itself. If it's the identity itself, the approval process will determine its approval. - */ - function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) external returns (bool success); - - /** - * @dev Approves an execution. - * - * Triggers Event: `Approved` - * Triggers on execution successful Event: `Executed` - * Triggers on execution failure Event: `ExecutionFailed` - */ - function approve(uint256 _id, bool _approve) external returns (bool success); - - /** - * @dev Removes _purpose for _key from the identity. - * - * Triggers Event: `KeyRemoved` - * - * Specification: MUST only be done by keys of purpose 1, or the identity itself. - * If it's the identity itself, the approval process will determine its approval. - */ - function removeKey(bytes32 _key, uint256 _purpose) external returns (bool success); - - /** - * @dev Passes an execution instruction to an ERC734 identity. - * How the execution is handled is up to the identity implementation: - * An execution COULD be requested and require `approve` to be called with one or more keys of purpose 1 or 2 to - * approve this execution. - * Execute COULD be used as the only accessor for `addKey` and `removeKey`. - * - * Triggers Event: ExecutionRequested - * Triggers on direct execution Event: Executed - */ - function execute(address _to, uint256 _value, bytes calldata _data) external payable returns (uint256 executionId); - - /** - * @dev Returns the full key data, if present in the identity. - */ - function getKey(bytes32 _key) external view returns (uint256[] memory purposes, uint256 keyType, bytes32 key); - - /** - * @dev Returns the list of purposes associated with a key. - */ - function getKeyPurposes(bytes32 _key) external view returns(uint256[] memory _purposes); - - /** - * @dev Returns an array of public key bytes32 held by this identity. - */ - function getKeysByPurpose(uint256 _purpose) external view returns (bytes32[] memory keys); - - /** - * @dev Returns TRUE if a key is present and has the given purpose. If the key is not present it returns FALSE. - */ - function keyHasPurpose(bytes32 _key, uint256 _purpose) external view returns (bool exists); -} diff --git a/assets/eip-3643/ONCHAINID/IERC735.sol b/assets/eip-3643/ONCHAINID/IERC735.sol deleted file mode 100644 index 58d02219fdc23d..00000000000000 --- a/assets/eip-3643/ONCHAINID/IERC735.sol +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -/** - * @dev interface of the ERC735 (Claim Holder) standard as defined in the EIP. - */ -interface IERC735 { - - /** - * @dev Emitted when a claim was added. - * - * Specification: MUST be triggered when a claim was successfully added. - */ - event ClaimAdded( - bytes32 indexed claimId, - uint256 indexed topic, - uint256 scheme, - address indexed issuer, - bytes signature, - bytes data, - string uri); - - /** - * @dev Emitted when a claim was removed. - * - * Specification: MUST be triggered when removeClaim was successfully called. - */ - event ClaimRemoved( - bytes32 indexed claimId, - uint256 indexed topic, - uint256 scheme, - address indexed issuer, - bytes signature, - bytes data, - string uri); - - /** - * @dev Emitted when a claim was changed. - * - * Specification: MUST be triggered when addClaim was successfully called on an existing claimId. - */ - event ClaimChanged( - bytes32 indexed claimId, - uint256 indexed topic, - uint256 scheme, - address indexed issuer, - bytes signature, - bytes data, - string uri); - - /** - * @dev Add or update a claim. - * - * Triggers Event: `ClaimAdded`, `ClaimChanged` - * - * Specification: Add or update a claim from an issuer. - * - * _signature is a signed message of the following structure: - * `keccak256(abi.encode(address identityHolder_address, uint256 topic, bytes data))`. - * Claim IDs are generated using `keccak256(abi.encode(address issuer_address + uint256 topic))`. - */ - function addClaim( - uint256 _topic, - uint256 _scheme, - address issuer, - bytes calldata _signature, - bytes calldata _data, - string calldata _uri) - external returns (bytes32 claimRequestId); - - /** - * @dev Removes a claim. - * - * Triggers Event: `ClaimRemoved` - * - * Claim IDs are generated using `keccak256(abi.encode(address issuer_address, uint256 topic))`. - */ - function removeClaim(bytes32 _claimId) external returns (bool success); - - /** - * @dev Get a claim by its ID. - * - * Claim IDs are generated using `keccak256(abi.encode(address issuer_address, uint256 topic))`. - */ - function getClaim(bytes32 _claimId) - external view returns( - uint256 topic, - uint256 scheme, - address issuer, - bytes memory signature, - bytes memory data, - string memory uri); - - /** - * @dev Returns an array of claim IDs by topic. - */ - function getClaimIdsByTopic(uint256 _topic) external view returns(bytes32[] memory claimIds); -} diff --git a/assets/eip-3643/ONCHAINID/IIdentity.sol b/assets/eip-3643/ONCHAINID/IIdentity.sol deleted file mode 100644 index dfc63a4da50309..00000000000000 --- a/assets/eip-3643/ONCHAINID/IIdentity.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -import "./IERC734.sol"; -import "./IERC735.sol"; - -// solhint-disable-next-line no-empty-blocks -interface IIdentity is IERC734, IERC735 {} diff --git a/assets/eip-3643/interfaces/IClaimTopicsRegistry.sol b/assets/eip-3643/interfaces/IClaimTopicsRegistry.sol deleted file mode 100644 index ea483f5f47c5c6..00000000000000 --- a/assets/eip-3643/interfaces/IClaimTopicsRegistry.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -interface IClaimTopicsRegistry { - /** - * this event is emitted when a claim topic has been added to the ClaimTopicsRegistry - * the event is emitted by the 'addClaimTopic' function - * `claimTopic` is the required claim added to the Claim Topics Registry - */ - event ClaimTopicAdded(uint256 indexed claimTopic); - - /** - * this event is emitted when a claim topic has been removed from the ClaimTopicsRegistry - * the event is emitted by the 'removeClaimTopic' function - * `claimTopic` is the required claim removed from the Claim Topics Registry - */ - event ClaimTopicRemoved(uint256 indexed claimTopic); - - /** - * @dev Add a trusted claim topic (For example: KYC=1, AML=2). - * Only owner can call. - * emits `ClaimTopicAdded` event - * cannot add more than 15 topics for 1 token as adding more could create gas issues - * @param _claimTopic The claim topic index - */ - function addClaimTopic(uint256 _claimTopic) external; - - /** - * @dev Remove a trusted claim topic (For example: KYC=1, AML=2). - * Only owner can call. - * emits `ClaimTopicRemoved` event - * @param _claimTopic The claim topic index - */ - function removeClaimTopic(uint256 _claimTopic) external; - - /** - * @dev Get the trusted claim topics for the security token - * @return Array of trusted claim topics - */ - function getClaimTopics() external view returns (uint256[] memory); -} diff --git a/assets/eip-3643/interfaces/ICompliance.sol b/assets/eip-3643/interfaces/ICompliance.sol deleted file mode 100644 index d57bcd87188546..00000000000000 --- a/assets/eip-3643/interfaces/ICompliance.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -interface ICompliance { - - /// events - - /** - * this event is emitted when a token has been bound to the compliance contract - * the event is emitted by the bindToken function - * `_token` is the address of the token to bind - */ - event TokenBound(address _token); - - /** - * this event is emitted when a token has been unbound from the compliance contract - * the event is emitted by the unbindToken function - * `_token` is the address of the token to unbind - */ - event TokenUnbound(address _token); - - /// functions - - /** - * @dev binds a token to the compliance contract - * @param _token address of the token to bind - * This function can be called ONLY by the owner of the compliance contract - * Emits a TokenBound event - */ - function bindToken(address _token) external; - - /** - * @dev unbinds a token from the compliance contract - * @param _token address of the token to unbind - * This function can be called ONLY by the owner of the compliance contract - * Emits a TokenUnbound event - */ - function unbindToken(address _token) external; - - - /** - * @dev function called whenever tokens are transferred - * from one wallet to another - * this function can be used to update state variables of the compliance contract - * This function can be called ONLY by the token contract bound to the compliance - * @param _from The address of the sender - * @param _to The address of the receiver - * @param _amount The amount of tokens involved in the transfer - */ - function transferred( - address _from, - address _to, - uint256 _amount - ) external; - - /** - * @dev function called whenever tokens are created on a wallet - * this function can be used to update state variables of the compliance contract - * This function can be called ONLY by the token contract bound to the compliance - * @param _to The address of the receiver - * @param _amount The amount of tokens involved in the minting - */ - function created(address _to, uint256 _amount) external; - - /** - * @dev function called whenever tokens are destroyed from a wallet - * this function can be used to update state variables of the compliance contract - * This function can be called ONLY by the token contract bound to the compliance - * @param _from The address on which tokens are burnt - * @param _amount The amount of tokens involved in the burn - */ - function destroyed(address _from, uint256 _amount) external; - - /** - * @dev checks that the transfer is compliant. - * default compliance always returns true - * READ ONLY FUNCTION, this function cannot be used to increment - * counters, emit events, ... - * @param _from The address of the sender - * @param _to The address of the receiver - * @param _amount The amount of tokens involved in the transfer - * This function will call all checks implemented on compliance - * If all checks return TRUE, the function returns TRUE - * returns FALSE otherwise - */ - function canTransfer( - address _from, - address _to, - uint256 _amount - ) external view returns (bool); - - /** - * @dev getter for the address of the token bound - * returns the address of the token - */ - function getTokenBound() external view returns (address); -} diff --git a/assets/eip-3643/interfaces/IERC3643.sol b/assets/eip-3643/interfaces/IERC3643.sol deleted file mode 100644 index a8ac52682b04a7..00000000000000 --- a/assets/eip-3643/interfaces/IERC3643.sol +++ /dev/null @@ -1,374 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -import "../registry/interface/IIdentityRegistry.sol"; -import "../compliance/modular/IModularCompliance.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -/// @dev interface -interface IERC3643 is IERC20 { - - /// events - - /** - * this event is emitted when the token information is updated. - * the event is emitted by the token init function and by the setTokenInformation function - * `_newName` is the name of the token - * `_newSymbol` is the symbol of the token - * `_newDecimals` is the decimals of the token - * `_newVersion` is the version of the token, current version is 3.0 - * `_newOnchainID` is the address of the onchainID of the token - */ - event UpdatedTokenInformation(string indexed _newName, string indexed _newSymbol, uint8 _newDecimals, string - _newVersion, address indexed _newOnchainID); - - /** - * this event is emitted when the IdentityRegistry has been set for the token - * the event is emitted by the token constructor and by the setIdentityRegistry function - * `_identityRegistry` is the address of the Identity Registry of the token - */ - event IdentityRegistryAdded(address indexed _identityRegistry); - - /** - * this event is emitted when the Compliance has been set for the token - * the event is emitted by the token constructor and by the setCompliance function - * `_compliance` is the address of the Compliance contract of the token - */ - event ComplianceAdded(address indexed _compliance); - - /** - * this event is emitted when an investor successfully recovers his tokens - * the event is emitted by the recoveryAddress function - * `_lostWallet` is the address of the wallet that the investor lost access to - * `_newWallet` is the address of the wallet that the investor provided for the recovery - * `_investorOnchainID` is the address of the onchainID of the investor who asked for a recovery - */ - event RecoverySuccess(address indexed _lostWallet, address indexed _newWallet, address indexed _investorOnchainID); - - /** - * this event is emitted when the wallet of an investor is frozen or unfrozen - * the event is emitted by setAddressFrozen and batchSetAddressFrozen functions - * `_userAddress` is the wallet of the investor that is concerned by the freezing status - * `_isFrozen` is the freezing status of the wallet - * if `_isFrozen` equals `true` the wallet is frozen after emission of the event - * if `_isFrozen` equals `false` the wallet is unfrozen after emission of the event - * `_owner` is the address of the agent who called the function to freeze the wallet - */ - event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner); - - /** - * this event is emitted when a certain amount of tokens is frozen on a wallet - * the event is emitted by freezePartialTokens and batchFreezePartialTokens functions - * `_userAddress` is the wallet of the investor that is concerned by the freezing status - * `_amount` is the amount of tokens that are frozen - */ - event TokensFrozen(address indexed _userAddress, uint256 _amount); - - /** - * this event is emitted when a certain amount of tokens is unfrozen on a wallet - * the event is emitted by unfreezePartialTokens and batchUnfreezePartialTokens functions - * `_userAddress` is the wallet of the investor that is concerned by the freezing status - * `_amount` is the amount of tokens that are unfrozen - */ - event TokensUnfrozen(address indexed _userAddress, uint256 _amount); - - /** - * this event is emitted when the token is paused - * the event is emitted by the pause function - * `_userAddress` is the address of the wallet that called the pause function - */ - event Paused(address _userAddress); - - /** - * this event is emitted when the token is unpaused - * the event is emitted by the unpause function - * `_userAddress` is the address of the wallet that called the unpause function - */ - event Unpaused(address _userAddress); - - /// functions - - /** - * @dev sets the token name - * @param _name the name of token to set - * Only the owner of the token smart contract can call this function - * emits a `UpdatedTokenInformation` event - */ - function setName(string calldata _name) external; - - /** - * @dev sets the token symbol - * @param _symbol the token symbol to set - * Only the owner of the token smart contract can call this function - * emits a `UpdatedTokenInformation` event - */ - function setSymbol(string calldata _symbol) external; - - /** - * @dev sets the onchain ID of the token - * @param _onchainID the address of the onchain ID to set - * Only the owner of the token smart contract can call this function - * emits a `UpdatedTokenInformation` event - */ - function setOnchainID(address _onchainID) external; - - /** - * @dev pauses the token contract, when contract is paused investors cannot transfer tokens anymore - * This function can only be called by a wallet set as agent of the token - * emits a `Paused` event - */ - function pause() external; - - /** - * @dev unpauses the token contract, when contract is unpaused investors can transfer tokens - * if their wallet is not blocked & if the amount to transfer is <= to the amount of free tokens - * This function can only be called by a wallet set as agent of the token - * emits an `Unpaused` event - */ - function unpause() external; - - /** - * @dev sets an address frozen status for this token. - * @param _userAddress The address for which to update frozen status - * @param _freeze Frozen status of the address - * This function can only be called by a wallet set as agent of the token - * emits an `AddressFrozen` event - */ - function setAddressFrozen(address _userAddress, bool _freeze) external; - - /** - * @dev freezes token amount specified for given address. - * @param _userAddress The address for which to update frozen tokens - * @param _amount Amount of Tokens to be frozen - * This function can only be called by a wallet set as agent of the token - * emits a `TokensFrozen` event - */ - function freezePartialTokens(address _userAddress, uint256 _amount) external; - - /** - * @dev unfreezes token amount specified for given address - * @param _userAddress The address for which to update frozen tokens - * @param _amount Amount of Tokens to be unfrozen - * This function can only be called by a wallet set as agent of the token - * emits a `TokensUnfrozen` event - */ - function unfreezePartialTokens(address _userAddress, uint256 _amount) external; - - /** - * @dev sets the Identity Registry for the token - * @param _identityRegistry the address of the Identity Registry to set - * Only the owner of the token smart contract can call this function - * emits an `IdentityRegistryAdded` event - */ - function setIdentityRegistry(address _identityRegistry) external; - - /** - * @dev sets the compliance contract of the token - * @param _compliance the address of the compliance contract to set - * Only the owner of the token smart contract can call this function - * calls bindToken on the compliance contract - * emits a `ComplianceAdded` event - */ - function setCompliance(address _compliance) external; - - /** - * @dev force a transfer of tokens between 2 whitelisted wallets - * In case the `from` address has not enough free tokens (unfrozen tokens) - * but has a total balance higher or equal to the `amount` - * the amount of frozen tokens is reduced in order to have enough free tokens - * to proceed the transfer, in such a case, the remaining balance on the `from` - * account is 100% composed of frozen tokens post-transfer. - * Require that the `to` address is a verified address, - * @param _from The address of the sender - * @param _to The address of the receiver - * @param _amount The number of tokens to transfer - * @return `true` if successful and revert if unsuccessful - * This function can only be called by a wallet set as agent of the token - * emits a `TokensUnfrozen` event if `_amount` is higher than the free balance of `_from` - * emits a `Transfer` event - */ - function forcedTransfer( - address _from, - address _to, - uint256 _amount - ) external returns (bool); - - /** - * @dev mint tokens on a wallet - * Improved version of default mint method. Tokens can be minted - * to an address if only it is a verified address as per the security token. - * @param _to Address to mint the tokens to. - * @param _amount Amount of tokens to mint. - * This function can only be called by a wallet set as agent of the token - * emits a `Transfer` event - */ - function mint(address _to, uint256 _amount) external; - - /** - * @dev burn tokens on a wallet - * In case the `account` address has not enough free tokens (unfrozen tokens) - * but has a total balance higher or equal to the `value` amount - * the amount of frozen tokens is reduced in order to have enough free tokens - * to proceed the burn, in such a case, the remaining balance on the `account` - * is 100% composed of frozen tokens post-transaction. - * @param _userAddress Address to burn the tokens from. - * @param _amount Amount of tokens to burn. - * This function can only be called by a wallet set as agent of the token - * emits a `TokensUnfrozen` event if `_amount` is higher than the free balance of `_userAddress` - * emits a `Transfer` event - */ - function burn(address _userAddress, uint256 _amount) external; - - /** - * @dev recovery function used to force transfer tokens from a - * lost wallet to a new wallet for an investor. - * @param _lostWallet the wallet that the investor lost - * @param _newWallet the newly provided wallet on which tokens have to be transferred - * @param _investorOnchainID the onchainID of the investor asking for a recovery - * This function can only be called by a wallet set as agent of the token - * emits a `TokensUnfrozen` event if there is some frozen tokens on the lost wallet if the recovery process is successful - * emits a `Transfer` event if the recovery process is successful - * emits a `RecoverySuccess` event if the recovery process is successful - * emits a `RecoveryFails` event if the recovery process fails - */ - function recoveryAddress( - address _lostWallet, - address _newWallet, - address _investorOnchainID - ) external returns (bool); - - /** - * @dev function allowing to issue transfers in batch - * Require that the msg.sender and `to` addresses are not frozen. - * Require that the total value should not exceed available balance. - * Require that the `to` addresses are all verified addresses, - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_toList.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _toList The addresses of the receivers - * @param _amounts The number of tokens to transfer to the corresponding receiver - * emits _toList.length `Transfer` events - */ - function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external; - - /** - * @dev function allowing to issue forced transfers in batch - * Require that `_amounts[i]` should not exceed available balance of `_fromList[i]`. - * Require that the `_toList` addresses are all verified addresses - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_fromList.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _fromList The addresses of the senders - * @param _toList The addresses of the receivers - * @param _amounts The number of tokens to transfer to the corresponding receiver - * This function can only be called by a wallet set as agent of the token - * emits `TokensUnfrozen` events if `_amounts[i]` is higher than the free balance of `_fromList[i]` - * emits _fromList.length `Transfer` events - */ - function batchForcedTransfer( - address[] calldata _fromList, - address[] calldata _toList, - uint256[] calldata _amounts - ) external; - - /** - * @dev function allowing to mint tokens in batch - * Require that the `_toList` addresses are all verified addresses - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_toList.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _toList The addresses of the receivers - * @param _amounts The number of tokens to mint to the corresponding receiver - * This function can only be called by a wallet set as agent of the token - * emits _toList.length `Transfer` events - */ - function batchMint(address[] calldata _toList, uint256[] calldata _amounts) external; - - /** - * @dev function allowing to burn tokens in batch - * Require that the `_userAddresses` addresses are all verified addresses - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _userAddresses The addresses of the wallets concerned by the burn - * @param _amounts The number of tokens to burn from the corresponding wallets - * This function can only be called by a wallet set as agent of the token - * emits _userAddresses.length `Transfer` events - */ - function batchBurn(address[] calldata _userAddresses, uint256[] calldata _amounts) external; - - /** - * @dev function allowing to set frozen addresses in batch - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _userAddresses The addresses for which to update frozen status - * @param _freeze Frozen status of the corresponding address - * This function can only be called by a wallet set as agent of the token - * emits _userAddresses.length `AddressFrozen` events - */ - function batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external; - - /** - * @dev function allowing to freeze tokens partially in batch - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _userAddresses The addresses on which tokens need to be frozen - * @param _amounts the amount of tokens to freeze on the corresponding address - * This function can only be called by a wallet set as agent of the token - * emits _userAddresses.length `TokensFrozen` events - */ - function batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; - - /** - * @dev function allowing to unfreeze tokens partially in batch - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _userAddresses The addresses on which tokens need to be unfrozen - * @param _amounts the amount of tokens to unfreeze on the corresponding address - * This function can only be called by a wallet set as agent of the token - * emits _userAddresses.length `TokensUnfrozen` events - */ - function batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; - - /** - * @dev Returns the address of the onchainID of the token. - * the onchainID of the token gives all the information available - * about the token and is managed by the token issuer or his agent. - */ - function onchainID() external view returns (address); - - /** - * @dev Returns the TREX version of the token. - * current version is 3.0.0 - */ - function version() external view returns (string memory); - - /** - * @dev Returns the Identity Registry linked to the token - */ - function identityRegistry() external view returns (IIdentityRegistry); - - /** - * @dev Returns the Compliance contract linked to the token - */ - function compliance() external view returns (IModularCompliance); - - /** - * @dev Returns true if the contract is paused, and false otherwise. - */ - function paused() external view returns (bool); - - /** - * @dev Returns the freezing status of a wallet - * if isFrozen returns `true` the wallet is frozen - * if isFrozen returns `false` the wallet is not frozen - * isFrozen returning `true` doesn't mean that the balance is free, tokens could be blocked by - * a partial freeze or the whole token could be blocked by pause - * @param _userAddress the address of the wallet on which isFrozen is called - */ - function isFrozen(address _userAddress) external view returns (bool); - - /** - * @dev Returns the amount of tokens that are partially frozen on a wallet - * the amount of frozen tokens is always <= to the total balance of the wallet - * @param _userAddress the address of the wallet on which getFrozenTokens is called - */ - function getFrozenTokens(address _userAddress) external view returns (uint256); -} diff --git a/assets/eip-3643/interfaces/IIdentityRegistry.sol b/assets/eip-3643/interfaces/IIdentityRegistry.sol deleted file mode 100644 index 4bc42055847597..00000000000000 --- a/assets/eip-3643/interfaces/IIdentityRegistry.sol +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -import "./ITrustedIssuersRegistry.sol"; -import "./IClaimTopicsRegistry.sol"; -import "./IIdentityRegistryStorage.sol"; - -import "@onchain-id/solidity/contracts/interface/IClaimIssuer.sol"; -import "@onchain-id/solidity/contracts/interface/IIdentity.sol"; - -interface IIdentityRegistry { - /** - * this event is emitted when the ClaimTopicsRegistry has been set for the IdentityRegistry - * the event is emitted by the IdentityRegistry constructor - * `claimTopicsRegistry` is the address of the Claim Topics Registry contract - */ - event ClaimTopicsRegistrySet(address indexed claimTopicsRegistry); - - /** - * this event is emitted when the IdentityRegistryStorage has been set for the IdentityRegistry - * the event is emitted by the IdentityRegistry constructor - * `identityStorage` is the address of the Identity Registry Storage contract - */ - event IdentityStorageSet(address indexed identityStorage); - - /** - * this event is emitted when the TrustedIssuersRegistry has been set for the IdentityRegistry - * the event is emitted by the IdentityRegistry constructor - * `trustedIssuersRegistry` is the address of the Trusted Issuers Registry contract - */ - event TrustedIssuersRegistrySet(address indexed trustedIssuersRegistry); - - /** - * this event is emitted when an Identity is registered into the Identity Registry. - * the event is emitted by the 'registerIdentity' function - * `investorAddress` is the address of the investor's wallet - * `identity` is the address of the Identity smart contract (onchainID) - */ - event IdentityRegistered(address indexed investorAddress, IIdentity indexed identity); - - /** - * this event is emitted when an Identity is removed from the Identity Registry. - * the event is emitted by the 'deleteIdentity' function - * `investorAddress` is the address of the investor's wallet - * `identity` is the address of the Identity smart contract (onchainID) - */ - event IdentityRemoved(address indexed investorAddress, IIdentity indexed identity); - - /** - * this event is emitted when an Identity has been updated - * the event is emitted by the 'updateIdentity' function - * `oldIdentity` is the old Identity contract's address to update - * `newIdentity` is the new Identity contract's - */ - event IdentityUpdated(IIdentity indexed oldIdentity, IIdentity indexed newIdentity); - - /** - * this event is emitted when an Identity's country has been updated - * the event is emitted by the 'updateCountry' function - * `investorAddress` is the address on which the country has been updated - * `country` is the numeric code (ISO 3166-1) of the new country - */ - event CountryUpdated(address indexed investorAddress, uint16 indexed country); - - /** - * @dev Register an identity contract corresponding to a user address. - * Requires that the user doesn't have an identity contract already registered. - * This function can only be called by a wallet set as agent of the smart contract - * @param _userAddress The address of the user - * @param _identity The address of the user's identity contract - * @param _country The country of the investor - * emits `IdentityRegistered` event - */ - function registerIdentity( - address _userAddress, - IIdentity _identity, - uint16 _country - ) external; - - /** - * @dev Removes an user from the identity registry. - * Requires that the user have an identity contract already deployed that will be deleted. - * This function can only be called by a wallet set as agent of the smart contract - * @param _userAddress The address of the user to be removed - * emits `IdentityRemoved` event - */ - function deleteIdentity(address _userAddress) external; - - /** - * @dev Replace the actual identityRegistryStorage contract with a new one. - * This function can only be called by the wallet set as owner of the smart contract - * @param _identityRegistryStorage The address of the new Identity Registry Storage - * emits `IdentityStorageSet` event - */ - function setIdentityRegistryStorage(address _identityRegistryStorage) external; - - /** - * @dev Replace the actual claimTopicsRegistry contract with a new one. - * This function can only be called by the wallet set as owner of the smart contract - * @param _claimTopicsRegistry The address of the new claim Topics Registry - * emits `ClaimTopicsRegistrySet` event - */ - function setClaimTopicsRegistry(address _claimTopicsRegistry) external; - - /** - * @dev Replace the actual trustedIssuersRegistry contract with a new one. - * This function can only be called by the wallet set as owner of the smart contract - * @param _trustedIssuersRegistry The address of the new Trusted Issuers Registry - * emits `TrustedIssuersRegistrySet` event - */ - function setTrustedIssuersRegistry(address _trustedIssuersRegistry) external; - - /** - * @dev Updates the country corresponding to a user address. - * Requires that the user should have an identity contract already deployed that will be replaced. - * This function can only be called by a wallet set as agent of the smart contract - * @param _userAddress The address of the user - * @param _country The new country of the user - * emits `CountryUpdated` event - */ - function updateCountry(address _userAddress, uint16 _country) external; - - /** - * @dev Updates an identity contract corresponding to a user address. - * Requires that the user address should be the owner of the identity contract. - * Requires that the user should have an identity contract already deployed that will be replaced. - * This function can only be called by a wallet set as agent of the smart contract - * @param _userAddress The address of the user - * @param _identity The address of the user's new identity contract - * emits `IdentityUpdated` event - */ - function updateIdentity(address _userAddress, IIdentity _identity) external; - - /** - * @dev function allowing to register identities in batch - * This function can only be called by a wallet set as agent of the smart contract - * Requires that none of the users has an identity contract already registered. - * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, - * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION - * @param _userAddresses The addresses of the users - * @param _identities The addresses of the corresponding identity contracts - * @param _countries The countries of the corresponding investors - * emits _userAddresses.length `IdentityRegistered` events - */ - function batchRegisterIdentity( - address[] calldata _userAddresses, - IIdentity[] calldata _identities, - uint16[] calldata _countries - ) external; - - /** - * @dev This functions checks whether a wallet has its Identity registered or not - * in the Identity Registry. - * @param _userAddress The address of the user to be checked. - * @return 'True' if the address is contained in the Identity Registry, 'false' if not. - */ - function contains(address _userAddress) external view returns (bool); - - /** - * @dev This functions checks whether an identity contract - * corresponding to the provided user address has the required claims or not based - * on the data fetched from trusted issuers registry and from the claim topics registry - * @param _userAddress The address of the user to be verified. - * @return 'True' if the address is verified, 'false' if not. - */ - function isVerified(address _userAddress) external view returns (bool); - - /** - * @dev Returns the onchainID of an investor. - * @param _userAddress The wallet of the investor - */ - function identity(address _userAddress) external view returns (IIdentity); - - /** - * @dev Returns the country code of an investor. - * @param _userAddress The wallet of the investor - */ - function investorCountry(address _userAddress) external view returns (uint16); - - /** - * @dev Returns the IdentityRegistryStorage linked to the current IdentityRegistry. - */ - function identityStorage() external view returns (IIdentityRegistryStorage); - - /** - * @dev Returns the TrustedIssuersRegistry linked to the current IdentityRegistry. - */ - function issuersRegistry() external view returns (ITrustedIssuersRegistry); - - /** - * @dev Returns the ClaimTopicsRegistry linked to the current IdentityRegistry. - */ - function topicsRegistry() external view returns (IClaimTopicsRegistry); -} diff --git a/assets/eip-3643/interfaces/IIdentityRegistryStorage.sol b/assets/eip-3643/interfaces/IIdentityRegistryStorage.sol deleted file mode 100644 index ed04b94103c386..00000000000000 --- a/assets/eip-3643/interfaces/IIdentityRegistryStorage.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -import "@onchain-id/solidity/contracts/interface/IIdentity.sol"; - -interface IIdentityRegistryStorage { - - /// events - - /** - * this event is emitted when an Identity is registered into the storage contract. - * the event is emitted by the 'registerIdentity' function - * `investorAddress` is the address of the investor's wallet - * `identity` is the address of the Identity smart contract (onchainID) - */ - event IdentityStored(address indexed investorAddress, IIdentity indexed identity); - - /** - * this event is emitted when an Identity is removed from the storage contract. - * the event is emitted by the 'deleteIdentity' function - * `investorAddress` is the address of the investor's wallet - * `identity` is the address of the Identity smart contract (onchainID) - */ - event IdentityUnstored(address indexed investorAddress, IIdentity indexed identity); - - /** - * this event is emitted when an Identity has been updated - * the event is emitted by the 'updateIdentity' function - * `oldIdentity` is the old Identity contract's address to update - * `newIdentity` is the new Identity contract's - */ - event IdentityModified(IIdentity indexed oldIdentity, IIdentity indexed newIdentity); - - /** - * this event is emitted when an Identity's country has been updated - * the event is emitted by the 'updateCountry' function - * `investorAddress` is the address on which the country has been updated - * `country` is the numeric code (ISO 3166-1) of the new country - */ - event CountryModified(address indexed investorAddress, uint16 indexed country); - - /** - * this event is emitted when an Identity Registry is bound to the storage contract - * the event is emitted by the 'addIdentityRegistry' function - * `identityRegistry` is the address of the identity registry added - */ - event IdentityRegistryBound(address indexed identityRegistry); - - /** - * this event is emitted when an Identity Registry is unbound from the storage contract - * the event is emitted by the 'removeIdentityRegistry' function - * `identityRegistry` is the address of the identity registry removed - */ - event IdentityRegistryUnbound(address indexed identityRegistry); - - /// functions - - /** - * @dev adds an identity contract corresponding to a user address in the storage. - * Requires that the user doesn't have an identity contract already registered. - * This function can only be called by an address set as agent of the smart contract - * @param _userAddress The address of the user - * @param _identity The address of the user's identity contract - * @param _country The country of the investor - * emits `IdentityStored` event - */ - function addIdentityToStorage( - address _userAddress, - IIdentity _identity, - uint16 _country - ) external; - - /** - * @dev Removes an user from the storage. - * Requires that the user have an identity contract already deployed that will be deleted. - * This function can only be called by an address set as agent of the smart contract - * @param _userAddress The address of the user to be removed - * emits `IdentityUnstored` event - */ - function removeIdentityFromStorage(address _userAddress) external; - - /** - * @dev Updates the country corresponding to a user address. - * Requires that the user should have an identity contract already deployed that will be replaced. - * This function can only be called by an address set as agent of the smart contract - * @param _userAddress The address of the user - * @param _country The new country of the user - * emits `CountryModified` event - */ - function modifyStoredInvestorCountry(address _userAddress, uint16 _country) external; - - /** - * @dev Updates an identity contract corresponding to a user address. - * Requires that the user address should be the owner of the identity contract. - * Requires that the user should have an identity contract already deployed that will be replaced. - * This function can only be called by an address set as agent of the smart contract - * @param _userAddress The address of the user - * @param _identity The address of the user's new identity contract - * emits `IdentityModified` event - */ - function modifyStoredIdentity(address _userAddress, IIdentity _identity) external; - - /** - * @notice Adds an identity registry as agent of the Identity Registry Storage Contract. - * This function can only be called by the wallet set as owner of the smart contract - * This function adds the identity registry to the list of identityRegistries linked to the storage contract - * cannot bind more than 300 IR to 1 IRS - * @param _identityRegistry The identity registry address to add. - */ - function bindIdentityRegistry(address _identityRegistry) external; - - /** - * @notice Removes an identity registry from being agent of the Identity Registry Storage Contract. - * This function can only be called by the wallet set as owner of the smart contract - * This function removes the identity registry from the list of identityRegistries linked to the storage contract - * @param _identityRegistry The identity registry address to remove. - */ - function unbindIdentityRegistry(address _identityRegistry) external; - - /** - * @dev Returns the identity registries linked to the storage contract - */ - function linkedIdentityRegistries() external view returns (address[] memory); - - /** - * @dev Returns the onchainID of an investor. - * @param _userAddress The wallet of the investor - */ - function storedIdentity(address _userAddress) external view returns (IIdentity); - - /** - * @dev Returns the country code of an investor. - * @param _userAddress The wallet of the investor - */ - function storedInvestorCountry(address _userAddress) external view returns (uint16); -} diff --git a/assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol b/assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol deleted file mode 100644 index 786b6a203535a9..00000000000000 --- a/assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.17; - -import "@onchain-id/solidity/contracts/interface/IClaimIssuer.sol"; - -interface ITrustedIssuersRegistry { - /** - * this event is emitted when a trusted issuer is added in the registry. - * the event is emitted by the addTrustedIssuer function - * `trustedIssuer` is the address of the trusted issuer's ClaimIssuer contract - * `claimTopics` is the set of claims that the trusted issuer is allowed to emit - */ - event TrustedIssuerAdded(IClaimIssuer indexed trustedIssuer, uint256[] claimTopics); - - /** - * this event is emitted when a trusted issuer is removed from the registry. - * the event is emitted by the removeTrustedIssuer function - * `trustedIssuer` is the address of the trusted issuer's ClaimIssuer contract - */ - event TrustedIssuerRemoved(IClaimIssuer indexed trustedIssuer); - - /** - * this event is emitted when the set of claim topics is changed for a given trusted issuer. - * the event is emitted by the updateIssuerClaimTopics function - * `trustedIssuer` is the address of the trusted issuer's ClaimIssuer contract - * `claimTopics` is the set of claims that the trusted issuer is allowed to emit - */ - event ClaimTopicsUpdated(IClaimIssuer indexed trustedIssuer, uint256[] claimTopics); - - /** - * @dev registers a ClaimIssuer contract as trusted claim issuer. - * Requires that a ClaimIssuer contract doesn't already exist - * Requires that the claimTopics set is not empty - * Requires that there is no more than 15 claimTopics - * Requires that there is no more than 50 Trusted issuers - * @param _trustedIssuer The ClaimIssuer contract address of the trusted claim issuer. - * @param _claimTopics the set of claim topics that the trusted issuer is allowed to emit - * This function can only be called by the owner of the Trusted Issuers Registry contract - * emits a `TrustedIssuerAdded` event - */ - function addTrustedIssuer(IClaimIssuer _trustedIssuer, uint256[] calldata _claimTopics) external; - - /** - * @dev Removes the ClaimIssuer contract of a trusted claim issuer. - * Requires that the claim issuer contract to be registered first - * @param _trustedIssuer the claim issuer to remove. - * This function can only be called by the owner of the Trusted Issuers Registry contract - * emits a `TrustedIssuerRemoved` event - */ - function removeTrustedIssuer(IClaimIssuer _trustedIssuer) external; - - /** - * @dev Updates the set of claim topics that a trusted issuer is allowed to emit. - * Requires that this ClaimIssuer contract already exists in the registry - * Requires that the provided claimTopics set is not empty - * Requires that there is no more than 15 claimTopics - * @param _trustedIssuer the claim issuer to update. - * @param _claimTopics the set of claim topics that the trusted issuer is allowed to emit - * This function can only be called by the owner of the Trusted Issuers Registry contract - * emits a `ClaimTopicsUpdated` event - */ - function updateIssuerClaimTopics(IClaimIssuer _trustedIssuer, uint256[] calldata _claimTopics) external; - - /** - * @dev Function for getting all the trusted claim issuers stored. - * @return array of all claim issuers registered. - */ - function getTrustedIssuers() external view returns (IClaimIssuer[] memory); - - /** - * @dev Function for getting all the trusted issuer allowed for a given claim topic. - * @param claimTopic the claim topic to get the trusted issuers for. - * @return array of all claim issuer addresses that are allowed for the given claim topic. - */ - function getTrustedIssuersForClaimTopic(uint256 claimTopic) external view returns (IClaimIssuer[] memory); - - /** - * @dev Checks if the ClaimIssuer contract is trusted - * @param _issuer the address of the ClaimIssuer contract - * @return true if the issuer is trusted, false otherwise. - */ - function isTrustedIssuer(address _issuer) external view returns (bool); - - /** - * @dev Function for getting all the claim topic of trusted claim issuer - * Requires the provided ClaimIssuer contract to be registered in the trusted issuers registry. - * @param _trustedIssuer the trusted issuer concerned. - * @return The set of claim topics that the trusted issuer is allowed to emit - */ - function getTrustedIssuerClaimTopics(IClaimIssuer _trustedIssuer) external view returns (uint256[] memory); - - /** - * @dev Function for checking if the trusted claim issuer is allowed - * to emit a certain claim topic - * @param _issuer the address of the trusted issuer's ClaimIssuer contract - * @param _claimTopic the Claim Topic that has to be checked to know if the `issuer` is allowed to emit it - * @return true if the issuer is trusted for this claim topic. - */ - function hasClaimTopic(address _issuer, uint256 _claimTopic) external view returns (bool); -} diff --git a/assets/eip-3770/examples.png b/assets/eip-3770/examples.png deleted file mode 100644 index 0793b6be0cfc33..00000000000000 Binary files a/assets/eip-3770/examples.png and /dev/null differ diff --git a/assets/eip-4337/image1.png b/assets/eip-4337/image1.png deleted file mode 100644 index ced8ea57d3ebb1..00000000000000 Binary files a/assets/eip-4337/image1.png and /dev/null differ diff --git a/assets/eip-4337/image2.png b/assets/eip-4337/image2.png deleted file mode 100644 index 6d0c6f71a4dcc7..00000000000000 Binary files a/assets/eip-4337/image2.png and /dev/null differ diff --git a/assets/eip-4361/example.js b/assets/eip-4361/example.js deleted file mode 100644 index 12ec7ee74a5ced..00000000000000 --- a/assets/eip-4361/example.js +++ /dev/null @@ -1,238 +0,0 @@ -// To run this example, navigate to this directory and run `npm i && node example.js` - -const apgApi = require('apg-js/src/apg-api/api'); -const apgLib = require('apg-js/src/apg-lib/node-exports'); - -const GRAMMAR = ` -sign-in-with-ethereum = - domain %s" wants you to sign in with your Ethereum account:" LF - address LF - LF - [ statement LF ] - LF - %s"URI: " URI LF - %s"Version: " version LF - %s"Chain ID: " chain-id LF - %s"Nonce: " nonce LF - %s"Issued At: " issued-at - [ LF %s"Expiration Time: " expiration-time ] - [ LF %s"Not Before: " not-before ] - [ LF %s"Request ID: " request-id ] - [ LF %s"Resources:" - resources ] - -domain = authority - -address = "0x" 40*40HEXDIG - ; Must also conform to captilization - ; checksum encoding specified in EIP-55 - ; where applicable (EOAs). - -statement = 1*( reserved / unreserved / " " ) - ; The purpose is to exclude LF (line breaks). - -version = "1" - -nonce = 8*( ALPHA / DIGIT ) - -issued-at = date-time -expiration-time = date-time -not-before = date-time - -request-id = *pchar - -chain-id = 1*DIGIT - ; See EIP-155 for valid CHAIN_IDs. - -resources = *( LF resource ) - -resource = "- " URI - -; ------------------------------------------------------------------------------ -; RFC 3986 - -URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] - -hier-part = "//" authority path-abempty - / path-absolute - / path-rootless - / path-empty - -scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - -authority = [ userinfo "@" ] host [ ":" port ] -userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) -host = IP-literal / IPv4address / reg-name -port = *DIGIT - -IP-literal = "[" ( IPv6address / IPvFuture ) "]" - -IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) - -IPv6address = 6( h16 ":" ) ls32 - / "::" 5( h16 ":" ) ls32 - / [ h16 ] "::" 4( h16 ":" ) ls32 - / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 - / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 - / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 - / [ *4( h16 ":" ) h16 ] "::" ls32 - / [ *5( h16 ":" ) h16 ] "::" h16 - / [ *6( h16 ":" ) h16 ] "::" - -h16 = 1*4HEXDIG -ls32 = ( h16 ":" h16 ) / IPv4address -IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet -dec-octet = DIGIT ; 0-9 - / %x31-39 DIGIT ; 10-99 - / "1" 2DIGIT ; 100-199 - / "2" %x30-34 DIGIT ; 200-249 - / "25" %x30-35 ; 250-255 - -reg-name = *( unreserved / pct-encoded / sub-delims ) - -path-abempty = *( "/" segment ) -path-absolute = "/" [ segment-nz *( "/" segment ) ] -path-rootless = segment-nz *( "/" segment ) -path-empty = 0pchar - -segment = *pchar -segment-nz = 1*pchar - -pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - -query = *( pchar / "/" / "?" ) - -fragment = *( pchar / "/" / "?" ) - -pct-encoded = "%" HEXDIG HEXDIG - -unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -reserved = gen-delims / sub-delims -gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" -sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - / "*" / "+" / "," / ";" / "=" - -; ------------------------------------------------------------------------------ -; RFC 3339 - -date-fullyear = 4DIGIT -date-month = 2DIGIT ; 01-12 -date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on - ; month/year -time-hour = 2DIGIT ; 00-23 -time-minute = 2DIGIT ; 00-59 -time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second - ; rules -time-secfrac = "." 1*DIGIT -time-numoffset = ("+" / "-") time-hour ":" time-minute -time-offset = "Z" / time-numoffset - -partial-time = time-hour ":" time-minute ":" time-second - [time-secfrac] -full-date = date-fullyear "-" date-month "-" date-mday -full-time = partial-time time-offset - -date-time = full-date "T" full-time - -; ------------------------------------------------------------------------------ -; RFC 5234 - -ALPHA = %x41-5A / %x61-7A ; A-Z / a-z -LF = %x0A - ; linefeed -DIGIT = %x30-39 - ; 0-9 -HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" -`; - -const parseMessage = (message) => { - const api = new apgApi(GRAMMAR); - api.generate(); - - const grammarObj = api.toObject(); - const parser = new apgLib.parser(); - parser.ast = new apgLib.ast(); - const id = apgLib.ids; - - const charToString = apgLib.utils.charsToString; - - const getField = (field) => function (state, chars, phraseIndex, phraseLength, data) { - const ret = id.SEM_OK; - if (state === id.SEM_PRE) { - data[field] = charToString(chars, phraseIndex, phraseLength); - } - return ret; - }; - - const domain = getField("domain"); - parser.ast.callbacks.domain = domain; - const address = getField("address"); - parser.ast.callbacks.address = address; - const statement = getField("statement"); - parser.ast.callbacks.statement = statement; - const uri = getField("uri"); - parser.ast.callbacks.uri = uri; - const version = getField("version"); - parser.ast.callbacks.version = version; - const chainId = getField("chainId"); - parser.ast.callbacks['chain-id'] = chainId; - const nonce = getField("nonce"); - parser.ast.callbacks.nonce = nonce; - const issuedAt = getField("issuedAt"); - parser.ast.callbacks['issued-at'] = issuedAt; - const expirationTime = getField("expirationTime"); - parser.ast.callbacks['expiration-time'] = expirationTime; - const notBefore = getField("notBefore"); - parser.ast.callbacks['not-before'] = notBefore; - const requestId = getField("requestId"); - parser.ast.callbacks['request-id'] = requestId; - - const resources = function (state, chars, phraseIndex, phraseLength, data) { - const ret = id.SEM_OK; - if (state === id.SEM_PRE) { - data.resources = apgLib.utils - .charsToString(chars, phraseIndex, phraseLength) - .slice(3) - .split('\n- '); - } - return ret; - }; - parser.ast.callbacks.resources = resources; - - const result = parser.parse(grammarObj, 'sign-in-with-ethereum', message); - if (!result.success) { - throw new Error(`Invalid message: ${JSON.stringify(result)}`); - } - const elements = {}; - parser.ast.translate(elements); - let obj = {}; - for (const [key, value] of Object.entries(elements)) { - obj[key] = value; - } - return obj; -} - -const createMessage = ({ domain, address, uri, version, chainId, nonce, issuedAt }) => { - const header = `${domain} wants you to sign in with your Ethereum account:\n${address}\n\n\n`; - const uriField = `URI: ${uri}\n`; - const versionField = `Version: ${version}\n`; - const chainField = `Chain ID: ${chainId}\n`; - const nonceField = `Nonce: ${nonce}\n`; - const issuedAtField = `Issued At: ${issuedAt}`; - return [header, uriField, versionField, chainField, nonceField, issuedAtField].join(''); -} - -const message = createMessage({ - domain: "example.com", - address: "0x51e913F93EBBF41f0DAc68219c31c1c15DFe3C49", - uri: "https://example.com", - version: '1', - chainId: '1', - nonce: "Ap4xKpGjEcYkYubmH4Vpw7pW8b3s6cJd", - issuedAt: "2022-05-18T14:11:46.065Z", -}); - -const messageObject = parseMessage(message); - -console.log(message, '\n'); -console.log(messageObject); diff --git a/assets/eip-4361/package.json b/assets/eip-4361/package.json deleted file mode 100644 index 18194739750aec..00000000000000 --- a/assets/eip-4361/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "eip4361-example", - "version": "1.0.0", - "description": "Reference implementation for Sign-In with Ethereum", - "main": "example.js", - "dependencies": { - "apg-js": "^4.1.1" - } -} diff --git a/assets/eip-4361/signing.png b/assets/eip-4361/signing.png deleted file mode 100644 index f92b4e6f3007cc..00000000000000 Binary files a/assets/eip-4361/signing.png and /dev/null differ diff --git a/assets/eip-4400/.gitignore b/assets/eip-4400/.gitignore deleted file mode 100644 index 6d1e7e071e9f71..00000000000000 --- a/assets/eip-4400/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules - -#Hardhat files -cache -artifacts -typechain diff --git a/assets/eip-4400/LICENSE b/assets/eip-4400/LICENSE deleted file mode 100644 index 1625c179360799..00000000000000 --- a/assets/eip-4400/LICENSE +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. \ No newline at end of file diff --git a/assets/eip-4400/README.md b/assets/eip-4400/README.md deleted file mode 100644 index d62d9ea2932cce..00000000000000 --- a/assets/eip-4400/README.md +++ /dev/null @@ -1,29 +0,0 @@ -
- -# ERC721 Consumable Extension - -[![License: CC0-1.0](https://img.shields.io/badge/License-CC0-yellow.svg)](https://creativecommons.org/publicdomain/zero/1.0/) - -
- -This project provides a reference implementation of the proposed `ERC721Consumer` OPTIONAL extension. - -## Install - -In order to install the required dependencies you need to execute: -```shell -npm install -``` - -## Compile - -In order to compile the solidity contracts you need to execute: -```shell -npx hardhat compile -``` - -## Tests - -```shell -npx hardhat test -``` \ No newline at end of file diff --git a/assets/eip-4400/abi/ERC721Consumable.json b/assets/eip-4400/abi/ERC721Consumable.json deleted file mode 100644 index 87c8cde16d0abc..00000000000000 --- a/assets/eip-4400/abi/ERC721Consumable.json +++ /dev/null @@ -1,410 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "name_", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol_", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "consumer", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ConsumerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_consumer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "changeConsumer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "consumerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "getApproved", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "_data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "tokenURI", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/assets/eip-4400/abi/IERC721Consumable.json b/assets/eip-4400/abi/IERC721Consumable.json deleted file mode 100644 index 8b0e6b8b3954d0..00000000000000 --- a/assets/eip-4400/abi/IERC721Consumable.json +++ /dev/null @@ -1,349 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "consumer", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ConsumerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_consumer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "changeConsumer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "consumerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "getApproved", - "outputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "_approved", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/assets/eip-4400/contracts/ERC721Consumable.sol b/assets/eip-4400/contracts/ERC721Consumable.sol deleted file mode 100644 index 56912b918f2954..00000000000000 --- a/assets/eip-4400/contracts/ERC721Consumable.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.11; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC721Consumable.sol"; - -contract ERC721Consumable is IERC721Consumable, ERC721 { - - // Mapping from token ID to consumer address - mapping(uint256 => address) _tokenConsumers; - - constructor (string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - /** - * @dev Returns true if the `msg.sender` is approved, owner or consumer of the `tokenId` - */ - function _isApprovedOwnerOrConsumer(uint256 tokenId) internal view returns (bool) { - return _isApprovedOrOwner(msg.sender, tokenId) || _tokenConsumers[tokenId] == msg.sender; - } - - /** - * @dev See {IERC721Consumable-consumerOf} - */ - function consumerOf(uint256 _tokenId) view external returns (address) { - require(_exists(_tokenId), "ERC721Consumable: consumer query for nonexistent token"); - return _tokenConsumers[_tokenId]; - } - - /** - * @dev See {IERC721Consumable-changeConsumer} - */ - function changeConsumer(address _consumer, uint256 _tokenId) external { - address owner = this.ownerOf(_tokenId); - require(msg.sender == owner || msg.sender == getApproved(_tokenId) || isApprovedForAll(owner, msg.sender), - "ERC721Consumable: changeConsumer caller is not owner nor approved"); - _changeConsumer(owner, _consumer, _tokenId); - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return interfaceId == type(IERC721Consumable).interfaceId || super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer(address _from, address _to, uint256 _tokenId) internal virtual override (ERC721) { - super._beforeTokenTransfer(_from, _to, _tokenId); - _changeConsumer(_from, address(0), _tokenId); - } - - /** - * @dev Changes the consumer - * Requirement: `tokenId` must exist - */ - function _changeConsumer(address _owner, address _consumer, uint256 _tokenId) internal { - _tokenConsumers[_tokenId] = _consumer; - emit ConsumerChanged(_owner, _consumer, _tokenId); - } -} diff --git a/assets/eip-4400/contracts/ExampleToken.sol b/assets/eip-4400/contracts/ExampleToken.sol deleted file mode 100644 index 74f5b48c2749ee..00000000000000 --- a/assets/eip-4400/contracts/ExampleToken.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.11; - -import "./ERC721Consumable.sol"; - -contract ExampleToken is ERC721Consumable { - - uint256 public idCounter = 0; - - constructor() ERC721Consumable("ReferenceImpl", "RIMPL") { } - - // @notice Mints new NFT to msg.sender - function mint() external returns (uint256) { - idCounter++; - _mint(msg.sender, idCounter); - return idCounter; - } -} diff --git a/assets/eip-4400/contracts/IERC721Consumable.sol b/assets/eip-4400/contracts/IERC721Consumable.sol deleted file mode 100644 index e03f737f18baae..00000000000000 --- a/assets/eip-4400/contracts/IERC721Consumable.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.11; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/// @title ERC-721 Consumer Role extension -/// Note: the ERC-165 identifier for this interface is 0x953c8dfa -interface IERC721Consumable is IERC721 { - - /// @notice Emitted when `owner` changes the `consumer` of an NFT - /// The zero address for consumer indicates that there is no consumer address - /// When a Transfer event emits, this also indicates that the consumer address - /// for that NFT (if any) is set to none - event ConsumerChanged(address indexed owner, address indexed consumer, uint256 indexed tokenId); - - /// @notice Get the consumer address of an NFT - /// @dev The zero address indicates that there is no consumer - /// Throws if `_tokenId` is not a valid NFT - /// @param _tokenId The NFT to get the consumer address for - /// @return The consumer address for this NFT, or the zero address if there is none - function consumerOf(uint256 _tokenId) view external returns (address); - - /// @notice Change or reaffirm the consumer address for an NFT - /// @dev The zero address indicates there is no consumer address - /// Throws unless `msg.sender` is the current NFT owner, an authorised - /// operator of the current owner or approved address - /// Throws if `_tokenId` is not valid NFT - /// @param _consumer The new consumer of the NFT - function changeConsumer(address _consumer, uint256 _tokenId) external; -} diff --git a/assets/eip-4400/hardhat.config.ts b/assets/eip-4400/hardhat.config.ts deleted file mode 100644 index da9785767d52a1..00000000000000 --- a/assets/eip-4400/hardhat.config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import '@nomiclabs/hardhat-waffle'; -import 'hardhat-abi-exporter'; -import 'hardhat-typechain'; - - -module.exports = { - solidity: { - compilers: [ - { - version: "0.8.11", - }, - ], - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - defaultNetwork: 'hardhat', - abiExporter: { - only: ['IERC721Consumable', 'ERC721Consumable'], - clear: true, - flat: true, - }, -}; diff --git a/assets/eip-4400/package.json b/assets/eip-4400/package.json deleted file mode 100644 index 3530cc75b46dfc..00000000000000 --- a/assets/eip-4400/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "hardhat-project", - "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.2", - "@nomiclabs/hardhat-waffle": "^2.0.1", - "@typechain/ethers-v5": "^2.0.0", - "@types/chai": "^4.2.14", - "@types/mocha": "^8.0.3", - "@types/node": "^14.14.6", - "chai": "^4.3.4", - "ethereum-waffle": "^3.4.0", - "ethers": "^5.4.7", - "hardhat": "^2.6.7", - "hardhat-abi-exporter": "^2.3.1", - "hardhat-typechain": "^0.3.3", - "ts-node": "^10.4.0", - "typechain": "^3.0.0", - "typescript": "^4.5.2" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.3.2" - } -} diff --git a/assets/eip-4400/test/erc721-consumable.ts b/assets/eip-4400/test/erc721-consumable.ts deleted file mode 100644 index a65c6e3ba34fce..00000000000000 --- a/assets/eip-4400/test/erc721-consumable.ts +++ /dev/null @@ -1,134 +0,0 @@ -import {ethers} from "hardhat"; -import {expect} from 'chai'; -import {SignerWithAddress} from "@nomiclabs/hardhat-ethers/signers"; -import {Erc721Consumable} from "../typechain"; - -describe("ERC721Consumable", async () => { - let owner: SignerWithAddress, approved: SignerWithAddress, operator: SignerWithAddress, consumer: SignerWithAddress, - other: SignerWithAddress; - let token: Erc721Consumable; - let snapshotId: any; - const tokenID = 1; // The first minted NFT - - before(async () => { - const signers = await ethers.getSigners(); - owner = signers[0]; - approved = signers[1]; - operator = signers[2]; - consumer = signers[3]; - other = signers[4]; - - const ConsumableToken = await ethers.getContractFactory("ExampleToken"); - const deployedContract = await ConsumableToken.deploy(); - await deployedContract.deployed(); - token = deployedContract as Erc721Consumable; - - await token.mint(); - }) - - beforeEach(async function () { - snapshotId = await ethers.provider.send('evm_snapshot', []); - }); - - afterEach(async function () { - await ethers.provider.send('evm_revert', [snapshotId]); - }); - - it('should implement ERC165', async () => { - expect(await token.supportsInterface("0x953c8dfa")).to.be.true; - }) - - it('should successfully change consumer', async () => { - // when: - await token.changeConsumer(consumer.address, tokenID); - // then: - expect(await token.consumerOf(tokenID)).to.equal(consumer.address); - }); - - it('should emit event with args', async () => { - // when: - const tx = await token.changeConsumer(consumer.address, tokenID); - - // then: - await expect(tx) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, consumer.address, tokenID); - }); - - it('should successfully change consumer when caller is approved', async () => { - // given: - await token.approve(approved.address, tokenID); - // when: - const tx = await token.connect(approved).changeConsumer(consumer.address, tokenID); - - // then: - await expect(tx) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, consumer.address, tokenID); - // and: - expect(await token.consumerOf(tokenID)).to.equal(consumer.address); - }); - - it('should successfully change consumer when caller is operator', async () => { - // given: - await token.setApprovalForAll(operator.address, true); - // when: - const tx = await token.connect(operator).changeConsumer(consumer.address, tokenID); - - // then: - await expect(tx) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, consumer.address, tokenID); - // and: - expect(await token.consumerOf(tokenID)).to.equal(consumer.address); - }); - - it('should revert when caller is not owner, not approved', async () => { - const expectedRevertMessage = 'ERC721Consumable: changeConsumer caller is not owner nor approved'; - await expect(token.connect(other).changeConsumer(consumer.address, tokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should revert when caller is approved for the token', async () => { - // given: - await token.changeConsumer(consumer.address, tokenID); - // then: - const expectedRevertMessage = 'ERC721Consumable: changeConsumer caller is not owner nor approved'; - await expect(token.connect(consumer).changeConsumer(consumer.address, tokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should revert when tokenID is nonexistent', async () => { - const invalidTokenID = 2; - const expectedRevertMessage = 'ERC721: owner query for nonexistent token'; - await expect(token.changeConsumer(consumer.address, invalidTokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should revert when calling consumerOf with nonexistent tokenID', async () => { - const invalidTokenID = 2; - const expectedRevertMessage = 'ERC721Consumable: consumer query for nonexistent token'; - await expect(token.consumerOf(invalidTokenID)) - .to.be.revertedWith(expectedRevertMessage); - }); - - it('should clear consumer on transfer', async () => { - await token.changeConsumer(consumer.address, tokenID); - await expect(token.transferFrom(owner.address, other.address, tokenID)) - .to.emit(token, 'ConsumerChanged') - .withArgs(owner.address, ethers.constants.AddressZero, tokenID); - }) - - it('should emit ConsumerChanged on mint', async () => { - await expect(token.mint()) - .to.emit(token, 'ConsumerChanged') - .withArgs(ethers.constants.AddressZero, ethers.constants.AddressZero, tokenID + 1); - }) - - it('should not be able to transfer from consumer', async () => { - const expectedRevertMessage = 'ERC721: transfer caller is not owner nor approved'; - await token.changeConsumer(consumer.address, tokenID); - await expect(token.connect(consumer).transferFrom(owner.address, other.address, tokenID)) - .to.revertedWith(expectedRevertMessage) - }) -}); \ No newline at end of file diff --git a/assets/eip-4400/tsconfig.json b/assets/eip-4400/tsconfig.json deleted file mode 100644 index 57fc82e7f48ca0..00000000000000 --- a/assets/eip-4400/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "outDir": "dist", - "resolveJsonModule": true - }, - "exclude": [ - "contracts" - ], - "include": [ - "./scripts", - "./test" - ], - "files": [ - "./hardhat.config.ts" - ] -} diff --git a/assets/eip-4519/ESP32_Firmware/main.cpp b/assets/eip-4519/ESP32_Firmware/main.cpp deleted file mode 100644 index 23450745728ae7..00000000000000 --- a/assets/eip-4519/ESP32_Firmware/main.cpp +++ /dev/null @@ -1,709 +0,0 @@ -/*** - * This example of ESP32 (Pycom LoPy4) firmware was developed by Javier Arcenegui at "Instituto de - * Microelectrónica de Sevilla IMSE-CNM (Universidad de Sevilla-CSIC)" for the EIP4519. This firmware makes the device generate its own Ethereum account and save in the EEPROM the data required in the future to regenerate - * the same account. In the newest versions of ESP32 ("ESP32 S2" or - * "ESP32 S3") these data can be saved in a protected area. - * - * The Smart Contract is published on "0x6Dba58fF5AA2d8447C4460d2527033A81646Ae97". - * It was proved in the Ethereum Kovan testnet. - * - * This code was developed with the infuraIO extension for Visual Studio Code and the Arduino Framework. - * The alphawallet/Web3E@^1.22 library must be added in the configuration file (platformio.ini) - * - * Helper data are employed to regenerate the same Ethereum account based on CTR-DRBG PRNG. - * The helper data format is the following: - * - * ------------------------- Helper data Format ----------------------- - * |---------------|----------------|----------------|----|----|----| - * | key_ctr |Personalization | contex_counter | rc | el | ri | - * |---------------|----------------|----------------|----|----|----| - * 51 35 19 3 2 1 0 (Byte) - * - ***/ - -#include -#include -#include -#include -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/entropy.h" -#include -#include -#include "esp_wifi.h" -#include -#include -#include -#include -#include "Trezor/secp256k1.h" -#include "Trezor/ecdsa.h" -#include "Trezor/sha3.h" - -#define EEPROM_SIZE 52 //The size required by the 51 bytes of helper data and the byte to know if the token is registered on the blockchain -#define EIP4519CONTRACT "0x6Dba58fF5AA2d8447C4460d2527033A81646Ae97" //This is the reference for the EIP4519 Smart Non-Fungible Token on Kovan (IT MUST BE CHANGED) - -//These are the states of the EIP4519 -enum STATES{ - waitingForOwner = 0, - engagedWithOwner = 1, - waitingForUser = 2, - engagedWithUser = 3 -}; - -void setupWifi(); //This function is the same as the wifi example of the ESP32 Arduino Wifi -uint8_t HexTo4Bits(uint8_t Hex); //This function is used to change an hexadecimal defined in ASCII character to a uint8_t -STATES queryTokenStatus(); //This function is used to recover the information of the SmartNFT associated to this device -void hex2Char(unsigned char *inHex, unsigned char *charH, unsigned char *charL); //This function is needed to convert an uint8_t to two ASCII characters - -void sendEngage(); //This function sends the hash of the shared key to complete the transaction in Ethereum - -//Declaration of the variable to save the state of the SmartNFT -STATES tokenState; - -//Declaration of the variables for the private/public Keys and the Address associated - -unsigned char privateKey_ethAccount[32]; -unsigned char publicKey_ethAccount[64]; -unsigned char ethAddress[20]; - -//Declaration of the variable to save the helper data generated/recovered, which are employed to obtain the private Key of Ethereum account -uint8_t helperData[51]; - -//These variables are used to save the addresses in ASCII associated to the SmartNFT and the SmartNFT ID -unsigned char OwnerAddress[42]; -unsigned char UserAddress[42]; -unsigned char deviceAddress[43]; -uint32_t tokenID; - -//Wifi setup parameters: IT MUST BE CHANGED -int wificounter = 0; -const char *ssid = ""; -const char *password = "= 10) - { - printf("Restarting ...\n"); - ESP.restart(); //Targeting 8266 & ESP32. You may need to replace this - - } - - delay(10); - - printf("\n"); - printf("WiFi connected.\n"); - printf("IP address: %d.%d.%d.%d\n",WiFi.localIP()[0],WiFi.localIP()[1],WiFi.localIP()[2],WiFi.localIP()[3]); -} - -STATES queryTokenStatus(){ - Contract contract(&web3, EIP4519CONTRACT); - //Generate the JSON to check the status - deviceAddress[0] = '0'; - deviceAddress[1] = 'x'; - for(int i = 0 ; i < 20 ; i++){ - hex2Char(ðAddress[i], &deviceAddress[2*i+2], &deviceAddress[2*i+3]); - } - - String tokenAddress; - - printf("Query Status from token : "); - for(int i = 0 ; i < 40 ; i++){ - printf("%c",deviceAddress[i+2]); - tokenDevice[i+2] = deviceAddress[i+2]; - } - printf("\n"); - - - //Call the function to get SmartNFT information - String func = "getInfoTokenFromBCA(address)"; - string param = contract.SetupContractData(func.c_str(), &tokenDevice); - string result = contract.ViewCall(¶m); - - //When data from blockchain are received, the SmartNFT information is saved - printf("%s\n",result.c_str()); - int i = 0; - while(result[i] != 'x'){ - i++; - } - i++; - OwnerAddress[0] = UserAddress[0] = '0'; - OwnerAddress[1] = UserAddress[1] = 'x'; - //Owner address - for(int j =24;j<64;j++){ - OwnerAddress[j-22] = result[i+j]; - } - i +=64; - //User address - for(int j =24;j<64;j++){ - UserAddress[j-22] = result[i+j]; - } - - printf("\nOwner of token : 0x"); - for(int j = 0 ; j < 40 ; j++){ - printf("%c",OwnerAddress[j+2]); - } - printf("\nUser of token : 0x"); - for(int j = 0 ; j < 40 ; j++){ - printf("%c",UserAddress[j+2]); - } - i +=64; - - //tokenID - tokenID = HexTo4Bits(result[i+56])*268435456+HexTo4Bits(result[i+57])*16777216+HexTo4Bits(result[i+58])*1048576+HexTo4Bits(result[i+59])*65536+HexTo4Bits(result[i+60])*4096+HexTo4Bits(result[i+61])*256+HexTo4Bits(result[i+62])*16+HexTo4Bits(result[i+63]); - printf("\nToken ID = %d\n",tokenID); - - //SmartNFT state - switch ((result[i+63]) - { - case '1': - return engagedWithOwner; - break; - case '2': - return waitingForUser; - break; - case '3': - return engagedWithUser; - break; - default: - return waitingForOwner - break; - } -} - - -uint8_t HexTo4Bits(uint8_t Hex){ - if (Hex>='0'&&Hex<='9'){ - return (Hex-'0'); - }else if(Hex>='a'&&Hex<='f'){ - return Hex-'a'+10; - }else if(Hex>='A'&&Hex<='F'){ - return Hex-'A'+10; - } -} - -void hex2Char(unsigned char *inHex, unsigned char *charH, unsigned char *charL){ - uint8_t charIN[2]; - charIN[0] = (inHex[0] / 16); - charIN[1] = (inHex[0] % 16); - if (charIN[0] < 10){ - charH[0] = charIN[0] + '0'; - }else{ - charH[0] = charIN[0] + 'a' - 10; - } - if (charIN[1] < 10){ - charL[0] = charIN[1] + '0'; - }else{ - charL[0] = charIN[1] + 'a' - 10; - } -} - -void sendEngage(){ - Contract contract(&web3, EIP4519CONTRACT); - //Define the function to call the blockchain. This function depends on the SmartNFT state - string func; - if(tokenState == waitingForOwner){ - func = "ownerEngagement(uint256)"; - }else if(tokenState == waitingForUser){ - func = "userEngagement(uint256)"; - } - - //Generate the hash of the shared key - keccak_256(K_XD,32,hash_K_XD); - - //Put this hash as a uint256 variable - uint256_t hash_K_XD_uin256 = 0; - for(int i =0 ; i < 32 ; i++){ - hash_K_XD_uin256 += (uint256_t)hash_K_XD[i]<<8*(7-i); - } - - //Call the function - string param = contract.SetupContractData(func.c_str(), &hash_K_XD_uin256); - string result = contract.Call(¶m); - -} diff --git a/assets/eip-4519/ESP32_Firmware/readme.md b/assets/eip-4519/ESP32_Firmware/readme.md deleted file mode 100644 index 611e466e0fd979..00000000000000 --- a/assets/eip-4519/ESP32_Firmware/readme.md +++ /dev/null @@ -1,16 +0,0 @@ -#EIP4519 Proof of Concept - Firmware -This firmware is designed for a device using an ESP32 as a smart asset associated with an EIP4519 SmartNFT. The device has two operation modes: registration mode and application mode. -##Registration mode -In this mode, the device generates 51 bytes with the TRNG of the ESP32 core. Those bytes are used for the initial values of a CTR-DRBG PRNG to generate the private key of the Ethereum account. Only the address of this account is shared. The UART port is needed for communications with this device. -The commands in this mode are: ->‘0’ – Check if the device is ready. ->‘1’ – Share the address of the account. ->‘2’ – Save the initial values of CTR-DRBG PRNG in an EEPROM and changes the operation mode. -##Application Mode -The device reads the EEPROM to obtain the initial values of the CTR-DRBG PRNG and recover the Ethereum account. The device connects to a WiFi station. With Infura, the device checks the state of its associated SmartNFT registered on an EIP4519 Smart Contract and also checks if the device must be engaged with the owner or the user. The UART port is needed for communications with this device. -The commands in this mode are: ->'Z'+OWNER/USER_ADDRESS – The device checks if the address must be authenticated and generates a nonce. ->'Y'+SIGN_D+'#'+NONCE_D – The device checks the signature, signs NONCE_D, and sends the signature. ->'Y'+SIGNED_PK+'#'+PK – The device checks the signature, generates the shared key, and sends the transaction to the EIP4519 Smart Contract. ->'C' – The EEPROM is cleared, only for debug process. ->'R' – The device is restarted to refresh the SmartNFT state, only for debug process. diff --git a/assets/eip-4519/PoC_SmartNFT/ERC721_interface.sol b/assets/eip-4519/PoC_SmartNFT/ERC721_interface.sol deleted file mode 100644 index 1fef13e705f449..00000000000000 --- a/assets/eip-4519/PoC_SmartNFT/ERC721_interface.sol +++ /dev/null @@ -1,126 +0,0 @@ -///SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; -/// @title ERC-721 Non-Fungible Token Standard -/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md -/// Note: the ERC-165 identifier for this interface is 0x80ac58cd -interface ERC721 /* is ERC165 */ { - /// @dev This emits when ownership of any NFT changes by any mechanism. - /// This event emits when NFTs are created (`from` == 0) and destroyed - /// (`to` == 0). Exception: during contract creation, any number of NFTs - /// may be created and assigned without emitting Transfer. At the time of - /// any transfer, the approved address for that NFT (if any) is reset to none. - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - /// @dev This emits when the approved address for an NFT is changed or - /// reaffirmed. The zero address indicates there is no approved address. - /// When a Transfer event emits, this also indicates that the approved - /// address for that NFT (if any) is reset to none. - event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); - - /// @dev This emits when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - - /// @notice Count all NFTs assigned to an owner - /// @dev NFTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param _owner An address for whom to query the balance - /// @return The number of NFTs owned by `_owner`, possibly zero - function balanceOf(address _owner) external view returns (uint256); - - /// @notice Find the owner of an NFT - /// @dev NFTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param _tokenId The identifier for an NFT - /// @return The address of the owner of the NFT - function ownerOf(uint256 _tokenId) external view returns (address); - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. When transfer is complete, this function - /// checks if `_to` is a smart contract (code size > 0). If so, it calls - /// `onERC721Received` on `_to` and throws if the return value is not - /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - /// @param data Additional data with no specified format, sent in call to `_to` - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external payable; - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev This works identically to the other function with an extra data parameter, - /// except this function just sets data to "" - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE - /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE - /// THEY MAY BE PERMANENTLY LOST - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Set or reaffirm the approved address for an NFT - /// @dev The zero address indicates there is no approved address. - /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized - /// operator of the current owner. - /// @param _approved The new approved NFT controller - /// @param _tokenId The NFT to approve - function approve(address _approved, uint256 _tokenId) external payable; - - /// @notice Enable or disable approval for a third party ("operator") to manage - /// all of `msg.sender`'s assets. - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// multiple operators per owner. - /// @param _operator Address to add to the set of authorized operators. - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAll(address _operator, bool _approved) external; - - /// @notice Get the approved address for a single NFT - /// @dev Throws if `_tokenId` is not a valid NFT - /// @param _tokenId The NFT to find the approved address for - /// @return The approved address for this NFT, or the zero address if there is none - function getApproved(uint256 _tokenId) external view returns (address); - - - /// @notice Query if an address is an authorized operator for another address - /// @param _owner The address that owns the NFTs - /// @param _operator The address that acts on behalf of the owner - /// @return True if `_operator` is an approved operator for `_owner`, false otherwise - function isApprovedForAll(address _owner, address _operator) external view returns (bool); -} - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} - -interface ERC721TokenReceiver { - /// @notice Handle the receipt of an NFT - /// @dev The ERC721 smart contract calls this function on the - /// recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return - /// of other than the magic value MUST result in the transaction being reverted. - /// @notice The contract address is always the message sender. - /// @param _operator The address which called `safeTransferFrom` function - /// @param _from The address which previously owned the token - /// @param _tokenId The NFT identifier which is being transferred - /// @param _data Additional data with no specified format - /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` - /// unless throwing - function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes memory _data) external returns(bytes4); -} diff --git a/assets/eip-4519/PoC_SmartNFT/PoC_SmartNFT.sol b/assets/eip-4519/PoC_SmartNFT/PoC_SmartNFT.sol deleted file mode 100644 index 2681dbc988c1b3..00000000000000 --- a/assets/eip-4519/PoC_SmartNFT/PoC_SmartNFT.sol +++ /dev/null @@ -1,272 +0,0 @@ -///SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./smartNFT_interface.sol"; -import "./ERC721_interface.sol"; - -contract smartNFT_SC is ERC721,smartNFT{ - enum States { waitingForOwner, engagedWithOwner, waitingForUser, engagedWithUser } - - address manufacturer; //Address of manufacturer and owner of Smart Contract - uint256 tokenCounter; //To give a genuine tokenID based on the number of tokens created - mapping(uint256 => address) ownerOfSD; //To khow who is the owner of a specific owner - mapping(address => uint256) tokenIDOfBCA; //To khow which is the tokenID associated to a secure device from the address - mapping(address => uint256) ownerBalance; //To know how many tokens an owner has - mapping(address => uint256) userBalance; //To know how many tokens a user can use - - struct Token_Struct{ - address approved; //Indicate who can transfer this token, 0 if no one - address SD; //Indicate the address of the secure device associated to this token - address user; //Indicate who can use this secure device - States state; //If blocked (false) then token should be verified by a new user or a new owner - uint256 hashK_OD; //Hash of the Key shared between owner and device - uint256 hashK_UD; //Hash of the Key shared between user and device - uint256 dataEngagement; //Public Key to create K_OD or K_UD depending on token state - uint256 timestamp; //Last time that device updated its proof of live - uint256 timeout; //timeout to verify a device error - } - - Token_Struct[] Secure_Token; - - constructor() { - manufacturer = msg.sender; - tokenCounter = 1; - Secure_Token.push(Token_Struct(address(0), address(0), address(0), States.waitingForOwner,0,0,0,0,0)); - - } - - function createToken(address _addressSD, address _addressOwner) public virtual override returns (uint256){ - //Check if the sender of message is the manufacturer - require(manufacturer == msg.sender); - //Check if the Blockchain Account of the secure device is in the SmartContract - if(tokenFromBCA(_addressSD)==0){ - //Create a new token - Secure_Token.push(Token_Struct(address(0), _addressSD, address(0), States.waitingForOwner,0,0,0,block.timestamp,86400)); - //Assigning a new tokenId - uint256 _tokenId = tokenCounter ++; - tokenIDOfBCA[_addressSD] = _tokenId; - //Assigning the owner - ownerOfSD[_tokenId] = _addressOwner; - ownerBalance[_addressOwner]++; - //Return tokenId obtained - return(_tokenId); - }else{ - //If the BCA already exists then return the _tokenId - return(tokenFromBCA(_addressSD)); - } - } - - function setUser(uint256 _tokenId, address _addressUser) public virtual override{ - //Check the sender and the token state - require((ownerOfSD[_tokenId] == msg.sender) && (Secure_Token[_tokenId].state >= States.engagedWithOwner)); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - //Only to avoid overflow, for example, in address 0. - if(userBalance[Secure_Token[_tokenId].user]>0){ - //Update the balance of tokens assigned to the old user - userBalance[Secure_Token[_tokenId].user]--; - } - //Update the balance of tokens assigned to the new user - userBalance[_addressUser]++; - //Assign the new user to the token - Secure_Token[_tokenId].user = _addressUser; - //Update the state of the token - Secure_Token[_tokenId].state = States.waitingForUser; - //Erase old key exchange data between device with old user assigned - Secure_Token[_tokenId].dataEngagement =0; - Secure_Token[_tokenId].hashK_UD = 0; - emit UserAssigned(_tokenId,_addressUser); - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function startOwnerEngagement(uint256 _tokenId, uint256 _dataEngagement, uint256 _hashK_O) public virtual override{ - //Check if sender is the Owner of token and the State of token - require(ownerOfSD[_tokenId] == msg.sender); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - Secure_Token[_tokenId].dataEngagement = _dataEngagement; - Secure_Token[_tokenId].hashK_OD = _hashK_O; - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function ownerEngagement(uint256 _hashK_D) public virtual override{ - uint256 _tokenId = tokenFromBCA(msg.sender); - //Check if public key owner-device exists from tokenID of BCA sender - require(Secure_Token[_tokenId].dataEngagement != 0); - require (Secure_Token[_tokenId].hashK_OD == _hashK_D); - require (Secure_Token[_tokenId].state == States.waitingForOwner); - //Erase PK_Owner-Device and update timestamp - Secure_Token[_tokenId].dataEngagement = 0; - Secure_Token[_tokenId].timestamp = block.timestamp; - //Update the state of token - Secure_Token[_tokenId].state = States.engagedWithOwner; - //Send a notification to owner and device - emit OwnerEngaged(_tokenId); - } - - function startUserEngagement(uint256 _tokenId, uint256 _dataEngagement, uint256 _hashK_U) public virtual override{ - //Check the sender and the state of token - require(Secure_Token[_tokenId].user == msg.sender); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - Secure_Token[_tokenId].dataEngagement = _dataEngagement; - Secure_Token[_tokenId].hashK_UD = _hashK_U; - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function userEngagement(uint256 _hashK_D) public virtual override{ - uint256 _tokenId = tokenFromBCA(msg.sender); - //Check if public key user-device exists from tokenID of BCA sender - require(Secure_Token[_tokenId].dataEngagement != 0); - require (Secure_Token[_tokenId].hashK_UD == _hashK_D); - require (Secure_Token[_tokenId].state == States.waitingForUser); - //Erase PK_User-Device and update timestamp - Secure_Token[_tokenId].dataEngagement = 0; - Secure_Token[_tokenId].timestamp = block.timestamp; - //Update the state of token - Secure_Token[_tokenId].state = States.engagedWithUser; - //Send a notification to user and device - emit UserEngaged(_tokenId); - } - - - function tokenFromBCA(address _addressSD) public virtual view override returns (uint256){ - return(tokenIDOfBCA[_addressSD]); - } - - function ownerOfFromBCA(address _addressSD) public virtual view override returns (address){ - return(ownerOfSD[tokenIDOfBCA[_addressSD]]); - } - - function userOf(uint256 _tokenId) public virtual view override returns (address){ - return(Secure_Token[_tokenId].user); - } - - function userOfFromBCA(address _addressSD) public virtual override view returns (address){ - return(Secure_Token[tokenIDOfBCA[_addressSD]].user); - } - - function userBalanceOf(address _addressUser) public virtual override view returns (uint256){ - return(userBalance[_addressUser]); - } - - function userBalanceOfAnOwner(address _addressUser, address _addressOwner) public virtual override view returns (uint256){ - //TODO - } - - function getInfoToken(uint256 _tokenId) public view returns ( address _BCA_OWNER, - address _BCA_USER, - address _BCA_SD, - uint8 _state){ - _BCA_OWNER = ownerOfSD[_tokenId]; - _BCA_USER = Secure_Token[_tokenId].user; - _BCA_SD = Secure_Token[_tokenId].SD; - if(Secure_Token[_tokenId].state == States.waitingForOwner){ - _state = 0; - }else if(Secure_Token[_tokenId].state == States.engagedWithOwner){ - _state = 1; - }else if(Secure_Token[_tokenId].state == States.waitingForUser){ - _state = 2; - }else { - _state = 3; - } - } - - function getInfoTokenFromBCA(address _addressSD) public view returns ( address _BCA_OWNER, - address _BCA_USER, - uint256 _tokenId, - uint8 _state){ - _tokenId = tokenIDOfBCA[_addressSD]; - _BCA_OWNER = ownerOfSD[_tokenId]; - _BCA_USER = Secure_Token[_tokenId].user; - if(Secure_Token[_tokenId].state == States.waitingForOwner){ - _state = 0; - }else if(Secure_Token[_tokenId].state == States.engagedWithOwner){ - _state = 1; - }else if(Secure_Token[_tokenId].state == States.waitingForUser){ - _state = 2; - }else { - _state = 3; - } - } - - function balanceOf(address _owner) public virtual override view returns (uint256){ - return(ownerBalance[_owner]); - } - - function ownerOf(uint256 _tokenId) public virtual override view returns (address){ - return(ownerOfSD[_tokenId]); - } - - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) public virtual override payable{ - - } - - function safeTransferFrom(address _from, address _to, uint256 _tokenId) public virtual override payable{ - transferFrom(_from, _to, _tokenId); - } - - function transferFrom(address _from, address _to, uint256 _tokenId) public virtual override payable{ - require((ownerOfSD[_tokenId] == msg.sender)||(Secure_Token[_tokenId].approved == msg.sender)); - require(ownerOfSD[_tokenId] == _from); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - ownerOfSD[_tokenId] = _to; - ownerBalance[_from]--; - ownerBalance[_to]++; - //Secure_Token[_tokenId].approved = address(0); - Secure_Token[_tokenId].user = address(0); - Secure_Token[_tokenId].state = States.waitingForOwner; - //Erase old key exchange data between device with old Owner - Secure_Token[_tokenId].dataEngagement = 0; - Secure_Token[_tokenId].hashK_UD = 0; - Secure_Token[_tokenId].hashK_OD = 0; - emit Transfer(_from,_to,_tokenId); - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - } - } - - function approve(address _approved, uint256 _tokenId) public virtual override payable{ - - } - - function setApprovalForAll(address _operator, bool _approved) public virtual override{ - - } - - function getApproved(uint256 _tokenId) public virtual override view returns (address){ - - } - - function isApprovedForAll(address _owner, address _operator) public virtual override view returns (bool){ - - } - - function checkTimeout(uint256 _tokenId) public virtual override returns (bool){ - require(ownerOfSD[_tokenId] == msg.sender); - if((Secure_Token[_tokenId].timestamp + Secure_Token[_tokenId].timeout) > block.timestamp){ - return true; - }else{ - Secure_Token[_tokenId].user = address(0); - emit TimeoutAlarm(_tokenId); - return false; - } - } - - function updateTimestamp() public virtual override{ - Secure_Token[tokenFromBCA(msg.sender)].timestamp = block.timestamp; - } - - function setTimeout(uint256 _tokenId, uint256 _timeout) public virtual override{ - require(ownerOfSD[_tokenId] == msg.sender); - Secure_Token[_tokenId].timeout = _timeout; - } -} diff --git a/assets/eip-4519/PoC_SmartNFT/README.md b/assets/eip-4519/PoC_SmartNFT/README.md deleted file mode 100644 index cbb277346fd0e2..00000000000000 --- a/assets/eip-4519/PoC_SmartNFT/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Proof of concept of an implementation of an Smart Non Fungible Token -This proof of concept is launch in the Ethereum Kovan Testnet with the address 0x7eB5A03E7ED70ABf70fee48965D0411d37F335aC. -Use the proposal Non Fungible Token binding assets with SmartNFT and define the user management of the assets. diff --git a/assets/eip-4519/PoC_SmartNFT/SmartNFT_interface.sol b/assets/eip-4519/PoC_SmartNFT/SmartNFT_interface.sol deleted file mode 100644 index 6b212032ccfacc..00000000000000 --- a/assets/eip-4519/PoC_SmartNFT/SmartNFT_interface.sol +++ /dev/null @@ -1,83 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/// @title smartNFT: Hardblock - ERC-721 Non-Fungible Token Standard-based -interface smartNFT{ - /// @dev This emits when an user of a NFT changes - /// This event emits when the user of a token is assigned - /// (`_addressUser` == 0) when a user is unassigned - event UserAssigned(uint256 indexed tokenID, address indexed _addressUser); - - /// @dev This emits when an user of a NFT verifies a device - /// This event emits when the user of a device finishes the assignment process - event UserEngaged(uint256 indexed tokenID); - - /// @dev This emits when an owner of a NFT verifies a device - /// This event emits when the owner of a device finishes the transfer process - event OwnerEngaged(uint256 indexed tokenID); - - //TODO: Describe new functions and events - event TimeoutAlarm(uint256 indexed tokenID); - - /// @notice This function defines how the smart device is bound to a new token - /// @dev Only the manufacturer of the smart device account can create a token and will be the first owner of the token - /// The initial state of the token is "waitingForOwner" until verified by the new owner - /// @param _addressSD An address generated by the smart device. Only de smart device can generate this account. _addressOwner is the first owner of the Smart Device - /// @return The tokenID of the token bound to the smart device - function createToken(address _addressSD, address _addressOwner) external returns (uint256); - - /// @notice This function defines the transference of use of a smart device - /// @dev Only the owner of the token account can transfer a token provided that the state of the token is "engagedWithOwner","waitingForUser" or "engagedWithUser". - /// The state of the token must change to "waitingForUser" and the parameter addressUser of the token defined by _tokenID must change to _addressUser - /// @param _tokenId The tokenID of the smart device - /// @param _addressUser The address of the new user - function setUser(uint256 _tokenId, address _addressUser) external; - - //TODO: Describe new functions and events - - function startOwnerEngagement(uint256 _tokenId, uint256 _dataEngage, uint256 _hashK_O) external; - function ownerEngagement(uint256 _hashK_D) external; - function startUserEngagement(uint256 _tokenId, uint256 _dataEngage, uint256 _hashK_U) external; - function userEngagement(uint256 _hashK_D) external; - function checkTimeout(uint256 _tokenId) external returns (bool); - function setTimeout(uint256 _tokenId, uint256 _timeout) external; - function updateTimestamp() external; - - /// @notice This function let us obtain the tokenID from an address of smart device - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressSD The address to obtain its token ID - /// @return The token ID of the token bound to _addressSD - function tokenFromBCA(address _addressSD) external view returns (uint256); - - /// @notice This function let us know who is the owner of the token from the address of the smart device - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressSD The address to obtain its owner - /// @return The owner of the token bound to _addressSD - function ownerOfFromBCA(address _addressSD) external view returns (address); - - /// @notice This function let us know who is the user of the token from the tokenId - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _tokenId of the token to obtain its user - /// @return The user of the token with _tokenId - function userOf(uint256 _tokenId) external view returns (address); - - /// @notice This function let us know who is the user of the token from the address of the smart device - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressSD The address to obtain its user. - /// @return The user of the token bound to _addressSD. - function userOfFromBCA(address _addressSD) external view returns (address); - - /// @notice This function let us know how many tokens an user has - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressUser The address of the user to know the number of tokens - /// @return The number of tokens of the user - function userBalanceOf(address _addressUser) external view returns (uint256); - - /// @notice This function let us know how many tokens of an owner an user has - /// @dev Everybody can call this function. It does not execute any code on blockchain, only reads - /// @param _addressUser The address of the user to know the number of tokens - /// @param _addressOwner The address of the owner of the tokens - /// @return The number of tokens of an owner that an user can use - function userBalanceOfAnOwner(address _addressUser, address _addressOwner) external view returns (uint256); -} diff --git a/assets/eip-4519/images/Figure1.jpg b/assets/eip-4519/images/Figure1.jpg deleted file mode 100644 index ac70ae6a545513..00000000000000 Binary files a/assets/eip-4519/images/Figure1.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure2.jpg b/assets/eip-4519/images/Figure2.jpg deleted file mode 100644 index aa99370a85c776..00000000000000 Binary files a/assets/eip-4519/images/Figure2.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure3.jpg b/assets/eip-4519/images/Figure3.jpg deleted file mode 100644 index dd0e0efa150552..00000000000000 Binary files a/assets/eip-4519/images/Figure3.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure4.jpg b/assets/eip-4519/images/Figure4.jpg deleted file mode 100644 index 62f1f81b0c95d1..00000000000000 Binary files a/assets/eip-4519/images/Figure4.jpg and /dev/null differ diff --git a/assets/eip-4519/images/Figure5.jpg b/assets/eip-4519/images/Figure5.jpg deleted file mode 100644 index 1f8a1707e6e6ca..00000000000000 Binary files a/assets/eip-4519/images/Figure5.jpg and /dev/null differ diff --git a/assets/eip-4519/sensors-21-03119.pdf b/assets/eip-4519/sensors-21-03119.pdf deleted file mode 100644 index 1c8f8dbce9fc21..00000000000000 Binary files a/assets/eip-4519/sensors-21-03119.pdf and /dev/null differ diff --git a/assets/eip-4671/ERC4671.sol b/assets/eip-4671/ERC4671.sol deleted file mode 100644 index 5343d9e2eab4f7..00000000000000 --- a/assets/eip-4671/ERC4671.sol +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -import "./IERC4671.sol"; -import "./IERC4671Metadata.sol"; -import "./IERC4671Enumerable.sol"; - -abstract contract ERC4671 is IERC4671, IERC4671Metadata, IERC4671Enumerable, ERC165 { - // Token data - struct Token { - address issuer; - address owner; - bool valid; - } - - // Mapping from tokenId to token - mapping(uint256 => Token) private _tokens; - - // Mapping from owner to token ids - mapping(address => uint256[]) private _indexedTokenIds; - - // Mapping from token id to index - mapping(address => mapping(uint256 => uint256)) private _tokenIdIndex; - - // Mapping from owner to number of valid tokens - mapping(address => uint256) private _numberOfValidTokens; - - // Token name - string private _name; - - // Token symbol - string private _symbol; - - // Total number of tokens emitted - uint256 private _emittedCount; - - // Total number of token holders - uint256 private _holdersCount; - - // Contract creator - address private _creator; - - constructor (string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - _creator = msg.sender; - } - - /// @notice Count all tokens assigned to an owner - /// @param owner Address for whom to query the balance - /// @return Number of tokens owned by `owner` - function balanceOf(address owner) public view virtual override returns (uint256) { - return _indexedTokenIds[owner].length; - } - - /// @notice Get owner of a token - /// @param tokenId Identifier of the token - /// @return Address of the owner of `tokenId` - function ownerOf(uint256 tokenId) public view virtual override returns (address) { - return _getTokenOrRevert(tokenId).owner; - } - - /// @notice Check if a token hasn't been revoked - /// @param tokenId Identifier of the token - /// @return True if the token is valid, false otherwise - function isValid(uint256 tokenId) public view virtual override returns (bool) { - return _getTokenOrRevert(tokenId).valid; - } - - /// @notice Check if an address owns a valid token in the contract - /// @param owner Address for whom to check the ownership - /// @return True if `owner` has a valid token, false otherwise - function hasValid(address owner) public view virtual override returns (bool) { - return _numberOfValidTokens[owner] > 0; - } - - /// @return Descriptive name of the tokens in this contract - function name() public view virtual override returns (string memory) { - return _name; - } - - /// @return An abbreviated name of the tokens in this contract - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /// @notice URI to query to get the token's metadata - /// @param tokenId Identifier of the token - /// @return URI for the token - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - _getTokenOrRevert(tokenId); - bytes memory baseURI = bytes(_baseURI()); - if (baseURI.length > 0) { - return string(abi.encodePacked( - baseURI, - Strings.toHexString(tokenId, 32) - )); - } - return ""; - } - - /// @return emittedCount Number of tokens emitted - function emittedCount() public view override returns (uint256) { - return _emittedCount; - } - - /// @return holdersCount Number of token holders - function holdersCount() public view override returns (uint256) { - return _holdersCount; - } - - /// @notice Get the tokenId of a token using its position in the owner's list - /// @param owner Address for whom to get the token - /// @param index Index of the token - /// @return tokenId of the token - function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { - uint256[] storage ids = _indexedTokenIds[owner]; - require(index < ids.length, "Token does not exist"); - return ids[index]; - } - - /// @notice Get a tokenId by it's index, where 0 <= index < total() - /// @param index Index of the token - /// @return tokenId of the token - function tokenByIndex(uint256 index) public view virtual override returns (uint256) { - return index; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return - interfaceId == type(IERC4671).interfaceId || - interfaceId == type(IERC4671Metadata).interfaceId || - interfaceId == type(IERC4671Enumerable).interfaceId || - super.supportsInterface(interfaceId); - } - - /// @notice Prefix for all calls to tokenURI - /// @return Common base URI for all token - function _baseURI() internal pure virtual returns (string memory) { - return ""; - } - - /// @notice Mark the token as revoked - /// @param tokenId Identifier of the token - function _revoke(uint256 tokenId) internal virtual { - Token storage token = _getTokenOrRevert(tokenId); - require(token.valid, "Token is already invalid"); - token.valid = false; - assert(_numberOfValidTokens[token.owner] > 0); - _numberOfValidTokens[token.owner] -= 1; - emit Revoked(token.owner, tokenId); - } - - /// @notice Mint a new token - /// @param owner Address for whom to assign the token - /// @return tokenId Identifier of the minted token - function _mint(address owner) internal virtual returns (uint256 tokenId) { - tokenId = _emittedCount; - _mintUnsafe(owner, tokenId, true); - emit Minted(owner, tokenId); - _emittedCount += 1; - } - - /// @notice Mint a given tokenId - /// @param owner Address for whom to assign the token - /// @param tokenId Token identifier to assign to the owner - /// @param valid Boolean to assert of the validity of the token - function _mintUnsafe(address owner, uint256 tokenId, bool valid) internal { - require(_tokens[tokenId].owner == address(0), "Cannot mint an assigned token"); - if (_indexedTokenIds[owner].length == 0) { - _holdersCount += 1; - } - _tokens[tokenId] = Token(msg.sender, owner, valid); - _tokenIdIndex[owner][tokenId] = _indexedTokenIds[owner].length; - _indexedTokenIds[owner].push(tokenId); - if (valid) { - _numberOfValidTokens[owner] += 1; - } - } - - /// @return True if the caller is the contract's creator, false otherwise - function _isCreator() internal view virtual returns (bool) { - return msg.sender == _creator; - } - - /// @notice Retrieve a token or revert if it does not exist - /// @param tokenId Identifier of the token - /// @return The Token struct - function _getTokenOrRevert(uint256 tokenId) internal view virtual returns (Token storage) { - Token storage token = _tokens[tokenId]; - require(token.owner != address(0), "Token does not exist"); - return token; - } - - /// @notice Remove a token - /// @param tokenId Token identifier to remove - function _removeToken(uint256 tokenId) internal virtual { - Token storage token = _getTokenOrRevert(tokenId); - _removeFromUnorderedArray(_indexedTokenIds[token.owner], _tokenIdIndex[token.owner][tokenId]); - if (_indexedTokenIds[token.owner].length == 0) { - assert(_holdersCount > 0); - _holdersCount -= 1; - } - if (token.valid) { - assert(_numberOfValidTokens[token.owner] > 0); - _numberOfValidTokens[token.owner] -= 1; - } - delete _tokens[tokenId]; - } - - /// @notice Removes an entry in an array by its index - /// @param array Array for which to remove the entry - /// @param index Index of the entry to remove - function _removeFromUnorderedArray(uint256[] storage array, uint256 index) internal { - require(index < array.length, "Trying to delete out of bound index"); - if (index != array.length - 1) { - array[index] = array[array.length - 1]; - } - array.pop(); - } -} diff --git a/assets/eip-4671/ERC4671Consensus.sol b/assets/eip-4671/ERC4671Consensus.sol deleted file mode 100644 index bc3f1fcde62dcb..00000000000000 --- a/assets/eip-4671/ERC4671Consensus.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -import "./ERC4671.sol"; -import "./IERC4671Consensus.sol"; - -contract ERC4671Consensus is ERC4671, IERC4671Consensus { - // Consensus voters addresses - mapping(address => bool) private _voters; - address[] private _votersArray; - - // Mapping from voter to mint approvals - mapping(address => mapping(address => bool)) private _mintApprovals; - - // Mapping from owner to approval counts - mapping(address => uint256) private _mintApprovalCounts; - - // Mapping from voter to revoke approvals - mapping(address => mapping(uint256 => bool)) private _revokeApprovals; - - // Mapping from tokenId to revoke counts - mapping(uint256 => uint256) private _revokeApprovalCounts; - - constructor (string memory name_, string memory symbol_, address[] memory voters_) ERC4671(name_, symbol_) { - _votersArray = voters_; - for (uint256 i=0; i mapping(address => bool)) _allowed; - - /// @notice Grant one-time minting right to `operator` for `owner` - /// An allowed operator can call the function to transfer rights. - /// @param operator Address allowed to mint a token - /// @param owner Address for whom `operator` is allowed to mint a token - function delegate(address operator, address owner) public virtual override { - _delegateAsDelegateOrCreator(operator, owner, _isCreator()); - } - - /// @notice Grant one-time minting right to a list of `operators` for a corresponding list of `owners` - /// An allowed operator can call the function to transfer rights. - /// @param operators Addresses allowed to mint a token - /// @param owners Addresses for whom `operators` are allowed to mint a token - function delegateBatch(address[] memory operators, address[] memory owners) public virtual override { - require(operators.length == owners.length, "operators and owners must have the same length"); - bool isCreator = _isCreator(); - for (uint i=0; i address[]) private _records; - - // Mapping from owner to IERC4671Enumerable contract index - mapping(address => mapping(address => uint256)) _indices; - - /// @notice Add a IERC4671Enumerable contract address to the caller's record - /// @param token Address of the IERC4671Enumerable contract to add - function add(address token) public virtual override { - address[] storage contracts = _records[msg.sender]; - _indices[msg.sender][token] = contracts.length; - contracts.push(token); - emit Added(msg.sender, token); - } - - /// @notice Remove a IERC4671Enumerable contract from the caller's record - /// @param token Address of the IERC4671Enumerable contract to remove - function remove(address token) public virtual override { - uint256 index = _indexOfTokenOrRevert(msg.sender, token); - address[] storage contracts = _records[msg.sender]; - if (index == contracts.length - 1) { - _indices[msg.sender][token] = 0; - } else { - _indices[msg.sender][contracts[contracts.length - 1]] = index; - } - contracts[index] = contracts[contracts.length - 1]; - contracts.pop(); - emit Removed(msg.sender, token); - } - - /// @notice Get all the IERC4671Enumerable contracts for a given owner - /// @param owner Address for which to retrieve the IERC4671Enumerable contracts - function get(address owner) public view virtual override returns (address[] memory) { - return _records[owner]; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return - interfaceId == type(IERC4671Store).interfaceId || - super.supportsInterface(interfaceId); - } - - function _indexOfTokenOrRevert(address owner, address token) private view returns (uint256) { - uint256 index = _indices[owner][token]; - require(index > 0 || _records[owner].length > 0, "Address not found"); - return index; - } -} diff --git a/assets/eip-4671/IERC4671.sol b/assets/eip-4671/IERC4671.sol deleted file mode 100644 index 3b97b9d489c059..00000000000000 --- a/assets/eip-4671/IERC4671.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IERC4671 is IERC165 { - /// Event emitted when a token `tokenId` is minted for `owner` - event Minted(address owner, uint256 tokenId); - - /// Event emitted when token `tokenId` of `owner` is revoked - event Revoked(address owner, uint256 tokenId); - - /// @notice Count all tokens assigned to an owner - /// @param owner Address for whom to query the balance - /// @return Number of tokens owned by `owner` - function balanceOf(address owner) external view returns (uint256); - - /// @notice Get owner of a token - /// @param tokenId Identifier of the token - /// @return Address of the owner of `tokenId` - function ownerOf(uint256 tokenId) external view returns (address); - - /// @notice Check if a token hasn't been revoked - /// @param tokenId Identifier of the token - /// @return True if the token is valid, false otherwise - function isValid(uint256 tokenId) external view returns (bool); - - /// @notice Check if an address owns a valid token in the contract - /// @param owner Address for whom to check the ownership - /// @return True if `owner` has a valid token, false otherwise - function hasValid(address owner) external view returns (bool); -} diff --git a/assets/eip-4671/IERC4671Consensus.sol b/assets/eip-4671/IERC4671Consensus.sol deleted file mode 100644 index 81e8663c77b081..00000000000000 --- a/assets/eip-4671/IERC4671Consensus.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Consensus is IERC4671 { - /// @notice Get voters addresses for this consensus contract - /// @return Addresses of the voters - function voters() external view returns (address[] memory); - - /// @notice Cast a vote to mint a token for a specific address - /// @param owner Address for whom to mint the token - function approveMint(address owner) external; - - /// @notice Cast a vote to revoke a specific token - /// @param tokenId Identifier of the token to revoke - function approveRevoke(uint256 tokenId) external; -} diff --git a/assets/eip-4671/IERC4671Delegate.sol b/assets/eip-4671/IERC4671Delegate.sol deleted file mode 100644 index cef2c6857690d3..00000000000000 --- a/assets/eip-4671/IERC4671Delegate.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Delegate is IERC4671 { - /// @notice Grant one-time minting right to `operator` for `owner` - /// An allowed operator can call the function to transfer rights. - /// @param operator Address allowed to mint a token - /// @param owner Address for whom `operator` is allowed to mint a token - function delegate(address operator, address owner) external; - - /// @notice Grant one-time minting right to a list of `operators` for a corresponding list of `owners` - /// An allowed operator can call the function to transfer rights. - /// @param operators Addresses allowed to mint - /// @param owners Addresses for whom `operators` are allowed to mint a token - function delegateBatch(address[] memory operators, address[] memory owners) external; - - /// @notice Mint a token. Caller must have the right to mint for the owner. - /// @param owner Address for whom the token is minted - function mint(address owner) external; - - /// @notice Mint tokens to multiple addresses. Caller must have the right to mint for all owners. - /// @param owners Addresses for whom the tokens are minted - function mintBatch(address[] memory owners) external; - - /// @notice Get the issuer of a token - /// @param tokenId Identifier of the token - /// @return Address who minted `tokenId` - function issuerOf(uint256 tokenId) external view returns (address); -} diff --git a/assets/eip-4671/IERC4671Enumerable.sol b/assets/eip-4671/IERC4671Enumerable.sol deleted file mode 100644 index 99f0eb3d1b720c..00000000000000 --- a/assets/eip-4671/IERC4671Enumerable.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Enumerable is IERC4671 { - /// @return emittedCount Number of tokens emitted - function emittedCount() external view returns (uint256); - - /// @return holdersCount Number of token holders - function holdersCount() external view returns (uint256); - - /// @notice Get the tokenId of a token using its position in the owner's list - /// @param owner Address for whom to get the token - /// @param index Index of the token - /// @return tokenId of the token - function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256); - - /// @notice Get a tokenId by it's index, where 0 <= index < total() - /// @param index Index of the token - /// @return tokenId of the token - function tokenByIndex(uint256 index) external view returns (uint256); -} diff --git a/assets/eip-4671/IERC4671Metadata.sol b/assets/eip-4671/IERC4671Metadata.sol deleted file mode 100644 index adc2fa52ee1c19..00000000000000 --- a/assets/eip-4671/IERC4671Metadata.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Metadata is IERC4671 { - /// @return Descriptive name of the tokens in this contract - function name() external view returns (string memory); - - /// @return An abbreviated name of the tokens in this contract - function symbol() external view returns (string memory); - - /// @notice URI to query to get the token's metadata - /// @param tokenId Identifier of the token - /// @return URI for the token - function tokenURI(uint256 tokenId) external view returns (string memory); -} diff --git a/assets/eip-4671/IERC4671Pull.sol b/assets/eip-4671/IERC4671Pull.sol deleted file mode 100644 index c719273b9c834a..00000000000000 --- a/assets/eip-4671/IERC4671Pull.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4671.sol"; - -interface IERC4671Pull is IERC4671 { - /// @notice Pull a token from the owner wallet to the caller's wallet - /// @param tokenId Identifier of the token to transfer - /// @param owner Address that owns tokenId - /// @param signature Signed data (tokenId, owner, recipient) by the owner of the token - function pull(uint256 tokenId, address owner, bytes memory signature) external; -} diff --git a/assets/eip-4671/IERC4671Store.sol b/assets/eip-4671/IERC4671Store.sol deleted file mode 100644 index 601b1f1f0957ba..00000000000000 --- a/assets/eip-4671/IERC4671Store.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IERC4671Store is IERC165 { - // Event emitted when a IERC4671Enumerable contract is added to the owner's records - event Added(address owner, address token); - - // Event emitted when a IERC4671Enumerable contract is removed from the owner's records - event Removed(address owner, address token); - - /// @notice Add a IERC4671Enumerable contract address to the caller's record - /// @param token Address of the IERC4671Enumerable contract to add - function add(address token) external; - - /// @notice Remove a IERC4671Enumerable contract from the caller's record - /// @param token Address of the IERC4671Enumerable contract to remove - function remove(address token) external; - - /// @notice Get all the IERC4671Enumerable contracts for a given owner - /// @param owner Address for which to retrieve the IERC4671Enumerable contracts - function get(address owner) external view returns (address[] memory); -} diff --git a/assets/eip-4675/.gitignore b/assets/eip-4675/.gitignore deleted file mode 100644 index 40154673ca9576..00000000000000 --- a/assets/eip-4675/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Dependency directory -node_modules - -# hardhat -cache -artifacts - -# macos -.DS_Store - -# IntelliJ IDE -.idea \ No newline at end of file diff --git a/assets/eip-4675/.solcover.js b/assets/eip-4675/.solcover.js deleted file mode 100644 index e532fc1950c54d..00000000000000 --- a/assets/eip-4675/.solcover.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - skipFiles: ['contracts/ERC20Token.sol'] -}; \ No newline at end of file diff --git a/assets/eip-4675/README.md b/assets/eip-4675/README.md deleted file mode 100644 index 5a14c32b5a4977..00000000000000 --- a/assets/eip-4675/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Multi-Fractional Non-Fungible Token -Solidity Implementation of Multi-Fractional Non-Fungible Token. - -## Problem Trying to solve -Before, ERC20 Token contract should be deployed every time when fractionalizing a specific NFT. - -To solve this problem, this standard proposes a token standard to cover multiple fractionalized nft in a contract without having to deploy each time. - -Issue : https://github.com/ethereum/EIPs/issues/4674 - -PR : https://github.com/ethereum/EIPs/pull/4675 - -## How to use -``` -contracts/ - helper/ - interface/ - math/ - MFNFT.sol - NFT.sol - ERC20Token.sol -``` - -### Contracts -``MFNFT.sol`` : Multi-Fractional Non-Fungible Token Contract - -``NFT.sol`` : Non-Fungible Token Contract - -``ERC20Token.sol`` : Sample ERC-20 Token Contract - -``helper/Verifier.sol`` : Contract that verifies the ownership of NFT before fractionalization - -``math/SafeMath.sol`` : Openzeppelin SafeMath Library - -``interface/IERC20.sol`` : ERC-20 Token Interface - -``interface/IERC721.sol`` : ERC-721 Token Interface - -``interface/IMFNFT`` : MFNFT Token Interface - -### Install & Test - -Installation -``` -npm install -``` - -Test -``` -npx hardhat test -``` - -Coverage -``` -npx hardhat coverage -``` diff --git a/assets/eip-4675/contracts/ERC20Token.sol b/assets/eip-4675/contracts/ERC20Token.sol deleted file mode 100644 index 6b8f5faaccfb2c..00000000000000 --- a/assets/eip-4675/contracts/ERC20Token.sol +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interface/IERC20.sol"; -import "./math/SafeMath.sol"; - -/** - * @title Standard RFT(ERC1633) token extending ERC20 - * - * @dev Implementation of the basic standard token. - * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md - * Originally based on code by FirstBlood: - * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol - * - * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for - * all accounts just by listening to said events. Note that this isn't required by the specification, and other - * compliant implementations may not do it. - */ -contract FT is IERC20 { - using SafeMath for uint256; - - mapping(address => uint256) private _balances; - - mapping(address => mapping(address => uint256)) private _allowed; - - uint256 private _totalSupply; - - // NFT Contract Address - address private _parentToken; - - // NFT ID of NFT(RFT) - TokenId - uint256 private _parentTokenId; - - // Admin Address to Set the Parent NFT - address private _admin; - - constructor(uint256 total_supply) { - _totalSupply = total_supply; - _balances[msg.sender] = total_supply; - _admin = msg.sender; - } - - /** - * @dev Total number of tokens in existence - */ - function totalSupply() public view override returns (uint256) { - return _totalSupply; - } - - /** - * @dev Gets the balance of the specified address. - * @param owner The address to query the balance of. - * @return An uint256 representing the amount owned by the passed address. - */ - function balanceOf(address owner) public view override returns (uint256) { - return _balances[owner]; - } - - /** - * @dev Function to check the amount of tokens that an owner allowed to a spender. - * @param owner address The address which owns the funds. - * @param spender address The address which will spend the funds. - * @return A uint256 specifying the amount of tokens still available for the spender. - */ - function allowance(address owner, address spender) - public - view - override - returns (uint256) - { - return _allowed[owner][spender]; - } - - /** - * @dev Transfer token for a specified address - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function transfer(address to, uint256 value) - public - override - returns (bool) - { - _transfer(msg.sender, to, value); - return true; - } - - /** - * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. - * Beware that changing an allowance with this method brings the risk that someone may use both the old - * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this - * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * @param spender The address which will spend the funds. - * @param value The amount of tokens to be spent. - */ - function approve(address spender, uint256 value) - public - override - returns (bool) - { - require(spender != address(0)); - - _allowed[msg.sender][spender] = value; - emit Approval(msg.sender, spender, value); - return true; - } - - /** - * @dev Transfer tokens from one address to another. - * Note that while this function emits an Approval event, this is not required as per the specification, - * and other compliant implementations may not emit the event. - * @param from address The address which you want to send tokens from - * @param to address The address which you want to transfer to - * @param value uint256 the amount of tokens to be transferred - */ - function transferFrom( - address from, - address to, - uint256 value - ) public override returns (bool) { - _allowed[from][msg.sender] = _allowed[from][msg.sender].sub(value); - _transfer(from, to, value); - emit Approval(from, msg.sender, _allowed[from][msg.sender]); - return true; - } - - /** - * @dev Increase the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To increment - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param addedValue The amount of tokens to increase the allowance by. - */ - function increaseAllowance(address spender, uint256 addedValue) - public - returns (bool) - { - require(spender != address(0)); - - _allowed[msg.sender][spender] = _allowed[msg.sender][spender].add( - addedValue - ); - emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); - return true; - } - - /** - * @dev Decrease the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To decrement - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param subtractedValue The amount of tokens to decrease the allowance by. - */ - function decreaseAllowance(address spender, uint256 subtractedValue) - public - returns (bool) - { - require(spender != address(0)); - - _allowed[msg.sender][spender] = _allowed[msg.sender][spender].sub( - subtractedValue - ); - emit Approval(msg.sender, spender, _allowed[msg.sender][spender]); - return true; - } - - /** - * @dev Transfer token for a specified addresses - * @param from The address to transfer from. - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function _transfer( - address from, - address to, - uint256 value - ) internal { - require(to != address(0)); - - _balances[from] = _balances[from].sub(value); - _balances[to] = _balances[to].add(value); - emit Transfer(from, to, value); - } - - /** - * @dev Internal function that mints an amount of the token and assigns it to - * an account. This encapsulates the modification of balances such that the - * proper events are emitted. - * @param account The account that will receive the created tokens. - * @param value The amount that will be created. - */ - function _mint(address account, uint256 value) internal { - require(account != address(0)); - - _totalSupply = _totalSupply.add(value); - _balances[account] = _balances[account].add(value); - emit Transfer(address(0), account, value); - } - - /** - * @dev Internal function that burns an amount of the token of a given - * account. - * @param account The account whose tokens will be burnt. - * @param value The amount that will be burnt. - */ - function _burn(address account, uint256 value) internal { - require(account != address(0)); - - _totalSupply = _totalSupply.sub(value); - _balances[account] = _balances[account].sub(value); - emit Transfer(account, address(0), value); - } - - /** - * @dev Internal function that burns an amount of the token of a given - * account, deducting from the sender's allowance for said account. Uses the - * internal burn function. - * Emits an Approval event (reflecting the reduced allowance). - * @param account The account whose tokens will be burnt. - * @param value The amount that will be burnt. - */ - function _burnFrom(address account, uint256 value) internal { - _allowed[account][msg.sender] = _allowed[account][msg.sender].sub( - value - ); - _burn(account, value); - emit Approval(account, msg.sender, _allowed[account][msg.sender]); - } -} diff --git a/assets/eip-4675/contracts/MFNFT.sol b/assets/eip-4675/contracts/MFNFT.sol deleted file mode 100644 index 098b4afdcd1398..00000000000000 --- a/assets/eip-4675/contracts/MFNFT.sol +++ /dev/null @@ -1,346 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interface/IMFNFT.sol"; -import "./interface/IERC721.sol"; -import "./math/SafeMath.sol"; -import "./helper/Verifier.sol"; - -/** - * @title TWIG_FNFT Contract - */ -contract MFNFT is IMFNFT, Verifier { - using SafeMath for uint256; - - mapping(uint256 => mapping(address => uint256)) private _balances; - - mapping(uint256 => mapping(address => mapping(address => uint256))) - private _allowed; - - // uint256 private _totalSupply; - mapping(uint256 => uint256) _totalSupply; - - // NFT Contract Address - // address private _parentToken; - mapping(uint256 => address) _parentToken; - - // NFT ID of NFT(RFT) - TokenId - // uint256 private _parentTokenId; - mapping(uint256 => uint256) _parentTokenId; - - // - mapping(address => mapping(uint256 => uint256)) private _Ids; - - // Scalar value to distinguish fractionalized NFT - uint256 public _id; - - // Admin Address to Set the Parent NFT - address private _admin; - - // Event emitted when token is added - event TokenAddition( - address indexed token, - uint256 tokenId, - uint256 _id, - uint256 totalSupply - ); - - constructor() { - _admin = msg.sender; - } - - /** - * @dev onlyAdmin prohibits function calls arbitrary msg.sender - * except _admin - */ - modifier onlyAdmin() { - require(msg.sender == _admin); - _; - } - - /** - * @dev Mandatory function to receive NFT as a contract(CA) - * @return Bytes4 which is the selector of this function - */ - function onERC721Received( - address _operator, - address _from, - uint256 _tokenId, - bytes calldata _data - ) external pure returns (bytes4) { - return this.onERC721Received.selector; - } - - /** - * @dev (ERC165) Determines if this contract supports Re-FT(ERC1633). - * @param interfaceID The bytes4 to query if it matches with the contract interface id. - */ - function supportsInterface(bytes4 interfaceID) - external - pure - returns (bool) - { - return - interfaceID == this.supportsInterface.selector || // ERC165 - interfaceID == this.parentToken.selector || // parentToken() - interfaceID == this.parentTokenId.selector || // parentTokenId() - interfaceID == - this.parentToken.selector ^ this.parentTokenId.selector; // RFT - } - - /** - * @dev Sets the Address of NFT Contract Address & NFT Token ID - * @param parentNFTContractAddress The address NFT Contract address. - * @param parentNFTTokenId The token id of NFT. - */ - function setParentNFT( - address parentNFTContractAddress, - uint256 parentNFTTokenId, - uint256 totalSupply - ) public onlyAdmin { - require( - parentNFTContractAddress != address(0), - "MFNFT::setParentNFT: Parent NFT Contract should not be zero" - ); - require( - getTokenId(parentNFTContractAddress, parentNFTTokenId) == 0, - "MFNFT::setParentNFT: Already owned(fractionalized) by this contract" - ); - - verifyOwnership(parentNFTContractAddress, parentNFTTokenId); - - _id++; - - _Ids[parentNFTContractAddress][parentNFTTokenId] = _id; - - _parentToken[_id] = parentNFTContractAddress; - _parentTokenId[_id] = parentNFTTokenId; - - _totalSupply[_id] = totalSupply; - _balances[_id][msg.sender] = totalSupply; - - emit TokenAddition( - parentNFTContractAddress, - parentNFTTokenId, - _id, - totalSupply - ); - } - - /** - * @dev Returns the tokenId of with the given NFT information - * @return An uint256 value representing the tokenId of given NFT - */ - function getTokenId(address token, uint256 tokenId) - public - view - returns (uint256) - { - return _Ids[token][tokenId]; - } - - /** - * @dev Returns if the NFT is owned(fractionalized) by this contract. - * @return An bool representing whether the NFT is fractionalized by this contract - */ - function isRegistered(address token, uint256 tokenId) public view returns (bool) { - return (_Ids[token][tokenId] != 0); - } - - /** - * @dev Returns the Address of Parent Token Address - * @return An Address representing the address of NFT Contract this Re-FT is pointing to. - */ - function parentToken(uint256 tokenId) external view returns (address) { - return _parentToken[tokenId]; - } - - /** - * @dev Returns the Token ID of NFT - * @return An uint256 representing the token id of the NFT this Re-FT is pointing to. - */ - function parentTokenId(uint256 tokenId) external view returns (uint256) { - return _parentTokenId[tokenId]; - } - - /** - * @dev Total number of tokens in existence - */ - function totalSupply(uint256 tokenId) - public - view - override - returns (uint256) - { - return _totalSupply[tokenId]; - } - - /** - * @dev Gets the balance of the specified address. - * @param owner The address to query the balance of. - * @return An uint256 representing the amount owned by the passed address. - */ - function balanceOf(address owner, uint256 tokenId) - public - view - override - returns (uint256) - { - return _balances[tokenId][owner]; - } - - /** - * @dev Function to check the amount of tokens that an owner allowed to a spender. - * @param owner address The address which owns the funds. - * @param spender address The address which will spend the funds. - * @return A uint256 specifying the amount of tokens still available for the spender. - */ - function allowance( - address owner, - address spender, - uint256 tokenId - ) public view override returns (uint256) { - return _allowed[tokenId][owner][spender]; - } - - /** - * @dev Transfer token for a specified address - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function transfer( - address to, - uint256 tokenId, - uint256 value - ) public override returns (bool) { - _transfer(msg.sender, to, tokenId, value); - return true; - } - - /** - * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. - * Beware that changing an allowance with this method brings the risk that someone may use both the old - * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this - * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: - * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 - * @param spender The address which will spend the funds. - * @param value The amount of tokens to be spent. - */ - function approve( - address spender, - uint256 tokenId, - uint256 value - ) public override returns (bool) { - require(spender != address(0)); - - _allowed[tokenId][msg.sender][spender] = value; - emit Approval(msg.sender, spender, tokenId, value); - return true; - } - - /** - * @dev Transfer tokens from one address to another. - * Note that while this function emits an Approval event, this is not required as per the specification, - * and other compliant implementations may not emit the event. - * @param from address The address which you want to send tokens from - * @param to address The address which you want to transfer to - * @param value uint256 the amount of tokens to be transferred - */ - function transferFrom( - address from, - address to, - uint256 tokenId, - uint256 value - ) public override returns (bool) { - _allowed[tokenId][from][msg.sender] = _allowed[tokenId][from][ - msg.sender - ].sub(value); - _transfer(from, to, tokenId, value); - emit Approval( - from, - msg.sender, - tokenId, - _allowed[tokenId][from][msg.sender] - ); - return true; - } - - /** - * @dev Increase the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To increment - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param addedValue The amount of tokens to increase the allowance by. - */ - function increaseAllowance( - address spender, - uint256 tokenId, - uint256 addedValue - ) public returns (bool) { - require(spender != address(0)); - - _allowed[tokenId][msg.sender][spender] = _allowed[tokenId][msg.sender][ - spender - ].add(addedValue); - - emit Approval( - msg.sender, - spender, - tokenId, - _allowed[tokenId][msg.sender][spender] - ); - return true; - } - - /** - * @dev Decrease the amount of tokens that an owner allowed to a spender. - * approve should be called when allowed_[_spender] == 0. To decrement - * allowed value is better to use this function to avoid 2 calls (and wait until - * the first transaction is mined) - * From MonolithDAO Token.sol - * Emits an Approval event. - * @param spender The address which will spend the funds. - * @param subtractedValue The amount of tokens to decrease the allowance by. - */ - function decreaseAllowance( - address spender, - uint256 tokenId, - uint256 subtractedValue - ) public returns (bool) { - require(spender != address(0)); - - _allowed[tokenId][msg.sender][spender] = _allowed[tokenId][msg.sender][ - spender - ].sub(subtractedValue); - emit Approval( - msg.sender, - spender, - tokenId, - _allowed[tokenId][msg.sender][spender] - ); - return true; - } - - /** - * @dev Transfer token for a specified addresses - * @param from The address to transfer from. - * @param to The address to transfer to. - * @param value The amount to be transferred. - */ - function _transfer( - address from, - address to, - uint256 tokenId, - uint256 value - ) internal { - require(to != address(0)); - - _balances[tokenId][from] = _balances[tokenId][from].sub(value); - _balances[tokenId][to] = _balances[tokenId][to].add(value); - - emit Transfer(from, to, tokenId, value); - } -} diff --git a/assets/eip-4675/contracts/NFT.sol b/assets/eip-4675/contracts/NFT.sol deleted file mode 100644 index 2dd6edbf96e296..00000000000000 --- a/assets/eip-4675/contracts/NFT.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -contract NFT is ERC721, ERC721Enumerable, ERC721Burnable, Ownable { - constructor() ERC721("MyToken", "MTK") {} - - function safeMint(address to, uint256 tokenId) public onlyOwner { - _safeMint(to, tokenId); - } - - function _baseURI() internal pure override returns (string memory) { - return "Test"; - } - - function _beforeTokenTransfer(address from, address to, uint256 tokenId) - internal - override(ERC721, ERC721Enumerable) - { - super._beforeTokenTransfer(from, to, tokenId); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(ERC721, ERC721Enumerable) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/helper/Verifier.sol b/assets/eip-4675/contracts/helper/Verifier.sol deleted file mode 100644 index de3af4c1cbf863..00000000000000 --- a/assets/eip-4675/contracts/helper/Verifier.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -contract Verifier { - - function verifyOwnership( - address token, - uint256 tokenId - ) internal { - // bytes(keccak256(bytes('ownerOf(uint256)'))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x6352211e, tokenId)); - require( - success && (data.length == 32 && bytesToAddress(data) == address(this)), - "Verifier::verifyOwnership: NFT ownership verification failed" - ); - } - - function getOwner( - address token, - uint256 tokenId - ) internal returns(address) { - // bytes(keccak256(bytes('ownerOf(uint256)'))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x6352211e, tokenId)); - require( - success && (data.length == 32), - "Verifier::getOwner: NFT ownership verification failed" - ); - return bytesToAddress(data); - } - - function bytesToAddress(bytes memory bys) internal pure returns (address addr) { - assembly { - addr := mload(add(bys,32)) - } - } -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/interface/IERC20.sol b/assets/eip-4675/contracts/interface/IERC20.sol deleted file mode 100644 index 912040eceb0249..00000000000000 --- a/assets/eip-4675/contracts/interface/IERC20.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/** - * @title ERC20 interface - * @dev see https://github.com/ethereum/EIPs/issues/20 - */ -interface IERC20 { - function transfer(address to, uint256 value) external returns (bool); - - function approve(address spender, uint256 value) external returns (bool); - - function transferFrom(address from, address to, uint256 value) external returns (bool); - - function totalSupply() external view returns (uint256); - - function balanceOf(address who) external view returns (uint256); - - function allowance(address owner, address spender) external view returns (uint256); - - event Transfer(address indexed from, address indexed to, uint256 value); - - event Approval(address indexed owner, address indexed spender, uint256 value); -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/interface/IERC721.sol b/assets/eip-4675/contracts/interface/IERC721.sol deleted file mode 100644 index ecc6976e6c5ba5..00000000000000 --- a/assets/eip-4675/contracts/interface/IERC721.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/** - * Contract that exposes the needed erc20 token functions - */ - -abstract contract IERC721 { - - event Transfer( - address indexed from, - address indexed to, - uint256 indexed tokenaId - ); - - event Approval( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - event ApprovalForAll( - address indexed owner, - address indexed operator, - bool approved - ); - - function balanceOf(address owner) public virtual view returns (uint256 balance); - function ownerOf(uint256 tokenId) public virtual view returns (address owner); - - function approve(address to, uint256 tokenId) public virtual; - function getApproved(uint256 tokenId) - public virtual view returns (address operator); - - function setApprovalForAll(address operator, bool _approved) public virtual; - function isApprovedForAll(address owner, address operator) - public virtual view returns (bool); - - function transferFrom(address from, address to, uint256 tokenId) public virtual; - function safeTransferFrom(address from, address to, uint256 tokenId) public virtual; - - -} \ No newline at end of file diff --git a/assets/eip-4675/contracts/interface/IMFNFT.sol b/assets/eip-4675/contracts/interface/IMFNFT.sol deleted file mode 100644 index 1c36139e2305f7..00000000000000 --- a/assets/eip-4675/contracts/interface/IMFNFT.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IMFNFT { - function transfer(address to, uint256 tokenId, uint256 value) external returns (bool); - - function approve(address spender, uint256 tokenId, uint256 value) external returns (bool); - - function transferFrom(address from, address to, uint256 tokenId, uint256 value) external returns (bool); - - function totalSupply(uint256 tokenId) external view returns (uint256); - - function balanceOf(address who, uint256 tokenId) external view returns (uint256); - - function allowance(address owner, address spender, uint256 tokenId) external view returns (uint256); - - event Transfer(address indexed from, address indexed to, uint256 tokenId, uint256 value); - - event Approval(address indexed owner, address indexed spender, uint256 tokenId, uint256 value); -} \ No newline at end of file diff --git a/assets/eip-4675/hardhat.config.ts b/assets/eip-4675/hardhat.config.ts deleted file mode 100644 index 044fd60f2c7972..00000000000000 --- a/assets/eip-4675/hardhat.config.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { task } from "hardhat/config"; -import "@nomiclabs/hardhat-waffle"; -import "solidity-coverage"; -import "hardhat-deploy"; -import dotenv from "dotenv"; -import type { HardhatUserConfig, HttpNetworkUserConfig } from "hardhat/types"; - - - -// Load environment variables. -dotenv.config(); -const { NODE_URL, INFURA_KEY, MNEMONIC, ETHERSCAN_API_KEY, PK, SOLIDITY_VERSION, SOLIDITY_SETTINGS } = process.env; - -// Test mnemonic -const DEFAULT_MNEMONIC = - "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat"; - -const sharedNetworkConfig: HttpNetworkUserConfig = {}; -if (PK) { - sharedNetworkConfig.accounts = [PK]; -} else { - sharedNetworkConfig.accounts = { - mnemonic: MNEMONIC || DEFAULT_MNEMONIC, - }; -} - -const primarySolidityVersion = SOLIDITY_VERSION || "0.8.0" - - -const userConfig: HardhatUserConfig = { - paths: { - artifacts: "artifacts", - cache: "cache", - deploy: "src/deploy", - sources: "contracts", - }, - solidity: { - compilers: [ - { version: primarySolidityVersion }, - { version: "0.6.12" }, - { version: "0.5.17" }, - { version: "0.7.5" }, - ] - }, - networks: { - hardhat: { - allowUnlimitedContractSize: true, - blockGasLimit: 100000000, - gas: 100000000 - }, - mainnet: { - ...sharedNetworkConfig, - url: `https://mainnet.infura.io/v3/${INFURA_KEY}`, - }, - xdai: { - ...sharedNetworkConfig, - url: "https://xdai.poanetwork.dev", - }, - ewc: { - ...sharedNetworkConfig, - url: `https://rpc.energyweb.org`, - }, - rinkeby: { - ...sharedNetworkConfig, - url: `https://rinkeby.infura.io/v3/${INFURA_KEY}`, - }, - goerli: { - ...sharedNetworkConfig, - url: `https://goerli.infura.io/v3/${INFURA_KEY}`, - }, - kovan: { - ...sharedNetworkConfig, - url: `https://kovan.infura.io/v3/${INFURA_KEY}`, - }, - volta: { - ...sharedNetworkConfig, - url: `https://volta-rpc.energyweb.org`, - }, - }, - namedAccounts: { - deployer: 0, - }, - mocha: { - timeout: 2000000, - }, - etherscan: { - apiKey: ETHERSCAN_API_KEY, - }, -} - -/** - * @type import('hardhat/config').HardhatUserConfig - */ -export default userConfig diff --git a/assets/eip-4675/package.json b/assets/eip-4675/package.json deleted file mode 100644 index e5728bfe3b3f91..00000000000000 --- a/assets/eip-4675/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "multi-fnft", - "version": "1.0.0", - "description": "Solidity Implementation of Multi-Fractional Non Fungible Token", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/PowerStream3604/multi-fnft.git" - }, - "keywords": [], - "author": "", - "license": "CC0-1.0", - "bugs": { - "url": "https://github.com/PowerStream3604/multi-fnft/issues" - }, - "homepage": "https://github.com/PowerStream3604/multi-fnft#readme", - "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.4", - "@nomiclabs/hardhat-waffle": "^2.0.1", - "@openzeppelin/contracts": "^4.4.1", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.0.0", - "@types/node": "^17.0.8", - "@types/yargs": "^17.0.8", - "chai": "^4.3.4", - "ethereum-waffle": "^3.4.0", - "ethers": "^5.5.3", - "hardhat": "^2.8.2", - "hardhat-deploy": "^0.9.24", - "solc": "^0.8.11", - "solidity-coverage": "^0.7.17", - "ts-node": "^10.4.0", - "typescript": "^4.5.4", - "yargs": "^17.3.1" - } -} diff --git a/assets/eip-4675/src/deploy/deploy_contracts.ts b/assets/eip-4675/src/deploy/deploy_contracts.ts deleted file mode 100644 index 297da1ac3f0b6e..00000000000000 --- a/assets/eip-4675/src/deploy/deploy_contracts.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { DeployFunction } from "hardhat-deploy/types"; -import { HardhatRuntimeEnvironment } from "hardhat/types"; - -const deploy: DeployFunction = async function ( - hre: HardhatRuntimeEnvironment, -) { - const { deployments, hardhatArguments, getNamedAccounts } = hre; - const { deployer } = await getNamedAccounts(); - const { deploy } = deployments; - - await deploy("MFNFT", { - from: deployer, - args: [], - log: true, - deterministicDeployment: true, - }); - - await deploy("NFT", { - from: deployer, - args: [], - log: true, - deterministicDeployment: true, - }); - -}; - -deploy.tags = ['contracts', 'MFNFT', 'NFT'] -export default deploy; diff --git a/assets/eip-4675/test/MFNFT.General.spec.ts b/assets/eip-4675/test/MFNFT.General.spec.ts deleted file mode 100644 index 25ab613bd2e37b..00000000000000 --- a/assets/eip-4675/test/MFNFT.General.spec.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { expect } from "chai"; -import hre, { deployments, ethers, waffle } from "hardhat"; -import { BigNumber } from "ethers"; -import { AddressZero } from "@ethersproject/constants"; -import { parseEther } from "@ethersproject/units"; -import { setMFNFTwithNFT, deployFTContract, getMFContract, getNFTContract, mintNFT } from "./utils/setup"; -import { transferFrom ,balanceOf, transfer, safeTransferFrom, addToken, approve, increaseAllowance, decreaseAllowance } from "./utils/execution"; - - -describe("Multi-Fractional Non-Fungible Token", async () => { - - const [admin, user1, user2, user3] = waffle.provider.getWallets(); - - // Scalar variable that gets incremented when token is added to MFNFT - const scalar_tokenId = 1; - - // token ID of the NFT - const tokenId = 1; - - // total supply of FT derived from NFT - const totalSupply = 1000; - - const setupTests = deployments.createFixture(async ({deployments}) => { - await deployments.fixture(); - return { - MFNFT: await getMFContract(), - NFT: await getNFTContract(), - } - }); - - describe("NFT Ownership", async () => { - it("should revert if NFT ownership is not given before token addition", async () => { - const { MFNFT, NFT } = await setupTests() - - await NFT.safeMint(user1.address, tokenId); - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply) - ).to.be.revertedWith("Verifier::verifyOwnership: NFT ownership verification failed") - }); - - it("should accept NFT after taking the ownership", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId); - - await addToken(MFNFT, NFT.address, tokenId, totalSupply) - }); - - it("should emit event for token addition", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId); - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply) - ).to.emit(MFNFT, "TokenAddition").withArgs(NFT.address, tokenId, 1, totalSupply) - }); - - it("should revert if given parentNFTContractAddress is zero", async () => { - const { MFNFT } = await setupTests() - - await expect( - addToken(MFNFT, AddressZero, tokenId, totalSupply) - ).to.be.revertedWith("MFNFT::setParentNFT: Parent NFT Contract should not be zero") - }); - - it("should revert if given parentNFTContractAddress doesn't support ERC-721", async() => { - const { MFNFT } = await setupTests(); - const FT = await deployFTContract(totalSupply); - - await expect ( - addToken(MFNFT, FT.address, tokenId, totalSupply) - ).to.be.reverted - }); - - it("should revert if setParentNFT() is called twice for the same NFT", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply); - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply) - ).to.be.revertedWith("MFNFT::setParentNFT: Already owned(fractionalized) by this contract") - }); - - it("should return true if NFT is owned & registered by MNFTContract -> isRegistered()", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply); - - expect(await MFNFT.isRegistered(NFT.address, tokenId)).to.be.eq(true) - }); - - it("should check if parentTokenContractAddress is set right", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - expect(await MFNFT.parentToken(scalar_tokenId)).to.be.eq(NFT.address) - }); - - it("should check if parentTokenId is set right", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - expect(await MFNFT.parentTokenId(scalar_tokenId)).to.be.eq(tokenId) - }); - - it("should check if totalSupply complys with designated value", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - expect(await MFNFT.totalSupply(scalar_tokenId)).to.be.equal(totalSupply) - }); - it("should check if _id is a scalar value that increases when token is added", async () => { - const { MFNFT, NFT } = await setupTests() - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - const _id = await MFNFT.getTokenId(NFT.address, tokenId); - - await setMFNFTwithNFT(MFNFT, NFT, tokenId+1, totalSupply) - - expect(await MFNFT.getTokenId(NFT.address, tokenId+1)).to.be.equal(BigNumber.from(_id).add(1)); - }); - - }); - - describe("Admin", async () => { - - it("should check if admin can add new token", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId) - - await addToken(MFNFT, NFT.address, tokenId, totalSupply, {from: admin}); - }); - - it("should revert if non-admin tries to add token", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, MFNFT.address, tokenId) - - await expect( - addToken(MFNFT, NFT.address, tokenId, totalSupply, {from: user1}) - ).to.be.reverted - }); - - }); - - describe("onERC721Received", async () => { - - it("should be able to accept ERC-721 token with safeTransferFrom()", async () => { - const { MFNFT, NFT } = await setupTests() - - await mintNFT(NFT, admin.address, tokenId) - - expect( - await safeTransferFrom(NFT, admin.address, MFNFT.address, tokenId) - ).to.emit(NFT, "Transfer").withArgs(admin.address, MFNFT.address, tokenId) - - expect(await NFT.ownerOf(tokenId)).to.be.equal(MFNFT.address) - }); - - it("should return expected value for onERC721Received()", async () => { - const { MFNFT } = await setupTests() - expect(await MFNFT.onERC721Received(admin.address, user1.address, tokenId, 0x0)).to.be.equal("0x150b7a02") - }); - - it("should return true if supportsInterface() receives supporting interface ID", async () => { - const { MFNFT } = await setupTests() - expect(await MFNFT.supportsInterface(0x01ffc9a7)).to.be.equal(true) - }) - - }); - - describe("Transfer & Allowance", async () => { - - const approvedValue = 100; - - it("should transfer exact amount of share to recipient", async () => { - const { MFNFT, NFT } = await setupTests() - - const transferAmount = 100; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - transfer(MFNFT, user1.address, scalar_tokenId, transferAmount) - ).to.emit(MFNFT, "Transfer").withArgs(admin.address, user1.address, scalar_tokenId, transferAmount) - - expect(await balanceOf(MFNFT, user1.address, scalar_tokenId)).to.be.equal(transferAmount) - }); - - it("should not be able to transfer more than balance", async () => { - const { MFNFT, NFT } = await setupTests() - - const transferAmount = 100; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - transfer(MFNFT, user1.address, scalar_tokenId, transferAmount + totalSupply) - ).to.be.reverted - }); - - it("should revert when trying to transfer to address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const transferAmount = 100; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - transfer(MFNFT, AddressZero, scalar_tokenId, transferAmount) - ).to.be.reverted - }); - - it("should check if approved user can spend on behalf", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - transferFrom(MFNFT, admin.address, user2.address, scalar_tokenId, approvedValue, {from: spender}) - ).to.emit(MFNFT, "Transfer").withArgs(admin.address, user2.address, scalar_tokenId, approvedValue) - }); - - it("should revert if user tries to approve address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = AddressZero; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender, scalar_tokenId, approvedValue) - ).to.be.reverted - }); - - it("should revert if user tries to use over approved amount", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - transferFrom(MFNFT, admin.address, user2.address, scalar_tokenId, approvedValue+100, {from: spender}) - ).to.be.reverted - }); - - it("should be able to increase allowance", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - increaseAllowance(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue * 2) - - expect(await MFNFT.allowance(admin.address, spender.address, tokenId)).to.be.equal(approvedValue * 2) - }); - - it("should revert if user tries to increase allowance for address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = AddressZero; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - increaseAllowance(MFNFT, AddressZero, scalar_tokenId, approvedValue * 2) - ).to.be.reverted - - expect(await MFNFT.allowance(admin.address, AddressZero, tokenId)).to.be.equal(0) - }) - - it("should be able to decrease allowance", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - decreaseAllowance(MFNFT, spender.address, scalar_tokenId, approvedValue / 2) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue / 2) - - expect(await MFNFT.allowance(admin.address, spender.address, tokenId)).to.be.equal(approvedValue / 2) - }); - - it("should revert if user tries to decrease allowance more than approved", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = user1; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - approve(MFNFT, spender.address, scalar_tokenId, approvedValue) - ).to.emit(MFNFT, "Approval").withArgs(admin.address, spender.address, scalar_tokenId, approvedValue) - - await expect( - decreaseAllowance(MFNFT, spender.address, scalar_tokenId, approvedValue * 2) - ).to.be.reverted - - expect(await MFNFT.allowance(admin.address, spender.address, tokenId)).to.be.equal(approvedValue) - }) - - it("should revert if user tries to decrease allowance for address zero", async () => { - const { MFNFT, NFT } = await setupTests() - - const spender = AddressZero; - - await setMFNFTwithNFT(MFNFT, NFT, tokenId, totalSupply) - - await expect( - decreaseAllowance(MFNFT, AddressZero, scalar_tokenId, approvedValue) - ).to.be.reverted - - expect(await MFNFT.allowance(admin.address, AddressZero, tokenId)).to.be.equal(0) - }) - }); - -}); \ No newline at end of file diff --git a/assets/eip-4675/test/utils/execution.ts b/assets/eip-4675/test/utils/execution.ts deleted file mode 100644 index 2d2a848b369d80..00000000000000 --- a/assets/eip-4675/test/utils/execution.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Contract, Wallet, utils, BigNumber, BigNumberish, Signer, PopulatedTransaction } from "ethers" -import { TypedDataSigner } from "@ethersproject/abstract-signer"; -import { AddressZero } from "@ethersproject/constants"; - -interface transOption { - from: string; - gas: BigInteger -} - -export const addToken = async (MFNFT: Contract, token: string, tokenId: BigNumberish, totalSupply: BigNumberish, options: any = {}): Promise => { - const tAddr = token || AddressZero; - const tId = tokenId || 1; - const tSupply = totalSupply || 1000; - - options.from == null ? options = null : (MFNFT = MFNFT.connect(options.from), options = null); - - return MFNFT.setParentNFT(tAddr, tId, tSupply, options); -} - - -export const safeTransferFrom = async (NFT: Contract, from: string, to: string, tokenId: BigNumberish) => { - return await NFT["safeTransferFrom(address,address,uint256)"](from, to, tokenId) -} - -export const transfer = async (MFNFT: Contract, to: string, _id: BigNumberish, value: BigNumberish, options: any = {}) => { - options.from == null ? options = null : (MFNFT = MFNFT.connect(options.from), options = null); - - return await MFNFT.transfer(to, _id, value) -} - -export const transferFrom = async (MFNFT: Contract, from: string, to: string, _id: BigNumberish, value: BigNumberish, options: any = {}) => { - options.from == null ? options = null : (MFNFT = MFNFT.connect(options.from), options = null); - - return await MFNFT.transferFrom(from, to, _id, value) -} - -export const balanceOf = async (MFNFT: Contract, owner: string, _id: BigNumberish) => { - return await MFNFT.balanceOf(owner, _id) -} - -export const approve = async (MFNFT: Contract, spender: string, tokenId: BigNumberish, value: BigNumberish) => { - return await MFNFT.approve(spender, tokenId, value) -} - -export const increaseAllowance = async (MFNFT: Contract, spender: string, tokenId: BigNumberish, value: BigNumberish) => { - return await MFNFT.increaseAllowance(spender, tokenId, value) -} - -export const decreaseAllowance = async (MFNFT: Contract, spender: string, tokenId: BigNumberish, value: BigNumberish) => { - return await MFNFT.decreaseAllowance(spender, tokenId, value) -} \ No newline at end of file diff --git a/assets/eip-4675/test/utils/setup.ts b/assets/eip-4675/test/utils/setup.ts deleted file mode 100644 index 957e517db2a7e0..00000000000000 --- a/assets/eip-4675/test/utils/setup.ts +++ /dev/null @@ -1,49 +0,0 @@ -import hre, { deployments } from "hardhat" -import { Contract, Wallet, utils, BigNumber, BigNumberish, Signer, PopulatedTransaction } from "ethers" -import { AddressZero } from "@ethersproject/constants"; -import { formatFixed, parseFixed } from "@ethersproject/bignumber"; -import { addToken } from "./execution"; -import { Address } from "cluster"; - -const MFContract = () => { - return "MFNFT"; -} - -export const getMFContract = async () => { - // const MFDeployment = await deployments.get(MFContract()); - const MF = await hre.ethers.getContractFactory(MFContract()); - // return MF.attach(MFDeployment.address); - return MF.deploy(); -} - -export const getNFTContract = async () => { - // const NFTDeployment = await deployments.get("NFT"); - const NFT = await hre.ethers.getContractFactory("NFT"); - // return NFT.attach(NFTDeployment.address); - return NFT.deploy(); -} - -export const deployFTContract = async (totalSupply: BigNumberish) => { - const tSupply = totalSupply || 1000; - const FT = await hre.ethers.getContractFactory("FT"); - return FT.deploy(tSupply); -} - -export const mintNFT = async (token: Contract, tokenOwner: string, tokenId:BigNumberish) => { - const NFT = token; - const tId = tokenId || 1; - const tOwner = tokenOwner || AddressZero; - return await NFT.safeMint(tOwner, tId); -} - -export const ownerOf = async (token: Contract, tokenId:BigNumberish) => { - const NFT = token; - const tId = tokenId || 1; - return await NFT.ownerOf(tId); -} - -export const setMFNFTwithNFT = async (MFNFT: Contract, NFT: Contract, tokenId: BigNumberish, totalSupply: BigNumberish) => { - await mintNFT(NFT, MFNFT.address, tokenId); - await addToken(MFNFT, NFT.address, tokenId, totalSupply); -} - diff --git a/assets/eip-4675/tsconfig.json b/assets/eip-4675/tsconfig.json deleted file mode 100644 index 8125850d3d1804..00000000000000 --- a/assets/eip-4675/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "outDir": "dist" - }, - "include": ["./scripts", "./test"], - "files": ["./hardhat.config.ts"] -} \ No newline at end of file diff --git a/assets/eip-4824/daostar_context.jsonld b/assets/eip-4824/daostar_context.jsonld deleted file mode 100644 index d595fa6061bcf3..00000000000000 --- a/assets/eip-4824/daostar_context.jsonld +++ /dev/null @@ -1,6 +0,0 @@ -{ - "@context": { - "@vocab": "http://daostar.org/schemas/", - "type": "@type" - } -} diff --git a/assets/eip-4824/daostar_ontology.ttl b/assets/eip-4824/daostar_ontology.ttl deleted file mode 100644 index 70b9116fa62174..00000000000000 --- a/assets/eip-4824/daostar_ontology.ttl +++ /dev/null @@ -1,262 +0,0 @@ -@prefix : . -@prefix owl: . -@prefix rdf: . -@prefix xml: . -@prefix xsd: . -@prefix rdfs: . -@base . - - rdf:type owl:Ontology . - -################################################################# -# Object Properties -################################################################# - -### http:/daostar.org/schemas/activities - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:label "activities"@en . - - -### http:/daostar.org/schemas/calls - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:comment "list of calls, which a proposal proposes"^^xsd:string ; - rdfs:label "calls"@en . - - -### http:/daostar.org/schemas/from - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:label "from"@en . - - -### http:/daostar.org/schemas/member - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range , - , - . - - -### http:/daostar.org/schemas/members - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range , - , - ; - rdfs:label "members"@en . - - -### http:/daostar.org/schemas/proposal - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:comment "proposal, which an activity relates to"^^xsd:string ; - rdfs:label "proposal"^^xsd:string . - - -### http:/daostar.org/schemas/proposals - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:comment "list of the DAO's proposals"^^xsd:string ; - rdfs:label "proposals"@en . - - -### http:/daostar.org/schemas/to - rdf:type owl:ObjectProperty ; - rdfs:subPropertyOf owl:topObjectProperty ; - rdfs:domain ; - rdfs:range ; - rdfs:label "to"@en . - - -################################################################# -# Data properties -################################################################# - -### http:/daostar.org/schemas/activityLogURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "activityLogURI"@en . - - -### http:/daostar.org/schemas/address - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain , - ; - rdfs:range xsd:string ; - rdfs:label "address"@en . - - -### http:/daostar.org/schemas/callData - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:label "callData"@en . - - -### http:/daostar.org/schemas/contentURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:comment "URI pointing to the context of a proposal"^^xsd:string ; - rdfs:label "contentURI"@en . - - -### http:/daostar.org/schemas/data - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "call data"^^xsd:string ; - rdfs:label "data"@en . - - -### http:/daostar.org/schemas/description - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "a description of the DAO"^^xsd:string ; - rdfs:label "description"@en . - - -### http:/daostar.org/schemas/governanceURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "governanceURI"@en . - - -### http:/daostar.org/schemas/id - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain , - ; - rdfs:range xsd:string ; - rdfs:comment "proposal ID"^^xsd:string ; - rdfs:label "id"@en . - - -### http:/daostar.org/schemas/membersURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "membersURI"^^xsd:string . - - -### http:/daostar.org/schemas/name - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain , - ; - rdfs:range xsd:string ; - rdfs:comment "name of the DAO"^^xsd:string ; - rdfs:label "name"@en . - - -### http:/daostar.org/schemas/operation - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:label "call or delegate call"^^xsd:string , - "operation"@en . - - -### http:/daostar.org/schemas/proposalsURI - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:anyURI ; - rdfs:label "proposalsURI"@en . - - -### http:/daostar.org/schemas/status - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "status of a proposal"^^xsd:string ; - rdfs:label "status"@en . - - -### http:/daostar.org/schemas/value - rdf:type owl:DatatypeProperty ; - rdfs:subPropertyOf owl:topDataProperty ; - rdfs:domain ; - rdfs:range xsd:string ; - rdfs:comment "call value"^^xsd:string ; - rdfs:label "value"^^xsd:string . - - -################################################################# -# Classes -################################################################# - -### http:/daostar.org/schemas/BlockchainAddress - rdf:type owl:Class ; - rdfs:comment "a blockchain address"^^xsd:string ; - rdfs:label "BlockchainAddress"@en . - - -### http:/daostar.org/schemas/CallDataEVM - rdf:type owl:Class ; - rdfs:comment "information regarding an EVM call"^^xsd:string ; - rdfs:label "CallDataEVM"@en . - - -### http:/daostar.org/schemas/DAO - rdf:type owl:Class ; - rdfs:label "DAO"@en . - - -### http:/daostar.org/schemas/EthereumAddress - rdf:type owl:Class ; - rdfs:subClassOf ; - rdfs:label "EthereumAddress"@en . - - -### http:/daostar.org/schemas/Proposal - rdf:type owl:Class ; - rdfs:label "Proposal"@en . - - -### http:/daostar.org/schemas/activity - rdf:type owl:Class ; - rdfs:comment "describes an interaction between a DAO member and a DAO proposal"^^xsd:string ; - rdfs:label "activity"@en . - - -### http:/daostar.org/schemas/member - rdf:type owl:Class . - - -################################################################# -# Annotations -################################################################# - - rdfs:comment "DAO member"^^xsd:string ; - rdfs:label "member"@en . - - -### Generated by the OWL API (version 4.5.13) https://github.com/owlcs/owlapi diff --git a/assets/eip-4886/contracts/EPS.sol b/assets/eip-4886/contracts/EPS.sol deleted file mode 100644 index 303450c6f71d1a..00000000000000 --- a/assets/eip-4886/contracts/EPS.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.7.0 (epsproxy/contracts/EPS.sol) - -pragma solidity ^0.8.9; - -/** - * @dev Implementation of the EPS register interface. - */ -interface EPS { - // Emitted when an address nominates a proxy address: - event NominationMade(address indexed nominator, address indexed proxy, uint256 timestamp, uint256 provider); - // Emitted when an address accepts a proxy nomination: - event NominationAccepted(address indexed nominator, address indexed proxy, address indexed delivery, uint256 timestamp, uint256 provider); - // Emitted when the proxy address updates the delivery address on a record: - event DeliveryUpdated(address indexed nominator, address indexed proxy, address indexed delivery, address oldDelivery, uint256 timestamp, uint256 provider); - // Emitted when a nomination record is deleted. initiator 0 = nominator, 1 = proxy: - event NominationDeleted(string initiator, address indexed nominator, address indexed proxy, uint256 timestamp, uint256 provider); - // Emitted when a register record is deleted. initiator 0 = nominator, 1 = proxy: - event RecordDeleted(string initiator, address indexed nominator, address indexed proxy, address indexed delivery, uint256 timestamp, uint256 provider); - // Emitted when the register fee is set: - event RegisterFeeSet(uint256 indexed registerFee); - // Emitted when the treasury address is set: - event TreasuryAddressSet(address indexed treasuryAddress); - // Emitted on withdrawal to the treasury address: - event Withdrawal(uint256 indexed amount, uint256 timestamp); - - function nominationExists(address _nominator) external view returns (bool); - function nominationExistsForCaller() external view returns (bool); - function proxyRecordExists(address _proxy) external view returns (bool); - function proxyRecordExistsForCaller() external view returns (bool); - function nominatorRecordExists(address _nominator) external view returns (bool); - function nominatorRecordExistsForCaller() external view returns (bool); - function getProxyRecord(address _proxy) external view returns (address nominator, address proxy, address delivery); - function getProxyRecordForCaller() external view returns (address nominator, address proxy, address delivery); - function getNominatorRecord(address _nominator) external view returns (address nominator, address proxy, address delivery); - function getNominatorRecordForCaller() external view returns (address nominator, address proxy, address delivery); - function addressIsActive(address _receivedAddress) external view returns (bool); - function addressIsActiveForCaller() external view returns (bool); - function getNomination(address _nominator) external view returns (address proxy); - function getNominationForCaller() external view returns (address proxy); - function getAddresses(address _receivedAddress) external view returns (address nominator, address delivery, bool isProxied); - function getAddressesForCaller() external view returns (address nominator, address delivery, bool isProxied); - function getRole(address _roleAddress) external view returns (string memory currentRole); - function getRoleForCaller() external view returns (string memory currentRole); - function makeNomination(address _proxy, uint256 _provider) external payable; - function acceptNomination(address _nominator, address _delivery, uint256 _provider) external; - function updateDeliveryAddress(address _delivery, uint256 _provider) external; - function deleteRecordByNominator(uint256 _provider) external; - function deleteRecordByProxy(uint256 _provider) external; - function setRegisterFee(uint256 _registerFee) external returns (bool); - function getRegisterFee() external view returns (uint256 _registerFee); - function setTreasuryAddress(address _treasuryAddress) external returns (bool); - function getTreasuryAddress() external view returns (address _treasuryAddress); - function withdraw(uint256 _amount) external returns (bool); -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/Proxiable.sol b/assets/eip-4886/contracts/Proxiable.sol deleted file mode 100644 index c6607b68457bfc..00000000000000 --- a/assets/eip-4886/contracts/Proxiable.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.7.0 (epsproxy/contracts/Proxiable.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@epsproxy/contracts/EPS.sol"; - -/** - * @dev Contract module which allows children to implement calls to the EPS - * proxy registry - */ - -abstract contract Proxiable is Context { - - EPS eps; // Address for the relevant chain passed in on the constructor. - - /** - * @dev Constructor initialises the register contract object - */ - constructor( - address _epsRegisterAddress - ) { - eps = EPS(_epsRegisterAddress); - } - - /** - * @dev Returns the proxied address details (nominator and delivery address) for a passed proxy address - */ - function getAddresses(address _receivedAddress) internal view returns (address nominator, address delivery, bool isProxied) { - return (eps.getAddresses(_receivedAddress)); - } - - /** - * @dev Returns true if this is currently a proxy address (i.e. has an entry that isn't expired): - */ - function proxyRecordExists(address _receivedAddress) internal view returns (bool isProxied) { - return (eps.proxyRecordExists(_receivedAddress)); - } - - /** - * @dev Returns an ERC20 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists. - */ - function ERC20BalanceOfNominator(address _receivedAddress, address tokenContract) internal virtual view returns (uint256 _tokenBalance){ - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - return IERC20(tokenContract).balanceOf(nominator); - } - - /** - * @dev Returns an ERC20 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists, IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to proxyRecordExists that determines if a proxy address is in use, which is then passed in on the call - * to the contract inheriting this method. This saves gas for anyone who is NOT using a proxy as we do not needlessly check for proxy details. - */ - function ERC20BalanceOfNominatorSwitched(address _receivedAddress, address _tokenContract, bool _isProxied) internal virtual view returns (uint256 _tokenBalance){ - if (_isProxied) { - return ERC20BalanceOfNominator(_receivedAddress, _tokenContract); - } - else { - return IERC20(_tokenContract).balanceOf(_receivedAddress); - } - } - - /** - * @dev Returns an ERC721 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists. - */ - function ERC721BalanceOfNominator(address _proxy, address tokenContract) internal virtual view returns (uint256 _tokenBalance){ - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_proxy); - return IERC721(tokenContract).balanceOf(nominator); - } - - /** - * @dev Returns an ERC721 token balance for the nominator, if a proxy record exists, or for the address - * passed in if no proxy record exists, IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to proxyRecordExists that determines if a proxy address is in use, which is then passed in on the call - * to the contract inheriting this method . This saves gas for anyone who is NOT using a proxy as we do not needlessly check for proxy details. - */ - function ERC721BalanceOfNominatorSwitched(address _receivedAddress, address _tokenContract, bool _isProxied) internal virtual view returns (uint256 _tokenBalance){ - if (_isProxied) { - return ERC721BalanceOfNominator(_receivedAddress, _tokenContract); - } - else { - return IERC721(_tokenContract).balanceOf(_receivedAddress); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/ProxyRegister.sol b/assets/eip-4886/contracts/ProxyRegister.sol deleted file mode 100644 index cf848175ce864f..00000000000000 --- a/assets/eip-4886/contracts/ProxyRegister.sol +++ /dev/null @@ -1,347 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.7.0 (epsproxy/contracts/ProxyRegister.sol) - -pragma solidity ^0.8.9; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@epsproxy/contracts/EPS.sol"; - -/** - * @dev The EPS Register contract. - */ -contract ProxyRegister is EPS, Ownable { - - struct Record { - address nominator; - address delivery; - } - - uint256 private registerFee; - address private treasury; - - mapping (address => address) nominatorToProxy; - mapping (address => Record) proxyToRecord; - - /** - * @dev Constructor initialises the register fee and treasury address: - */ - constructor( - uint256 _registerFee, - address _treasury - ) { - setRegisterFee(_registerFee); - setTreasuryAddress(_treasury); - } - - /** - * @dev Nominators can nominate ONCE only - */ - modifier isNotCurrentNominator(address _nominator) { - require(!nominationExists(_nominator), "Address has an existing nomination"); - _; - } - - /** - * @dev Check if this nominator is already on the registry - */ - modifier isExistingNominator(address _nominator) { - require(nominationExists(_nominator), "Nominator entry does not exist"); - _; - } - - /** - * @dev Proxys can act as proxy ONCE only - */ - modifier isNotCurrentProxy(address _proxy) { - require(!proxyRecordExists(_proxy), "Address is already acting as a proxy"); - _; - } - - /** - * @dev Check if this proxy is already on the registry - */ - modifier isExistingProxy(address _proxy) { - require(proxyRecordExists(_proxy), "Proxy entry does not exist"); - _; - } - - /** - * @dev Return if an entry exists for this nominator address - */ - function nominationExists(address _nominator) public view returns (bool) { - return nominatorToProxy[_nominator] != address(0); - } - - /** - * @dev Return if an entry exists for this nominator address - For Caller - */ - function nominationExistsForCaller() public view returns (bool) { - return nominationExists(msg.sender); - } - - /** - * @dev Return if an entry exists for this proxy address - */ - function proxyRecordExists(address _proxy) public view returns (bool) { - return proxyToRecord[_proxy].nominator != address(0); - } - - /** - * @dev Return if an entry exists for this proxy address - For Caller - */ - function proxyRecordExistsForCaller() external view returns (bool) { - return proxyRecordExists(msg.sender); - } - - /** - * @dev Return if an entry exists for this nominator address - */ - function nominatorRecordExists(address _nominator) public view returns (bool) { - return proxyToRecord[nominatorToProxy[_nominator]].nominator != address(0); - } - - /** - * @dev Return if an entry exists for this nominator address - For Caller - */ - function nominatorRecordExistsForCaller() external view returns (bool) { - return nominatorRecordExists(msg.sender); - } - - /** - * @dev Return if the address is an active proxy address OR a nominator with an active proxy - */ - function addressIsActive(address _receivedAddress) public view returns (bool) { - bool isActive = false; - // Check if the address is an active proxy address or active nominator: - if (proxyRecordExists(_receivedAddress) || nominatorRecordExists(_receivedAddress)){ - isActive = true; - } - return isActive; - } - - /** - * @dev Return if the address is an active proxy address OR a nominator with an active proxy - For Caller - */ - function addressIsActiveForCaller() external view returns (bool) { - return addressIsActive(msg.sender); - } - - /** - * @dev Get entry details by proxy - */ - function getProxyRecord(address _proxy) public view returns (address nominator, address proxy, address delivery) { - Record memory currentItem = proxyToRecord[_proxy]; - return (currentItem.nominator, nominatorToProxy[currentItem.nominator], currentItem.delivery); - } - - /** - * @dev Get entry details by proxy - For Caller - */ - function getProxyRecordForCaller() external view returns (address nominator, address proxy, address delivery) { - return (getProxyRecord(msg.sender)); - } - - /** - * @dev Get entry details by nominator - */ - function getNominatorRecord(address _nominator) public view returns (address nominator, address proxy, address delivery) { - address proxyAddress = nominatorToProxy[_nominator]; - if (proxyToRecord[proxyAddress].nominator == address(0)) { - // This function returns registry entries. If there is no entry on the registry (despite there being a nomination), do - // not return the proxy address: - proxyAddress = address(0); - } - return (proxyToRecord[proxyAddress].nominator, proxyAddress, proxyToRecord[proxyAddress].delivery); - } - - /** - * @dev Get entry details by nominator - For Caller - */ - function getNominatorRecordForCaller() external view returns (address nominator, address proxy, address delivery) { - return (getNominatorRecord(msg.sender)); - } - - /** - * @dev Get nomination details only for nominator - */ - function getNomination(address _nominator) public view returns (address proxy) { - return (nominatorToProxy[_nominator]); - } - - /** - * @dev Get nomination details only for nominator - For Caller - */ - function getNominationForCaller() public view returns (address proxy) { - return (getNomination(msg.sender)); - } - - /** - * @dev Returns the proxied address details (nominator and delivery address) for a passed proxy address - */ - function getAddresses(address _receivedAddress) public view returns (address nominator, address delivery, bool isProxied) { - require(!nominationExists(_receivedAddress), "Nominator address cannot interact directly, only through the proxy address"); - Record memory currentItem = proxyToRecord[_receivedAddress]; - if (proxyToRecord[_receivedAddress].nominator == address(0)) { - return(_receivedAddress, _receivedAddress, false); - } - else { - return (currentItem.nominator, currentItem.delivery, true); - } - } - - /** - * @dev Returns the proxied address details (owner and delivery address) for the msg.sender being interacted with - */ - function getAddressesForCaller() external view returns (address nominator, address delivery, bool isProxied) { - return (getAddresses(msg.sender)); - } - - /** - * @dev Returns the current role of a given address (nominator, proxy, none) - */ - function getRole(address _roleAddress) public view returns (string memory currentRole) { - if (proxyRecordExists(_roleAddress)) { - return "Proxy"; - } - if (nominationExists(_roleAddress)) { - if (proxyRecordExists(nominatorToProxy[_roleAddress])) { - return "Nominator - Proxy Active"; - } - else { - return "Nominator - Proxy Pending"; - } - } - return "None"; - } - - /** - * @dev Returns the current role of a given address (nominator, proxy, none) - For Caller - */ - function getRoleForCaller() external view returns (string memory currentRole) { - return getRole(msg.sender); - } - - /** - * @dev The nominator initiates a proxy entry - */ - function makeNomination(address _proxy, uint256 _provider) external payable isNotCurrentNominator(msg.sender) isNotCurrentProxy(_proxy) isNotCurrentProxy(msg.sender) { - require (_proxy != address(0), "Proxy address must be provided"); - require (_proxy != msg.sender, "Proxy address cannot be the same as Nominator address"); - require(msg.value == registerFee, "Register fee must be paid"); - nominatorToProxy[msg.sender] = _proxy; - emit NominationMade(msg.sender, _proxy, block.timestamp, _provider); - } - - /** - * @dev Proxy accepts nomination - */ - function acceptNomination(address _nominator, address _delivery, uint256 _provider) external isNotCurrentProxy(msg.sender) isNotCurrentProxy(_nominator) { - // The nominator must be passed in: - require (_nominator != address(0), "Nominator address must be provided"); - // The sender must match the proxy nomination: - require (nominatorToProxy[_nominator] == msg.sender, "Caller is not the nominated proxy for this nominator"); - // We have a valid nomination, create the ProxyRegisterItem: - proxyToRecord[msg.sender] = Record(_nominator, _delivery); - emit NominationAccepted(_nominator, msg.sender, _delivery, block.timestamp, _provider); - } - - /** - * @dev Change delivery address on an existing proxy item. Can only be called by the proxy address. - */ - function updateDeliveryAddress(address _delivery, uint256 _provider) external isExistingProxy(msg.sender) { - Record memory priorItem = proxyToRecord[msg.sender]; - proxyToRecord[msg.sender].delivery = _delivery; - emit DeliveryUpdated(priorItem.nominator, msg.sender, _delivery, priorItem.delivery, block.timestamp, _provider); - } - - /** - * @dev delete a proxy entry. BOTH the nominator and proxy can delete a proxy arrangement and all - * aspects of that proxy arrangement will be removed. - */ - function deleteRecordByNominator(uint256 _provider) external isExistingNominator(msg.sender) { - deleteProxyRegisterItems(msg.sender, nominatorToProxy[msg.sender], "nominator", _provider); - } - - /** - * @dev delete a proxy entry. BOTH the nominator and proxy can delete a proxy arrangement and all - * aspects of that proxy arrangement will be removed. - */ - function deleteRecordByProxy(uint256 _provider) external isExistingProxy(msg.sender) { - deleteProxyRegisterItems(proxyToRecord[msg.sender].nominator, msg.sender, "proxy", _provider); - } - - /** - * @dev delete the nomination and record (if present) - */ - function deleteProxyRegisterItems(address _nominator, address _proxy, string memory _initiator, uint256 _provider) internal { - // First remove the nomination. We know this must exists, as it has to come before the proxy can be accepted: - delete nominatorToProxy[_nominator]; - emit NominationDeleted(_initiator, _nominator, _proxy, block.timestamp, _provider); - // Now remove the proxy register item. If the nominator is deleting a nomination that has not been accepted by a proxy - // then this will not exists. Check that the proxy is for this nominator. - if (proxyToRecord[_proxy].nominator == _nominator) { - address deletedDelivery = proxyToRecord[_proxy].delivery; - delete proxyToRecord[_proxy]; - emit RecordDeleted(_initiator, _nominator, _proxy, deletedDelivery, block.timestamp, _provider); - } - } - - /** - * @dev set the fee for initiating a registration (accepting a proxy, updating the delivery address and deletions will always be free) - */ - function setRegisterFee(uint256 _registerFee) public onlyOwner returns (bool) - { - require(_registerFee != registerFee, "No change to register fee"); - registerFee = _registerFee; - emit RegisterFeeSet(registerFee); - return true; - } - - /** - * @dev return the register fee: - */ - function getRegisterFee() external view returns (uint256 _registerFee) { - return(registerFee); - } - - /** - * @dev set the treasury address: - */ - function setTreasuryAddress(address _treasuryAddress) public onlyOwner returns (bool) - { - require(_treasuryAddress != treasury, "No change to treasury address"); - treasury = _treasuryAddress; - emit TreasuryAddressSet(treasury); - return true; - } - - /** - * @dev get the treasury address: - */ - function getTreasuryAddress() external view returns (address _treasuryAddress) { - return(treasury); - } - - /** - * @dev withdraw eth to the treasury: - */ - function withdraw(uint256 _amount) external onlyOwner returns (bool) { - (bool success, ) = treasury.call{value: _amount}(""); - require(success, "Withdrawal failed."); - emit Withdrawal(_amount, block.timestamp); - return true; - } - - /** - * @dev revert fallback - */ - fallback() external payable { - revert(); - } - - /** - * @dev revert receive - */ - receive() external payable { - revert(); - } -} diff --git a/assets/eip-4886/contracts/examples/EPSExample721.sol b/assets/eip-4886/contracts/examples/EPSExample721.sol deleted file mode 100644 index b603ff2d336955..00000000000000 --- a/assets/eip-4886/contracts/examples/EPSExample721.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/EPSExample721.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -contract EPSExample721 is ERC721, ERC721Burnable, Ownable, Proxiable { - using Counters for Counters.Counter; - using Strings for uint256; - - Counters.Counter private _tokenIdCounter; - bool hasMinted; - - mapping (address => bool) minterHasMinted; - uint256 constant MAX_SUPPLY = 10000; - - constructor(address _epsRegisterAddress) - ERC721("EPSExample721", "EPS721") - Proxiable(_epsRegisterAddress) { - } - - /** - * @dev This address hasn't minted already - */ - modifier hasNotAlreadyMinted(address _receivedAddress) { - require(minterHasMinted[_receivedAddress] != true, "Address has already minted, allocation exhausted"); - _; - } - - modifier isProxyAddress(address _receivedAddress) { - require(proxyRecordExists(_receivedAddress), "Only a proxy address can mint this token - go to app.epsproxy.com"); - _; - } - - modifier supplyNotExhausted() { - require(_tokenIdCounter.current() < MAX_SUPPLY, "Max supply reached - cannot be minted"); - _; - } - - function _baseURI() internal pure override returns (string memory) { - return "https://epsproxy.com/test/"; - } - - function totalSupply() public pure returns (uint256) { - return (MAX_SUPPLY); - } - - function tokenURI(uint256 tokenId) public view override(ERC721) returns (string memory) - { - require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - - string memory baseURI = _baseURI(); - return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json")) : ""; - } - - function proxyMint() external hasNotAlreadyMinted(msg.sender) isProxyAddress(msg.sender) supplyNotExhausted() { - uint256 tokenId = _tokenIdCounter.current(); - _tokenIdCounter.increment(); - _safeMintProxied(msg.sender, tokenId); - minterHasMinted[msg.sender] = true; - } - - function _burn(uint256 tokenId) internal override(ERC721) { - super._burn(tokenId); - } - - /** - * @dev call safemint after determining the delivery address. - */ - function _safeMintProxied(address _to, uint256 _tokenId) internal virtual { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_to); - _safeMint(delivery, _tokenId); - } - - /** - * @dev call safemint after determining the delivery address IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to _proxyRecordExists that determines if a proxy address is in use. This saves gas for anyone who is - * NOT using a proxy as we do not needlessly check for proxy details. - */ - function safeMintProxiedSwitch(address _to, uint256 _tokenId, bool _isProxied) internal virtual { - if (_isProxied) { - _safeMintProxied(_to, _tokenId); - } - else { - _safeMint(_to, _tokenId); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/examples/EPSGenesis.sol b/assets/eip-4886/contracts/examples/EPSGenesis.sol deleted file mode 100644 index f3eab5769d321a..00000000000000 --- a/assets/eip-4886/contracts/examples/EPSGenesis.sol +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/EPSGenesis.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -/** -* @dev Contract instance for the EPS Genesis ERC-1155. This contract provides two token -* types designed to showcase the capabilities of the Eternal Proxy Service. -* - Open mint: Every proxy address can mint ONE of these tokens. Delivery will be to the -* delivery address. This demonstrates: -* (a) Retrieval of information and processing based on information in the EPS Register. -* (b) Delivery of new assets to the delivery address specified on the register. -* - Gated mint: Every proxy address that acts for a nominator one of three pre-determined -* NFTs can mint the 'gated' token. This will again be delivered to the delivery address -* specified on the EPS register. In addition this demonstrates: -* (c) Checking of the contract balance of the Nominator to determine eligibility. In -* this use of the EPS register risk to the eligibility asset is entirely eliminated -* as the contract interaction is with the proxy address, which does not hold the -* asset, not the nominator. -*/ -contract EPSGenesis is ERC1155, Ownable, ERC1155Burnable, ERC1155Supply, Proxiable { - -/** -* @dev Not required, but provided to give consistency with ERC-721 in terms of display -* in tools like etherscan: -*/ - string public constant NAME = "EPSGenesis"; - string public constant SYMBOL = "EPSGEN"; - - /** - * @dev Definition of token classes and max supply of each: - */ - uint256 public constant OPEN_TOKEN = 0; - uint256 public constant GATED_TOKEN = 1; - uint256 public constant MAX_SUPPLY_OPEN = 10000; - uint256 public constant MAX_SUPPLY_GATED = 10000; - - /** - * @dev tokens that need to be held to mint the gated tokens, provided on the constructor: - */ - address public immutable GATE_1; - address public immutable GATE_2; - address public immutable GATE_3; - - /** - * @dev each address is entitled to just ONE of each type. Each is free, except gas. - */ - mapping (address => bool) minterHasMintedOpen; - mapping (address => bool) minterHasMintedGated; - - constructor(address _epsRegisterAddress, address _GATE_1, address _GATE_2, address _GATE_3, string memory _contractURI) - ERC1155(_contractURI) - Proxiable(_epsRegisterAddress) { - GATE_1 = _GATE_1; - GATE_2 = _GATE_2; - GATE_3 = _GATE_3; - } - - /** - * @dev modifiers to control access based on previous minting: - */ - modifier hasNotAlreadyMintedOpen(address _receivedAddress) { - require(minterHasMintedOpen[_receivedAddress] != true, "Address has already minted in open mint, allocation exhausted"); - _; - } - - modifier hasNotAlreadyMintedGated(address _receivedAddress) { - require(minterHasMintedGated[_receivedAddress] != true, "Address has already minted in gated mint, allocation exhausted"); - _; - } - - /** - * @dev modifier to only allow minting from a proxy address: - */ - modifier isProxyAddress(address _receivedAddress) { - require(proxyRecordExists(_receivedAddress), "Only a proxy address can mint this token - go to app.epsproxy.com"); - _; - } - - /** - * @dev modifier to ensure supply(s) is not exhausted: - */ - modifier supplyNotExhaustedOpen() { - require(totalSupply(OPEN_TOKEN) < MAX_SUPPLY_OPEN, "Max supply reached for open mint - cannot be minted"); - _; - } - - modifier supplyNotExhaustedGated() { - require(totalSupply(GATED_TOKEN) < MAX_SUPPLY_GATED, "Max supply reached for gated mint - cannot be minted"); - _; - } - - /** - * @dev perform minting of the open tokens: - */ - function proxyMintOpen(address _receivedAddress) internal hasNotAlreadyMintedOpen(_receivedAddress) isProxyAddress(_receivedAddress) supplyNotExhaustedOpen() { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - - _mint(delivery, OPEN_TOKEN, 1, ""); - - minterHasMintedOpen[_receivedAddress] = true; - } - - /** - * @dev perform minting of the gated tokens: - */ - function proxyMintGated(address _receivedAddress) internal hasNotAlreadyMintedGated(_receivedAddress) isProxyAddress(_receivedAddress) supplyNotExhaustedGated() { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - - require((IERC721(GATE_1).balanceOf(nominator) >= 1 || IERC721(GATE_2).balanceOf(nominator) >= 1 || IERC721(GATE_3).balanceOf(nominator) >= 1), "Must hold an eligible token for this mint"); - - _mint(delivery, GATED_TOKEN, 1, ""); - - minterHasMintedGated[_receivedAddress] = true; - } - - /** - * @dev external function for minting calls. Required boolean values for open and gated minting (can pass both as true to perform both) - */ - function mintEPSGenesis(bool mintOpen, bool mintGated) external { - if (mintOpen) { - proxyMintOpen(msg.sender); - } - if (mintGated) { - proxyMintGated(msg.sender); - } - } - - /** - * @dev external and public functions to determine eligibility off-chain: - */ - function hasMintedOpen(address _receivedAddress) external view returns (bool) { - return(minterHasMintedOpen[_receivedAddress]); - } - - function hasMintedGated(address _receivedAddress) external view returns (bool) { - return(minterHasMintedGated[_receivedAddress]); - } - - function hasAProxyRecord(address _receivedAddress) external view returns (bool) { - return(proxyRecordExists(_receivedAddress)); - } - - function hasNominatorWithEligibleToken(address _receivedAddress) public view returns (bool) { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_receivedAddress); - return((IERC721(GATE_1).balanceOf(nominator) >= 1 || IERC721(GATE_2).balanceOf(nominator) >= 1 || IERC721(GATE_3).balanceOf(nominator) >= 1)); - } - - function addressStatus(address _receivedAddress) public view returns (bool open, bool gated, bool proxy, bool eligible) { - return(minterHasMintedOpen[_receivedAddress], minterHasMintedGated[_receivedAddress], proxyRecordExists(_receivedAddress), hasNominatorWithEligibleToken(_receivedAddress)); - } - - /** - * @dev name, max and total supply for tools like etherscan: - */ - function name() public pure returns (string memory) { - return NAME; - } - - function symbol() public pure returns (string memory) { - return SYMBOL; - } - - function totalSupply() public pure returns (uint256) { - return (MAX_SUPPLY_OPEN + MAX_SUPPLY_GATED); - } - - // The following functions are overrides required by Solidity. - - function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) - internal - override(ERC1155, ERC1155Supply) - { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/examples/ERC20Proxied.sol b/assets/eip-4886/contracts/examples/ERC20Proxied.sol deleted file mode 100644 index 9971c3f6e3742a..00000000000000 --- a/assets/eip-4886/contracts/examples/ERC20Proxied.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/ERC20Proxied.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -/** - * @dev Contract module which allows children to implement proxied delivery - * on minting calls - */ -abstract contract ERC20Proxied is Context, ERC20, Proxiable { - - /** - * @dev call mint after determining the delivery address. - */ - function _mintProxied(address _account, uint256 _amount) internal virtual { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_account); - _mint(delivery, _amount); - } - - /** - * @dev call mint after determining the delivery address IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to _proxyRecordExists that determines if a proxy address is in use. This saves gas for anyone who is - * NOT using a proxy as we do not needlessly check for proxy details. - */ - function _MintProxiedSwitch(address _account, uint256 _amount, bool _isProxied) internal virtual { - if (_isProxied) { - _mintProxied(_account, _amount); - } - else { - _mint(_account, _amount); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/contracts/examples/ERC721Proxied.sol b/assets/eip-4886/contracts/examples/ERC721Proxied.sol deleted file mode 100644 index f0df21e13c202b..00000000000000 --- a/assets/eip-4886/contracts/examples/ERC721Proxied.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// EPSProxy Contracts v1.8.0 (epsproxy/contracts/examples/ERC721Proxied.sol) - -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@epsproxy/contracts/Proxiable.sol"; - -/** - * @dev Contract module which allows children to implement proxied delivery - * on minting calls - */ -abstract contract ERC721Proxied is Context, ERC721, Proxiable { - - constructor( - address _epsRegisterAddress, - string memory _name, - string memory _symbol - ) Proxiable(_epsRegisterAddress) - ERC721("_name", "_symbol") - { - } - - /** - * @dev Returns the proxied address details (nominator address, delivery address) for a passed proxy address. - * Call this to view the details for any given proxy address. - */ - function _getAddresses(address _receivedAddress) internal virtual view returns (address _nominator, address _delivery, bool _isProxied){ - return (getAddresses(_receivedAddress)); - } - - /** - * @dev Returns if a given address is a proxy or not: - */ - function _proxyRecordExists(address _receivedAddress) internal virtual view returns (bool _isProxied){ - return (proxyRecordExists(_receivedAddress)); - } - - /** - * @dev call safemint after determining the delivery address. - */ - function _safeMintProxied(address _to, uint256 _tokenId) internal virtual { - address nominator; - address delivery; - bool isProxied; - (nominator, delivery, isProxied) = getAddresses(_to); - _safeMint(delivery, _tokenId); - } - - /** - * @dev call safemint after determining the delivery address IF we have been passed a bool indicating - * that a proxied address is in use. This function should be used in conjunction with an off-chain call - * to _proxyRecordExists that determines if a proxy address is in use. This saves gas for anyone who is - * NOT using a proxy as we do not needlessly check for proxy details. - */ - function _safeMintProxiedSwitch(address _to, uint256 _tokenId, bool _isProxied) internal virtual { - if (_isProxied) { - _safeMintProxied(_to, _tokenId); - } - else { - _safeMint(_to, _tokenId); - } - } -} \ No newline at end of file diff --git a/assets/eip-4886/test/ProxyRegister.js b/assets/eip-4886/test/ProxyRegister.js deleted file mode 100644 index 47da8a15bdab76..00000000000000 --- a/assets/eip-4886/test/ProxyRegister.js +++ /dev/null @@ -1,1412 +0,0 @@ -const { expect } = require("chai") -const registerFee = 0.005; -const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" -const tokenURI = "https://arweave.net/_Kk3lIGmZTnwT6Q67UOTlNG2biZq1F8F9jmoH3EaA14/{id}.json" - -describe("Genesis1155", function () { - let hardhatProxyRegister - let owner - let addr1 - let addr2 - let addr3 - let treasury - let addrs - - beforeEach(async function () { - ;[owner, addr1, addr2, addr3, treasury, ...addrs] = await ethers.getSigners() - - const ProxyRegister = await ethers.getContractFactory("ProxyRegister") - hardhatProxyRegister = await ProxyRegister.deploy( - ethers.utils.parseEther(registerFee.toString()), - treasury.address, - ) - - const mockERC721 = await ethers.getContractFactory("mockERC721") - hardhatBoredApes = await mockERC721.deploy() - hardhatLazyLions = await mockERC721.deploy() - hardhatLoomlockNFT = await mockERC721.deploy() - - const EPSGenOne = await ethers.getContractFactory("EPSGenesis") - hardhatEPSGenOne = await EPSGenOne.deploy( - hardhatProxyRegister.address, - hardhatBoredApes.address, - hardhatLazyLions.address, - hardhatLoomlockNFT.address, - tokenURI - ) - - }) - - context("Non-proxy addresses", function () { - describe("Minting", function () { - it("Cannot mint both NFTs", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - - }) - it("Cannot mint open NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, false), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - it("Cannot mint gated NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(false, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - }) - - }); - - context("Proxy address", function () { - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - }) - - describe("Nominator Minting", function () { - it("Cannot mint both NFTs", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - - }) - it("Cannot mint open NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(true, false), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - it("Cannot mint gated NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr1).mintEPSGenesis(false, true), - ).to.be.revertedWith("Only a proxy address can mint this token - go to app.epsproxy.com") - }) - }) - - describe("Proxy Minting - no eligible token", function () { - it("Cannot mint both NFTs", async () => { - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(true, true), - ).to.be.revertedWith("Must hold an eligible token for this mint") - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("Cannot mint gated NFT", async () => { - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(false, true), - ).to.be.revertedWith("Must hold an eligible token for this mint") - }) - }) - - describe("Proxy Minting - eligible token 1", function () { - beforeEach(async function () { - var tx1 = await hardhatBoredApes - .connect(addr1) - .safeMint() - - }) - - it("CAN mint both NFTs", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("CAN mint gated NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - }) - }) - - describe("Proxy Minting - eligible token 2", function () { - beforeEach(async function () { - var tx1 = await hardhatLazyLions - .connect(addr1) - .safeMint() - - }) - - it("CAN mint both NFTs", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("CAN mint gated NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - }) - }) - - describe("Proxy Minting - eligible token 3", function () { - beforeEach(async function () { - var tx1 = await hardhatLoomlockNFT - .connect(addr1) - .safeMint() - - }) - - it("CAN mint both NFTs", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - }) - it("CAN mint open NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - }) - it("CAN mint gated NFT", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - }) - }) - - describe("Proxy Minting - eligible token, cannot mint 2 of either", function () { - beforeEach(async function () { - var tx1 = await hardhatBoredApes - .connect(addr1) - .safeMint() - - }) - - it("Cannot mint 2 on call to both", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(true, true), - ).to.be.revertedWith("Address has already minted in open mint, allocation exhausted") - - }) - it("Cannot mint 2 open", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(true, false) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(0) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 0)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 0)).to.equal(1) - - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(true, false), - ).to.be.revertedWith("Address has already minted in open mint, allocation exhausted") - }) - it("Cannot mint 2 gated", async () => { - var tx1 = await hardhatEPSGenOne - .connect(addr2) - .mintEPSGenesis(false, true) - expect(tx1).to.emit(hardhatEPSGenOne, "TransferSingle") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.to).to.equal(addr3.address) - expect(receipt.events[0].args.id).to.equal(1) - expect(receipt.events[0].args.value).to.equal(1) - - expect(await hardhatEPSGenOne.balanceOf(addr1.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr2.address, 1)).to.equal(0) - expect(await hardhatEPSGenOne.balanceOf(addr3.address, 1)).to.equal(1) - - await expect( - hardhatEPSGenOne.connect(addr2).mintEPSGenesis(false, true), - ).to.be.revertedWith("Address has already minted in gated mint, allocation exhausted") - }) - }) - }); -}); - -describe("ProxyRegister", function () { - let hardhatProxyRegister - let owner - let addr1 - let addr2 - let addr3 - let treasury - let addrs - - beforeEach(async function () { - ;[owner, addr1, addr2, addr3, treasury, ...addrs] = await ethers.getSigners() - - const ProxyRegister = await ethers.getContractFactory("ProxyRegister") - hardhatProxyRegister = await ProxyRegister.deploy( - ethers.utils.parseEther(registerFee.toString()), - treasury.address, - ) - }) - - context("Contract Setup", function () { - describe("Constructor", function () { - it("Has a contract balance of 0", async () => { - const contractBalance = await ethers.provider.getBalance( - hardhatProxyRegister.address, - ) - expect(contractBalance).to.equal(0) - }) - }) - }); - - context("Owner Only Functions", function () { - describe("Owner can execute", function () { - - it("Set registerFee", async () => { - var tx1 = await hardhatProxyRegister - .connect(owner) - .setRegisterFee(ethers.utils.parseEther("0.01")) - expect(tx1).to.emit(hardhatProxyRegister, "RegisterFeeSet") - var receipt = await tx1.wait() - expect(receipt.events[0].args.registerFee).to.equal(BigInt(ethers.utils.parseEther("0.01"))) - - const registerFeeParameter = await hardhatProxyRegister.getRegisterFee() - expect(registerFeeParameter).to.equal(ethers.utils.parseEther("0.01")) - }) - - it("Set treasuryAddress", async () => { - var tx1 = await hardhatProxyRegister - .connect(owner) - .setTreasuryAddress(addr3.address) - expect(tx1).to.emit(hardhatProxyRegister, "TreasuryAddressSet") - var receipt = await tx1.wait() - expect(receipt.events[0].args.treasuryAddress).to.equal(addr3.address) - - const treasuryAddressParameter = await hardhatProxyRegister.getTreasuryAddress() - expect(treasuryAddressParameter).to.equal(addr3.address) - }) - }) - - describe("Non-owner cannot execute", function () { - it("Set registerFee", async () => { - await expect( - hardhatProxyRegister.connect(addr1).setRegisterFee(ethers.utils.parseEther("0.01")), - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - - it("Set treasuryAddress", async () => { - await expect( - hardhatProxyRegister.connect(addr1).setTreasuryAddress(addr3.address), - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - }) - - describe("Withdraw", async () => { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - }) - - it("fails if not owner", async () => { - await expect( - hardhatProxyRegister.connect(addr1).withdraw(ethers.utils.parseEther(registerFee.toString())), - ).to.be.revertedWith("Ownable: caller is not the owner") - }) - - it("allows owner to withdraw to the treasury", async () => { - const withdrawalAmount = ethers.utils.parseEther(registerFee.toString()) - - expect( - await ethers.provider.getBalance(hardhatProxyRegister.address), - ).to.equal(withdrawalAmount) - - const initialTreasuryBalance = await ethers.provider.getBalance( - treasury.address, - ) - tx = await hardhatProxyRegister.connect(owner).withdraw(withdrawalAmount) - const receipt = await tx.wait() - const finalTreasuryBalance = await ethers.provider.getBalance( - treasury.address, - ) - const finalContractBalance = await ethers.provider.getBalance( - hardhatProxyRegister.address, - ) - - expect(finalTreasuryBalance).to.equal( - initialTreasuryBalance.add(withdrawalAmount), - ) - expect(finalContractBalance).to.equal(0) - }) - }) - }); - - context("Nominate a Proxy", function () { - describe("Add a nomination", function () { - it("New proxy nomination is added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - }) - - it("Duplicate proxy nomination not added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - await expect( - hardhatProxyRegister.connect(addr1).makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address has an existing nomination") - }) - - it("Duplicate proxy nomination not added, even to a new proxy address", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - await expect( - - hardhatProxyRegister.connect(addr1).makeNomination( addr3.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address has an existing nomination") - }) - - it("Proxy nomination where nominator is an existing proxy not added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr2).makeNomination( addr3.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address is already acting as a proxy") - - }) - - it("Proxy nomination for an address that is already a proxy not added", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr3).makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Address is already acting as a proxy") - }) - - it("Address cannot be proxied to itself", async () => { - await expect( - hardhatProxyRegister.connect(addr1).makeNomination( addr1.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()),}) - ).to.be.revertedWith("Proxy address cannot be the same as Nominator address") - }) - }) - }) - - context("Accept a Nomination", function () { - describe("Accept the Nomination", function () { - - it("Can do if valid", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const registerEntry2 = await hardhatProxyRegister.connect(addr2).getProxyRecordForCaller() - expect(registerEntry2[0]).to.equal(addr1.address) - expect(registerEntry2[1]).to.equal(addr2.address) - expect(registerEntry2[2]).to.equal(addr3.address) - - }) - - it("Cannot do for non-existent nomination", async () => { - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Caller is not the nominated proxy for this nominator") - }) - - it("Cannot do for another address's nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Caller is not the nominated proxy for this nominator") - }) - - it("Cannot do for a nominator that is now a proxy", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr1.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx2).to.emit(hardhatProxyRegister, "NominationMade") - - var tx3 = await hardhatProxyRegister - .connect(addr1) - .acceptNomination( addr3.address, addr3.address, 1,) - expect(tx3).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Address is already acting as a proxy") - }) - - it("Cannot do for address that is already a proxy", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx3 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr3.address, addr3.address, 1,) - expect(tx3).to.emit(hardhatProxyRegister, "NominationAccepted") - - await expect( - hardhatProxyRegister.connect(addr2).acceptNomination( addr1.address, addr3.address, 1) - ).to.be.revertedWith("Address is already acting as a proxy") - }) - }) - }); - - - context("Change a proxy entry", function () { - - describe("Change delivery address", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const registerEntry2 = await hardhatProxyRegister.connect(addr2).getProxyRecordForCaller() - expect(registerEntry2[0]).to.equal(addr1.address) - expect(registerEntry2[1]).to.equal(addr2.address) - expect(registerEntry2[2]).to.equal(addr3.address) - }) - - it("Proxy Address can update delivery", async () => { - const previousRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(previousRegistryEntry[0]).to.equal(addr1.address) - expect(previousRegistryEntry[1]).to.equal(addr2.address) - expect(previousRegistryEntry[2]).to.equal(addr3.address) - - var tx1 = await hardhatProxyRegister - .connect(addr2) - .updateDeliveryAddress( addr1.address, 1,) - expect(tx1).to.emit(hardhatProxyRegister, "DeliveryUpdated") - - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - expect(receipt.events[0].args.delivery).to.equal(addr1.address) - expect(receipt.events[0].args.oldDelivery).to.equal(addr3.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const newRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(newRegistryEntry[0]).to.equal(addr1.address) - expect(newRegistryEntry[1]).to.equal(addr2.address) - expect(newRegistryEntry[2]).to.equal(addr1.address) - }) - - it("Nominator cannot edit an entry", async () => { - const previousRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(previousRegistryEntry[0]).to.equal(addr1.address) - expect(previousRegistryEntry[1]).to.equal(addr2.address) - expect(previousRegistryEntry[2]).to.equal(addr3.address) - - await expect( - hardhatProxyRegister.connect(addr1).updateDeliveryAddress( addr1.address, 1,) - ).to.be.revertedWith("Proxy entry does not exist") - }) - - it("Another address cannot edit an entry", async () => { - const previousRegistryEntry = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(previousRegistryEntry[0]).to.equal(addr1.address) - expect(previousRegistryEntry[1]).to.equal(addr2.address) - expect(previousRegistryEntry[2]).to.equal(addr3.address) - - await expect( - hardhatProxyRegister.connect(addr3).updateDeliveryAddress( addr1.address, 1,) - ).to.be.revertedWith("Proxy entry does not exist") - }) - }) - }); - - context("Delete a proxy entry", function () { - - describe("Delete called from Nominator", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - }) - - it("Removes a nomination only", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - var tx1 = await hardhatProxyRegister - .connect(addr1) - .deleteRecordByNominator(1,) - expect(tx1).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry2).to.equal(ZERO_ADDRESS) - }) - - it("Doesn't remove a proxy entry that is for another nominator", async () => { - // edge case test, but you can have two addresses both nominate a third address - // as a proxy. This is OK. Let's say the proxy accepts the second nomination. If the - // first address (the nomination that wasn't accepted) deletes that nominoation we DON'T - // want that to delete the second nominator's valud proxy entry, so let's check that - // doesn't happen - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - // Now load a proxy up from address 3 to address 2: - var tx1 = await hardhatProxyRegister - .connect(addr3) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr3.address, addr1.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - // Verify that addr3 has the proxy with 2: - const registerEntry1 = await hardhatProxyRegister.connect(addr3).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr3.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr1.address) - - // Delete the nomination from address 1: - var tx3 = await hardhatProxyRegister - .connect(addr1) - .deleteRecordByNominator(1,) - expect(tx3).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry2).to.equal(ZERO_ADDRESS) - - // Verify that addr3 has the proxy with 2: - const registerEntry3 = await hardhatProxyRegister.connect(addr3).getNominatorRecordForCaller() - expect(registerEntry3[0]).to.equal(addr3.address) - expect(registerEntry3[1]).to.equal(addr2.address) - expect(registerEntry3[2]).to.equal(addr1.address) - }) - - it("Removes nomination and proxy register Item", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - const proxyRecordExists = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false) - - const proxyEntryExists1 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists1).to.equal(false) - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const proxyEntryExists2 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists2).to.equal(true) - - const proxyEntryExists3 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists3).to.equal(true) - - var tx1 = await hardhatProxyRegister - .connect(addr1) - .deleteRecordByNominator(1,) - expect(tx1).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry2).to.equal(ZERO_ADDRESS) - - const proxyEntryExists4 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists4).to.equal(false) - - const proxyEntryExists5 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists5).to.equal(false) - }) - }) - - describe("Delete called from Proxy", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - var receipt = await tx1.wait() - expect(receipt.events[0].args.nominator).to.equal(addr1.address) - expect(receipt.events[0].args.proxy).to.equal(addr2.address) - currentTime = (await ethers.provider.getBlock("latest")).timestamp - expect(receipt.events[0].args.timestamp).to.equal(currentTime) - - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - }) - - it("Removes nomination and proxy register Item", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - const proxyRecordExists = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false) - - const proxyEntryExists1 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists1).to.equal(false) - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const proxyEntryExists2 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists2).to.equal(true) - - const proxyEntryExists3 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists3).to.equal(true) - - var tx1 = await hardhatProxyRegister - .connect(addr2) - .deleteRecordByProxy(1,) - expect(tx1).to.emit(hardhatProxyRegister, "NominationDeleted") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry2[0]).to.equal(ZERO_ADDRESS) - expect(registerEntry2[1]).to.equal(ZERO_ADDRESS) - expect(registerEntry2[2]).to.equal(ZERO_ADDRESS) - - const proxyEntryExists4 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists4).to.equal(false) - - const proxyEntryExists5 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists5).to.equal(false) - }) - - it("Call from Nominator does not work", async () => { - const registerEntry = await hardhatProxyRegister.connect(addr1).getNominationForCaller() - expect(registerEntry).to.equal(addr2.address) - - const proxyRecordExists = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false) - - const proxyEntryExists1 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists1).to.equal(false) - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - const registerEntry1 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry1[0]).to.equal(addr1.address) - expect(registerEntry1[1]).to.equal(addr2.address) - expect(registerEntry1[2]).to.equal(addr3.address) - - const proxyEntryExists2 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists2).to.equal(true) - - const proxyEntryExists3 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists3).to.equal(true) - - await expect( - hardhatProxyRegister.connect(addr1).deleteRecordByProxy(1,) - ).to.be.revertedWith("Proxy entry does not exist") - - const registerEntry2 = await hardhatProxyRegister.connect(addr1).getNominatorRecordForCaller() - expect(registerEntry2[0]).to.equal(addr1.address) - expect(registerEntry2[1]).to.equal(addr2.address) - expect(registerEntry2[2]).to.equal(addr3.address) - - const proxyEntryExists4 = await hardhatProxyRegister.connect(addr1).nominatorRecordExistsForCaller() - expect(proxyEntryExists4).to.equal(true) - - const proxyEntryExists5 = await hardhatProxyRegister.connect(addr2).proxyRecordExistsForCaller() - expect(proxyEntryExists5).to.equal(true) - }) - }) - }); - - context("Getter Functions", function () { - - describe("Fee and Treasury", function () { - it("getRegisterFee", async () => { - const fee = await hardhatProxyRegister.getRegisterFee() - expect(fee).to.equal(ethers.utils.parseEther(registerFee.toString())); - }) - - it("getTreasuryAddress", async () => { - const treasuryAddress = await hardhatProxyRegister.getTreasuryAddress() - expect(treasuryAddress).to.equal(treasury.address); - }) - }) - - describe("No proxy details saved", function () { - it("nominationExists", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(false); - }) - - it("nominationExistsForCaller", async () => { - const nominationExists = await hardhatProxyRegister.connect(addr1.address).nominationExistsForCaller() - expect(nominationExists).to.equal(false); - }) - - it("getNomination", async () => { - const proxy = await hardhatProxyRegister.getNomination(addr1.address) - expect(proxy).to.equal(ZERO_ADDRESS); - }) - - it("getNominationForCaller", async () => { - const proxy = await hardhatProxyRegister.getNominationForCaller() - expect(proxy).to.equal(ZERO_ADDRESS); - }) - - it("proxyRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.proxyRecordExists(addr2.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("proxyRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).proxyRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.nominatorRecordExists(addr1.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr1.address).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("addressIsActive", async () => { - const addressIsActive = await hardhatProxyRegister.addressIsActive(addr1.address) - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.addressIsActive(addr2.address) - expect(addressIsActive2).to.equal(false); - }) - - it("addressIsActiveForCaller", async () => { - const addressIsActive = await hardhatProxyRegister.connect(addr1.address).addressIsActiveForCaller() - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.connect(addr2.address).addressIsActiveForCaller() - expect(addressIsActive2).to.equal(false); - }) - - it("getProxyRecord", async () => { - const entry = await hardhatProxyRegister.getProxyRecord(addr2.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getProxyRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getProxyRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecord", async () => { - const entry = await hardhatProxyRegister.getNominatorRecord(addr1.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr1.address).getNominatorRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getAddresses", async () => { - const entry = await hardhatProxyRegister.getAddresses(addr2.address) - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - - it("getAddressesForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getAddressesForCaller() - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - }) - - describe("With Proxy Nomination", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - }) - - it("nominationExists", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(true); - }) - - it("nominationExistsForCaller", async () => { - const nominationExists = await hardhatProxyRegister.connect(addr1.address).nominationExistsForCaller() - expect(nominationExists).to.equal(true); - }) - - it("getNomination", async () => { - const proxy = await hardhatProxyRegister.getNomination(addr1.address) - expect(proxy).to.equal(addr2.address); - }) - - it("getNominationForCaller", async () => { - const proxy = await hardhatProxyRegister.connect(addr1.address).getNominationForCaller() - expect(proxy).to.equal(addr2.address); - }) - - it("proxyRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.proxyRecordExists(addr2.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("proxyRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).proxyRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.nominatorRecordExists(addr1.address) - expect(proxyRecordExists).to.equal(false); - }) - - it("nominatorRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(false); - }) - - it("addressIsActive", async () => { - const addressIsActive = await hardhatProxyRegister.addressIsActive(addr1.address) - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.addressIsActive(addr2.address) - expect(addressIsActive2).to.equal(false); - }) - - it("addressIsActiveForCaller", async () => { - const addressIsActive = await hardhatProxyRegister.connect(addr1.address).addressIsActiveForCaller() - expect(addressIsActive).to.equal(false); - - const addressIsActive2 = await hardhatProxyRegister.connect(addr2.address).addressIsActiveForCaller() - expect(addressIsActive2).to.equal(false); - }) - - it("getProxyRecord", async () => { - const entry = await hardhatProxyRegister.getProxyRecord(addr2.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getProxyRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getProxyRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecord", async () => { - const entry = await hardhatProxyRegister.getNominatorRecord(addr1.address) - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getNominatorRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr1.address).getNominatorRecordForCaller() - expect(entry[0]).to.equal(ZERO_ADDRESS); - expect(entry[1]).to.equal(ZERO_ADDRESS); - expect(entry[2]).to.equal(ZERO_ADDRESS); - }) - - it("getAddresses", async () => { - const entry = await hardhatProxyRegister.getAddresses(addr2.address) - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - - it("getAddressesForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getAddressesForCaller() - expect(entry[0]).to.equal(addr2.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(false); - }) - }) - - describe("With Proxy Active", function () { - - beforeEach(async function () { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - }) - - it("nominationExists", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(true); - }) - - it("nominationExistsForCaller", async () => { - const nominationExists = await hardhatProxyRegister.connect(addr1.address).nominationExistsForCaller() - expect(nominationExists).to.equal(true); - }) - - it("getNomination", async () => { - const proxy = await hardhatProxyRegister.getNomination(addr1.address) - expect(proxy).to.equal(addr2.address); - }) - - it("getNominationForCaller", async () => { - const proxy = await hardhatProxyRegister.connect(addr1.address).getNominationForCaller() - expect(proxy).to.equal(addr2.address); - }) - - it("proxyRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.proxyRecordExists(addr2.address) - expect(proxyRecordExists).to.equal(true); - }) - - it("proxyRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr2.address).proxyRecordExistsForCaller() - expect(proxyRecordExists).to.equal(true); - }) - - it("nominatorRecordExists", async () => { - const proxyRecordExists = await hardhatProxyRegister.nominatorRecordExists(addr1.address) - expect(proxyRecordExists).to.equal(true); - }) - - it("nominatorRecordExistsForCaller", async () => { - const proxyRecordExists = await hardhatProxyRegister.connect(addr1.address).nominatorRecordExistsForCaller() - expect(proxyRecordExists).to.equal(true); - }) - - it("addressIsActive", async () => { - const addressIsActive = await hardhatProxyRegister.addressIsActive(addr1.address) - expect(addressIsActive).to.equal(true); - - const addressIsActive2 = await hardhatProxyRegister.addressIsActive(addr2.address) - expect(addressIsActive2).to.equal(true); - }) - - it("addressIsActiveForCaller", async () => { - const addressIsActive = await hardhatProxyRegister.connect(addr1.address).addressIsActiveForCaller() - expect(addressIsActive).to.equal(true); - - const addressIsActive2 = await hardhatProxyRegister.connect(addr2.address).addressIsActiveForCaller() - expect(addressIsActive2).to.equal(true); - }) - - it("getProxyRecord", async () => { - const entry = await hardhatProxyRegister.getProxyRecord(addr2.address) - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getProxyRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getProxyRecordForCaller() - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getNominatorRecord", async () => { - const entry = await hardhatProxyRegister.getNominatorRecord(addr1.address) - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getNominatorRecordForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr1.address).getNominatorRecordForCaller() - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr2.address); - expect(entry[2]).to.equal(addr3.address); - }) - - it("getAddresses", async () => { - const entry = await hardhatProxyRegister.getAddresses(addr2.address) - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr3.address); - expect(entry[2]).to.equal(true); - }) - - it("getAddressesForCaller", async () => { - const entry = await hardhatProxyRegister.connect(addr2.address).getAddressesForCaller() - expect(entry[0]).to.equal(addr1.address); - expect(entry[1]).to.equal(addr3.address); - expect(entry[2]).to.equal(true); - }) - - it("getAddresses for nominator with valid proxy", async () => { - const nominationExists = await hardhatProxyRegister.nominationExists(addr1.address) - expect(nominationExists).to.equal(true); - await expect( - hardhatProxyRegister.getAddresses(addr1.address) - ).to.be.revertedWith("Nominator address cannot interact directly, only through the proxy address") - }) - - it("getAddressesForCaller for nominator with valid proxy", async () => { - await expect( - hardhatProxyRegister.connect(addr1.address).getAddressesForCaller() - ).to.be.revertedWith("Nominator address cannot interact directly, only through the proxy address") - }) - }) - - describe("Role", function () { - it("Returns none with no proxy or nomination", async () => { - var role = await hardhatProxyRegister.getRole(addr1.address) - expect(role).to.equal("None"); - - role = await hardhatProxyRegister.connect(addr1.address).getRoleForCaller() - expect(role).to.equal("None"); - }) - - it("Returns pending with unaccepted nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var role = await hardhatProxyRegister.getRole(addr1.address) - expect(role).to.equal("Nominator - Proxy Pending"); - - role = await hardhatProxyRegister.connect(addr1.address).getRoleForCaller() - expect(role).to.equal("Nominator - Proxy Pending"); - }) - - it("Returns active with accepted nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - var role = await hardhatProxyRegister.getRole(addr1.address) - expect(role).to.equal("Nominator - Proxy Active"); - - role = await hardhatProxyRegister.connect(addr1.address).getRoleForCaller() - expect(role).to.equal("Nominator - Proxy Active"); - }) - - it("Returns proxy with unaccepted nomination", async () => { - var tx1 = await hardhatProxyRegister - .connect(addr1) - .makeNomination( addr2.address, 1, { - value: ethers.utils.parseEther(registerFee.toString()), - }) - expect(tx1).to.emit(hardhatProxyRegister, "NominationMade") - - var tx2 = await hardhatProxyRegister - .connect(addr2) - .acceptNomination( addr1.address, addr3.address, 1) - expect(tx2).to.emit(hardhatProxyRegister, "NominationAccepted") - - var role = await hardhatProxyRegister.getRole(addr2.address) - expect(role).to.equal("Proxy"); - - role = await hardhatProxyRegister.connect(addr2.address).getRoleForCaller() - expect(role).to.equal("Proxy"); - }) - }) - - }); -}) diff --git a/assets/eip-4907/.gitignore b/assets/eip-4907/.gitignore deleted file mode 100644 index 504afef81fbadc..00000000000000 --- a/assets/eip-4907/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -package-lock.json diff --git a/assets/eip-4907/README.md b/assets/eip-4907/README.md deleted file mode 100644 index 720f0849dcc6ef..00000000000000 --- a/assets/eip-4907/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# EIP-4907 -EIP-4907 is an extension of ERC-721. It proposes an additional role **user** and a valid duration indicator **expires**. It allows users and developers manage the use right more simple and efficient. - -### Tools -* [Visual Studio Code](https://code.visualstudio.com/) -* [Solidity](https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity) - Solidity support for Visual Studio code -* [Truffle](https://truffleframework.com/) - the most popular development framework for Ethereum - -### Install -``` -npm install -``` - -### Test -``` -truffle test -``` - -### Additional Resources -* [Official Truffle Documentation](http://truffleframework.com/docs/) for complete and detailed guides, tips, and sample code. \ No newline at end of file diff --git a/assets/eip-4907/contracts/ERC4907.sol b/assets/eip-4907/contracts/ERC4907.sol deleted file mode 100644 index 6bc044f98dc674..00000000000000 --- a/assets/eip-4907/contracts/ERC4907.sol +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC4907.sol"; - -contract ERC4907 is ERC721, IERC4907 { - struct UserInfo - { - address user; // address of user role - uint64 expires; // unix timestamp, user expires - } - - mapping (uint256 => UserInfo) internal _users; - - constructor(string memory name_, string memory symbol_) - ERC721(name_,symbol_) - { - } - - /// @notice set the user and expires of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - function setUser(uint256 tokenId, address user, uint64 expires) public virtual{ - require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: transfer caller is not owner nor approved"); - UserInfo storage info = _users[tokenId]; - info.user = user; - info.expires = expires; - emit UpdateUser(tokenId,user,expires); - } - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId)public view virtual returns(address){ - if( uint256(_users[tokenId].expires) >= block.timestamp){ - return _users[tokenId].user; - } - else{ - return address(0); - } - } - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) public view virtual returns(uint256){ - return _users[tokenId].expires; - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC4907).interfaceId || super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId); - - if (from != to && _users[tokenId].user != address(0)) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0); - } - } -} diff --git a/assets/eip-4907/contracts/ERC4907Demo.sol b/assets/eip-4907/contracts/ERC4907Demo.sol deleted file mode 100644 index daa6d9956cb34e..00000000000000 --- a/assets/eip-4907/contracts/ERC4907Demo.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC4907.sol"; - -contract ERC4907Demo is ERC4907 { - - constructor(string memory name_, string memory symbol_) - ERC4907(name_,symbol_) - { - } - - function mint(uint256 tokenId, address to) public { - _mint(to, tokenId); - } - -} diff --git a/assets/eip-4907/contracts/IERC4907.sol b/assets/eip-4907/contracts/IERC4907.sol deleted file mode 100644 index 9858dfe9ecc118..00000000000000 --- a/assets/eip-4907/contracts/IERC4907.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC4907 { - // Logged when the user of a token assigns a new user or updates expires - /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed - /// The zero address for user indicates that there is no user address - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires); - - /// @notice set the user and expires of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - function setUser(uint256 tokenId, address user, uint64 expires) external ; - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId) external view returns(address); - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) external view returns(uint256); -} diff --git a/assets/eip-4907/migrations/1_initial_migration.js b/assets/eip-4907/migrations/1_initial_migration.js deleted file mode 100644 index 1eb915e52305bf..00000000000000 --- a/assets/eip-4907/migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -const ERC4907Demo = artifacts.require("ERC4907Demo"); - -module.exports = function (deployer) { - deployer.deploy(ERC4907Demo, "ERC4907Demo", "ERC4907Demo"); -}; diff --git a/assets/eip-4907/package.json b/assets/eip-4907/package.json deleted file mode 100644 index 9104d3de2abfa2..00000000000000 --- a/assets/eip-4907/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "ERC-4907", - "version": "1.0.0", - "description": "", - "main": "truffle-config.js", - "directories": { - "test": "test" - }, - "scripts": {}, - "keywords": [], - "author": "", - "license": "CC0-1.0", - "dependencies": { - "@openzeppelin/contracts": "^4.3.3", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "chai": "^4.3.6" - } -} diff --git a/assets/eip-4907/test/test.js b/assets/eip-4907/test/test.js deleted file mode 100644 index f2df2ed2e8b38c..00000000000000 --- a/assets/eip-4907/test/test.js +++ /dev/null @@ -1,25 +0,0 @@ -const { assert } = require("chai"); - -const ERC4907Demo = artifacts.require("ERC4907Demo"); - -contract("test", async (accounts) => { - it("should set user to Bob", async () => { - // Get initial balances of first and second account. - const Alice = accounts[0]; - const Bob = accounts[1]; - - const instance = await ERC4907Demo.deployed("T", "T"); - const demo = instance; - - await demo.mint(1, Alice); - let expires = Math.floor(new Date().getTime() / 1000) + 1000; - await demo.setUser(1, Bob, BigInt(expires)); - - let user_1 = await demo.userOf(1); - - assert.equal(user_1, Bob, "User of NFT 1 should be Bob"); - - let owner_1 = await demo.ownerOf(1); - assert.equal(owner_1, Alice, "Owner of NFT 1 should be Alice"); - }); -}); diff --git a/assets/eip-4907/truffle-config.js b/assets/eip-4907/truffle-config.js deleted file mode 100644 index 3517e055e93ec1..00000000000000 --- a/assets/eip-4907/truffle-config.js +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Use this file to configure your truffle project. It's seeded with some - * common settings for different networks and features like migrations, - * compilation and testing. Uncomment the ones you need or modify - * them to suit your project as necessary. - * - * More information about configuration can be found at: - * - * trufflesuite.com/docs/advanced/configuration - * - * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) - * to sign your transactions before they're sent to a remote public node. Infura accounts - * are available for free at: infura.io/register. - * - * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate - * public/private key pairs. If you're publishing your code to GitHub make sure you load this - * phrase from a file you've .gitignored so it doesn't accidentally become public. - * - */ - -// const HDWalletProvider = require('@truffle/hdwallet-provider'); -// -// const fs = require('fs'); -// const mnemonic = fs.readFileSync(".secret").toString().trim(); - -module.exports = { - /** - * Networks define how you connect to your ethereum client and let you set the - * defaults web3 uses to send transactions. If you don't specify one truffle - * will spin up a development blockchain for you on port 9545 when you - * run `develop` or `test`. You can ask a truffle command to use a specific - * network from the command line, e.g - * - * $ truffle test --network - */ - - networks: { - // Useful for testing. The `development` name is special - truffle uses it by default - // if it's defined here and no other network is specified at the command line. - // You should run a client (like ganache-cli, geth or parity) in a separate terminal - // tab if you use this network and you must also set the `host`, `port` and `network_id` - // options below to some value. - // - // development: { - // host: "127.0.0.1", // Localhost (default: none) - // port: 8545, // Standard Ethereum port (default: none) - // network_id: "*", // Any network (default: none) - // }, - // Another network with more advanced options... - // advanced: { - // port: 8777, // Custom port - // network_id: 1342, // Custom network - // gas: 8500000, // Gas sent with each transaction (default: ~6700000) - // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) - // from:
, // Account to send txs from (default: accounts[0]) - // websocket: true // Enable EventEmitter interface for web3 (default: false) - // }, - // Useful for deploying to a public network. - // NB: It's important to wrap the provider as a function. - // ropsten: { - // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), - // network_id: 3, // Ropsten's id - // gas: 5500000, // Ropsten has a lower block limit than mainnet - // confirmations: 2, // # of confs to wait between deployments. (default: 0) - // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) - // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) - // }, - // Useful for private networks - // private: { - // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), - // network_id: 2111, // This network is yours, in the cloud. - // production: true // Treats this network as if it was a public net. (default: false) - // } - }, - - // Set default mocha options here, use special reporters etc. - mocha: { - // timeout: 100000 - }, - - // Configure your compilers - compilers: { - solc: { - version: "0.8.10", // Fetch exact version from solc-bin (default: truffle's version) - // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) - settings: { - // See the solidity docs for advice about optimization and evmVersion - optimizer: { - enabled: false, - runs: 200, - }, - // , - // evmVersion: "byzantium" - // } - }, - }, - - // Truffle DB is currently disabled by default; to enable it, change enabled: - // false to enabled: true. The default storage location can also be - // overridden by specifying the adapter settings, as shown in the commented code below. - // - // NOTE: It is not possible to migrate your contracts to truffle DB and you should - // make a backup of your artifacts to a safe location before enabling this feature. - // - // After you backed up your artifacts you can utilize db by running migrate as follows: - // $ truffle migrate --reset --compile-all - // - // db: { - // enabled: false, - // host: "127.0.0.1", - // adapter: { - // name: "sqlite", - // settings: { - // directory: ".db" - // } - // } - }, -}; diff --git a/assets/eip-4910/eip-4910-print-families.png b/assets/eip-4910/eip-4910-print-families.png deleted file mode 100644 index 30815eed361dcb..00000000000000 Binary files a/assets/eip-4910/eip-4910-print-families.png and /dev/null differ diff --git a/assets/eip-4910/eip-4910-royalties.png b/assets/eip-4910/eip-4910-royalties.png deleted file mode 100644 index ddf6951d02a39a..00000000000000 Binary files a/assets/eip-4910/eip-4910-royalties.png and /dev/null differ diff --git a/assets/eip-4955/different-renders.jpeg b/assets/eip-4955/different-renders.jpeg deleted file mode 100644 index 6a36d1133b3d5f..00000000000000 Binary files a/assets/eip-4955/different-renders.jpeg and /dev/null differ diff --git a/assets/eip-4973/ERC4973-flat.sol b/assets/eip-4973/ERC4973-flat.sol deleted file mode 100644 index e20aa82208f0ae..00000000000000 --- a/assets/eip-4973/ERC4973-flat.sol +++ /dev/null @@ -1,1028 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.8; - -// OpenZeppelin Contracts (last updated v4.7.1) (utils/cryptography/SignatureChecker.sol) - -// OpenZeppelin Contracts (last updated v4.7.3) (utils/cryptography/ECDSA.sol) - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol) - -/** - * @dev String operations. - */ -library Strings { - bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef"; - uint8 private constant _ADDRESS_LENGTH = 20; - - /** - * @dev Converts a `uint256` to its ASCII `string` decimal representation. - */ - function toString(uint256 value) internal pure returns (string memory) { - // Inspired by OraclizeAPI's implementation - MIT licence - // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol - - if (value == 0) { - return "0"; - } - uint256 temp = value; - uint256 digits; - while (temp != 0) { - digits++; - temp /= 10; - } - bytes memory buffer = new bytes(digits); - while (value != 0) { - digits -= 1; - buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); - value /= 10; - } - return string(buffer); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. - */ - function toHexString(uint256 value) internal pure returns (string memory) { - if (value == 0) { - return "0x00"; - } - uint256 temp = value; - uint256 length = 0; - while (temp != 0) { - length++; - temp >>= 8; - } - return toHexString(value, length); - } - - /** - * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. - */ - function toHexString(uint256 value, uint256 length) internal pure returns (string memory) { - bytes memory buffer = new bytes(2 * length + 2); - buffer[0] = "0"; - buffer[1] = "x"; - for (uint256 i = 2 * length + 1; i > 1; --i) { - buffer[i] = _HEX_SYMBOLS[value & 0xf]; - value >>= 4; - } - require(value == 0, "Strings: hex length insufficient"); - return string(buffer); - } - - /** - * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. - */ - function toHexString(address addr) internal pure returns (string memory) { - return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); - } -} - -/** - * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. - * - * These functions can be used to verify that a message was signed by the holder - * of the private keys of a given address. - */ -library ECDSA { - enum RecoverError { - NoError, - InvalidSignature, - InvalidSignatureLength, - InvalidSignatureS, - InvalidSignatureV - } - - function _throwError(RecoverError error) private pure { - if (error == RecoverError.NoError) { - return; // no error: do nothing - } else if (error == RecoverError.InvalidSignature) { - revert("ECDSA: invalid signature"); - } else if (error == RecoverError.InvalidSignatureLength) { - revert("ECDSA: invalid signature length"); - } else if (error == RecoverError.InvalidSignatureS) { - revert("ECDSA: invalid signature 's' value"); - } else if (error == RecoverError.InvalidSignatureV) { - revert("ECDSA: invalid signature 'v' value"); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature` or error string. This address can then be used for verification purposes. - * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. - * - * Documentation for signature generation: - * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] - * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] - * - * _Available since v4.3._ - */ - function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) { - if (signature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - // ecrecover takes the signature parameters, and the only way to get them - // currently is to use assembly. - /// @solidity memory-safe-assembly - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - return tryRecover(hash, v, r, s); - } else { - return (address(0), RecoverError.InvalidSignatureLength); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature`. This address can then be used for verification purposes. - * - * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {toEthSignedMessageHash} on it. - */ - function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, signature); - _throwError(error); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. - * - * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] - * - * _Available since v4.3._ - */ - function tryRecover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address, RecoverError) { - bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - uint8 v = uint8((uint256(vs) >> 255) + 27); - return tryRecover(hash, v, r, s); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. - * - * _Available since v4.2._ - */ - function recover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, r, vs); - _throwError(error); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `v`, - * `r` and `s` signature fields separately. - * - * _Available since v4.3._ - */ - function tryRecover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address, RecoverError) { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return (address(0), RecoverError.InvalidSignatureS); - } - if (v != 27 && v != 28) { - return (address(0), RecoverError.InvalidSignatureV); - } - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(hash, v, r, s); - if (signer == address(0)) { - return (address(0), RecoverError.InvalidSignature); - } - - return (signer, RecoverError.NoError); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `v`, - * `r` and `s` signature fields separately. - */ - function recover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address) { - (address recovered, RecoverError error) = tryRecover(hash, v, r, s); - _throwError(error); - return recovered; - } - - /** - * @dev Returns an Ethereum Signed Message, created from a `hash`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. - */ - function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) { - // 32 is the length in bytes of hash, - // enforced by the type signature above - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); - } - - /** - * @dev Returns an Ethereum Signed Message, created from `s`. This - * produces hash corresponding to the one signed with the - * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] - * JSON-RPC method as part of EIP-191. - * - * See {recover}. - */ - function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", Strings.toString(s.length), s)); - } - - /** - * @dev Returns an Ethereum Signed Typed Data, created from a - * `domainSeparator` and a `structHash`. This produces hash corresponding - * to the one signed with the - * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] - * JSON-RPC method as part of EIP-712. - * - * See {recover}. - */ - function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - } -} - -// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol) - -/** - * @dev Collection of functions related to the address type - */ -library Address { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - * - * [IMPORTANT] - * ==== - * You shouldn't rely on `isContract` to protect against flash loan attacks! - * - * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets - * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract - * constructor. - * ==== - */ - function isContract(address account) internal view returns (bool) { - // This method relies on extcodesize/address.code.length, which returns 0 - // for contracts in construction, since the code is only stored at the end - // of the constructor execution. - - return account.code.length > 0; - } - - /** - * @dev Replacement for Solidity's `transfer`: sends `amount` wei to - * `recipient`, forwarding all available gas and reverting on errors. - * - * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost - * of certain opcodes, possibly making contracts go over the 2300 gas limit - * imposed by `transfer`, making them unable to receive funds via - * `transfer`. {sendValue} removes this limitation. - * - * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. - * - * IMPORTANT: because control is transferred to `recipient`, care must be - * taken to not create reentrancy vulnerabilities. Consider using - * {ReentrancyGuard} or the - * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. - */ - function sendValue(address payable recipient, uint256 amount) internal { - require(address(this).balance >= amount, "Address: insufficient balance"); - - (bool success, ) = recipient.call{value: amount}(""); - require(success, "Address: unable to send value, recipient may have reverted"); - } - - /** - * @dev Performs a Solidity function call using a low level `call`. A - * plain `call` is an unsafe replacement for a function call: use this - * function instead. - * - * If `target` reverts with a revert reason, it is bubbled up by this - * function (like regular Solidity function calls). - * - * Returns the raw returned data. To convert to the expected return value, - * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. - * - * Requirements: - * - * - `target` must be a contract. - * - calling `target` with `data` must not revert. - * - * _Available since v3.1._ - */ - function functionCall(address target, bytes memory data) internal returns (bytes memory) { - return functionCall(target, data, "Address: low-level call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with - * `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, 0, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but also transferring `value` wei to `target`. - * - * Requirements: - * - * - the calling contract must have an ETH balance of at least `value`. - * - the called Solidity function must be `payable`. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value - ) internal returns (bytes memory) { - return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); - } - - /** - * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but - * with `errorMessage` as a fallback revert reason when `target` reverts. - * - * _Available since v3.1._ - */ - function functionCallWithValue( - address target, - bytes memory data, - uint256 value, - string memory errorMessage - ) internal returns (bytes memory) { - require(address(this).balance >= value, "Address: insufficient balance for call"); - require(isContract(target), "Address: call to non-contract"); - - (bool success, bytes memory returndata) = target.call{value: value}(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { - return functionStaticCall(target, data, "Address: low-level static call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a static call. - * - * _Available since v3.3._ - */ - function functionStaticCall( - address target, - bytes memory data, - string memory errorMessage - ) internal view returns (bytes memory) { - require(isContract(target), "Address: static call to non-contract"); - - (bool success, bytes memory returndata) = target.staticcall(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { - return functionDelegateCall(target, data, "Address: low-level delegate call failed"); - } - - /** - * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], - * but performing a delegate call. - * - * _Available since v3.4._ - */ - function functionDelegateCall( - address target, - bytes memory data, - string memory errorMessage - ) internal returns (bytes memory) { - require(isContract(target), "Address: delegate call to non-contract"); - - (bool success, bytes memory returndata) = target.delegatecall(data); - return verifyCallResult(success, returndata, errorMessage); - } - - /** - * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason using the provided one. - * - * _Available since v4.3._ - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} - -// OpenZeppelin Contracts v4.4.1 (interfaces/IERC1271.sol) - -/** - * @dev Interface of the ERC1271 standard signature validation method for - * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271]. - * - * _Available since v4.1._ - */ -interface IERC1271 { - /** - * @dev Should return whether the signature provided is valid for the provided data - * @param hash Hash of the data to be signed - * @param signature Signature byte array associated with _data - */ - function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); -} - -/** - * @dev Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA - * signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like - * Argent and Gnosis Safe. - * - * _Available since v4.1._ - */ -library SignatureChecker { - /** - * @dev Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the - * signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`. - * - * NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus - * change through time. It could return true at block N and false at block N+1 (or the opposite). - */ - function isValidSignatureNow( - address signer, - bytes32 hash, - bytes memory signature - ) internal view returns (bool) { - (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature); - if (error == ECDSA.RecoverError.NoError && recovered == signer) { - return true; - } - - (bool success, bytes memory result) = signer.staticcall( - abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature) - ); - return (success && - result.length == 32 && - abi.decode(result, (bytes32)) == bytes32(IERC1271.isValidSignature.selector)); - } -} - -// OpenZeppelin Contracts v4.4.1 (utils/cryptography/draft-EIP712.sol) - -/** - * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. - * - * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, - * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding - * they need in their contracts using a combination of `abi.encode` and `keccak256`. - * - * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding - * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA - * ({_hashTypedDataV4}). - * - * The implementation of the domain separator was designed to be as efficient as possible while still properly updating - * the chain id to protect against replay attacks on an eventual fork of the chain. - * - * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method - * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. - * - * _Available since v3.4._ - */ -abstract contract EIP712 { - /* solhint-disable var-name-mixedcase */ - // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to - // invalidate the cached domain separator if the chain id changes. - bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; - uint256 private immutable _CACHED_CHAIN_ID; - address private immutable _CACHED_THIS; - - bytes32 private immutable _HASHED_NAME; - bytes32 private immutable _HASHED_VERSION; - bytes32 private immutable _TYPE_HASH; - - /* solhint-enable var-name-mixedcase */ - - /** - * @dev Initializes the domain separator and parameter caches. - * - * The meaning of `name` and `version` is specified in - * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: - * - * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. - * - `version`: the current major version of the signing domain. - * - * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart - * contract upgrade]. - */ - constructor(string memory name, string memory version) { - bytes32 hashedName = keccak256(bytes(name)); - bytes32 hashedVersion = keccak256(bytes(version)); - bytes32 typeHash = keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ); - _HASHED_NAME = hashedName; - _HASHED_VERSION = hashedVersion; - _CACHED_CHAIN_ID = block.chainid; - _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion); - _CACHED_THIS = address(this); - _TYPE_HASH = typeHash; - } - - /** - * @dev Returns the domain separator for the current chain. - */ - function _domainSeparatorV4() internal view returns (bytes32) { - if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) { - return _CACHED_DOMAIN_SEPARATOR; - } else { - return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); - } - } - - function _buildDomainSeparator( - bytes32 typeHash, - bytes32 nameHash, - bytes32 versionHash - ) private view returns (bytes32) { - return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this))); - } - - /** - * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this - * function returns the hash of the fully encoded EIP712 message for this domain. - * - * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: - * - * ```solidity - * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( - * keccak256("Mail(address to,string contents)"), - * mailTo, - * keccak256(bytes(mailContents)) - * ))); - * address signer = ECDSA.recover(digest, signature); - * ``` - */ - function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { - return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); - } -} - -// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol) - -// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) - -/** - * @dev Interface of the ERC165 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-165[EIP]. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others ({ERC165Checker}). - * - * For an implementation, see {ERC165}. - */ -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -/** - * @dev Implementation of the {IERC165} interface. - * - * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check - * for the additional interface id that will be supported. For example: - * - * ```solidity - * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); - * } - * ``` - * - * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation. - */ -abstract contract ERC165 is IERC165 { - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC165).interfaceId; - } -} - -// OpenZeppelin Contracts v4.4.1 (utils/structs/BitMaps.sol) - -/** - * @dev Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. - * Largelly inspired by Uniswap's https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol[merkle-distributor]. - */ -library BitMaps { - struct BitMap { - mapping(uint256 => uint256) _data; - } - - /** - * @dev Returns whether the bit at `index` is set. - */ - function get(BitMap storage bitmap, uint256 index) internal view returns (bool) { - uint256 bucket = index >> 8; - uint256 mask = 1 << (index & 0xff); - return bitmap._data[bucket] & mask != 0; - } - - /** - * @dev Sets the bit at `index` to the boolean `value`. - */ - function setTo( - BitMap storage bitmap, - uint256 index, - bool value - ) internal { - if (value) { - set(bitmap, index); - } else { - unset(bitmap, index); - } - } - - /** - * @dev Sets the bit at `index`. - */ - function set(BitMap storage bitmap, uint256 index) internal { - uint256 bucket = index >> 8; - uint256 mask = 1 << (index & 0xff); - bitmap._data[bucket] |= mask; - } - - /** - * @dev Unsets the bit at `index`. - */ - function unset(BitMap storage bitmap, uint256 index) internal { - uint256 bucket = index >> 8; - uint256 mask = 1 << (index & 0xff); - bitmap._data[bucket] &= ~mask; - } -} - -interface IERC721Metadata { - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function tokenURI(uint256 tokenId) external view returns (string memory); -} - -/// @title Account-bound tokens -/// @dev See https://eips.ethereum.org/EIPS/eip-4973 -/// Note: the ERC-165 identifier for this interface is 0xeb72bb7c -interface IERC4973 { - /// @dev This emits when ownership of any ABT changes by any mechanism. - /// This event emits when ABTs are given or equipped and unequipped - /// (`to` == 0). - event Transfer( - address indexed from, address indexed to, uint256 indexed tokenId - ); - /// @notice Count all ABTs assigned to an owner - /// @dev ABTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param owner An address for whom to query the balance - /// @return The number of ABTs owned by `address owner`, possibly zero - - function balanceOf(address owner) external view returns (uint256); - /// @notice Find the address bound to an ERC4973 account-bound token - /// @dev ABTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param tokenId The identifier for an ABT. - /// @return The address of the owner bound to the ABT. - function ownerOf(uint256 tokenId) external view returns (address); - /// @notice Removes the `uint256 tokenId` from an account. At any time, an - /// ABT receiver must be able to disassociate themselves from an ABT - /// publicly through calling this function. After successfully executing this - /// function, given the parameters for calling `function give` or - /// `function take` a token must be re-equipable. - /// @dev Must emit a `event Transfer` with the `address to` field pointing to - /// the zero address. - /// @param tokenId The identifier for an ABT. - function unequip(uint256 tokenId) external; - /// @notice Creates and transfers the ownership of an ABT from the - /// transaction's `msg.sender` to `address to`. - /// @dev Throws unless `bytes signature` represents a signature of the - // EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` expressing - /// `address to`'s explicit agreement to be publicly associated with - /// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be - /// generated by type-casting the `bytes32` EIP-712 structured data hash to a - /// `uint256`. If `bytes signature` is empty or `address to` is a contract, - /// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must - /// be made to `address to`. A successful execution must result in the - /// `event Transfer(msg.sender, to, tokenId)`. Once an ABT exists as an - /// `uint256 tokenId` in the contract, `function give(...)` must throw. - /// @param to The receiver of the ABT. - /// @param metadata The metadata that will be associated to the ABT. - /// @param signature A signature of the EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` signed by - /// `address to`. - /// @return A unique `uint256 tokenId` generated by type-casting the `bytes32` - /// EIP-712 structured data hash to a `uint256`. - function give(address to, bytes calldata metadata, bytes calldata signature) - external - returns (uint256); - /// @notice Creates and transfers the ownership of an ABT from an - /// `address from` to the transaction's `msg.sender`. - /// @dev Throws unless `bytes signature` represents a signature of the - /// EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` expressing - /// `address from`'s explicit agreement to be publicly associated with - /// `msg.sender` and `bytes metadata`. A unique `uint256 tokenId` must be - /// generated by type-casting the `bytes32` EIP-712 structured data hash to a - /// `uint256`. If `bytes signature` is empty or `address from` is a contract, - /// an EIP-1271-compatible call to `function isValidSignatureNow(...)` must - /// be made to `address from`. A successful execution must result in the - /// emission of an `event Transfer(from, msg.sender, tokenId)`. Once an ABT - /// exists as an `uint256 tokenId` in the contract, `function take(...)` must - /// throw. - /// @param from The origin of the ABT. - /// @param metadata The metadata that will be associated to the ABT. - /// @param signature A signature of the EIP-712 structured data hash - /// `Agreement(address active,address passive,bytes metadata)` signed by - /// `address from`. - /// @return A unique `uint256 tokenId` generated by type-casting the `bytes32` - /// EIP-712 structured data hash to a `uint256`. - function take(address from, bytes calldata metadata, bytes calldata signature) - external - returns (uint256); - /// @notice Decodes the opaque metadata bytestring of an ABT into the token - /// URI that will be associated with it once it is created on chain. - /// @param metadata The metadata that will be associated to an ABT. - /// @return A URI that represents the metadata. - function decodeURI(bytes calldata metadata) external returns (string memory); -} - -bytes32 constant AGREEMENT_HASH = - keccak256("Agreement(address active,address passive,bytes metadata)"); - -/// @notice Reference implementation of EIP-4973 tokens. -/// @author Tim Daubenschütz, Rahul Rumalla (https://github.com/rugpullindex/ERC4973/blob/master/src/ERC4973.sol) -abstract contract ERC4973 is EIP712, ERC165, IERC721Metadata, IERC4973 { - using BitMaps for BitMaps.BitMap; - - BitMaps.BitMap private _usedHashes; - - string private _name; - string private _symbol; - - mapping(uint256 => address) private _owners; - mapping(uint256 => string) private _tokenURIs; - mapping(address => uint256) private _balances; - - constructor(string memory name_, string memory symbol_, string memory version) - EIP712(name_, version) - { - _name = name_; - _symbol = symbol_; - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return interfaceId == type(IERC721Metadata).interfaceId - || interfaceId == type(IERC4973).interfaceId - || super.supportsInterface(interfaceId); - } - - function name() public view virtual override returns (string memory) { - return _name; - } - - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - function tokenURI(uint256 tokenId) - public - view - virtual - override - returns (string memory) - { - require(_exists(tokenId), "tokenURI: token doesn't exist"); - return _tokenURIs[tokenId]; - } - - function unequip(uint256 tokenId) public virtual override { - require(msg.sender == ownerOf(tokenId), "unequip: sender must be owner"); - _usedHashes.unset(tokenId); - _burn(tokenId); - } - - function balanceOf(address owner) - public - view - virtual - override - returns (uint256) - { - require(owner != address(0), "balanceOf: address zero is not a valid owner"); - return _balances[owner]; - } - - function ownerOf(uint256 tokenId) public view virtual returns (address) { - address owner = _owners[tokenId]; - require(owner != address(0), "ownerOf: token doesn't exist"); - return owner; - } - - function give(address to, bytes calldata metadata, bytes calldata signature) - external - virtual - returns (uint256) - { - require(msg.sender != to, "give: cannot give from self"); - uint256 tokenId = _safeCheckAgreement(msg.sender, to, metadata, signature); - string memory uri = decodeURI(metadata); - _mint(msg.sender, to, tokenId, uri); - _usedHashes.set(tokenId); - return tokenId; - } - - function take(address from, bytes calldata metadata, bytes calldata signature) - external - virtual - returns (uint256) - { - require(msg.sender != from, "take: cannot take from self"); - uint256 tokenId = _safeCheckAgreement(msg.sender, from, metadata, signature); - string memory uri = decodeURI(metadata); - _mint(from, msg.sender, tokenId, uri); - _usedHashes.set(tokenId); - return tokenId; - } - - function decodeURI(bytes calldata metadata) - public - virtual - returns (string memory) - { - return string(metadata); - } - - function _safeCheckAgreement( - address active, - address passive, - bytes calldata metadata, - bytes calldata signature - ) - internal - virtual - returns (uint256) - { - bytes32 hash = _getHash(active, passive, metadata); - uint256 tokenId = uint256(hash); - - require( - SignatureChecker.isValidSignatureNow(passive, hash, signature), - "_safeCheckAgreement: invalid signature" - ); - require(!_usedHashes.get(tokenId), "_safeCheckAgreement: already used"); - return tokenId; - } - - function _getHash(address active, address passive, bytes calldata metadata) - internal - view - returns (bytes32) - { - bytes32 structHash = - keccak256(abi.encode(AGREEMENT_HASH, active, passive, keccak256(metadata))); - return _hashTypedDataV4(structHash); - } - - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _owners[tokenId] != address(0); - } - - function _mint(address from, address to, uint256 tokenId, string memory uri) - internal - virtual - returns (uint256) - { - require(!_exists(tokenId), "mint: tokenID exists"); - _balances[to] += 1; - _owners[tokenId] = to; - _tokenURIs[tokenId] = uri; - emit Transfer(from, to, tokenId); - return tokenId; - } - - function _burn(uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - - _balances[owner] -= 1; - delete _owners[tokenId]; - delete _tokenURIs[tokenId]; - - emit Transfer(owner, address(0), tokenId); - } -} - diff --git a/assets/eip-4973/package.json b/assets/eip-4973/package.json deleted file mode 100644 index 496d41afa50b0f..00000000000000 --- a/assets/eip-4973/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "erc4973", - "version": "0.4.0", - "description": "A standard interface for non-transferrable non-fungible tokens, also known as \"account-bound\" or \"soulbound tokens\" or \"badges\".", - "files": [ - "/src/ERC4973.sol", - "/src/ERC165.sol", - "/src/interfaces/IERC165.sol", - "/src/interfaces/IERC4973.sol", - "/src/interfaces/IERC721Metadata.sol", - "/sdk/src/index.mjs" - ], - "scripts": { - "test": "forge test", - "test:sdk": "ava sdk/test", - "gen:flatfile": "forge flatten src/ERC4973.sol > ./assets/ERC4973-flat.sol", - "gen:sdk": "cp package.json assets/package.json && cp -r sdk/ assets/sdk", - "gen:assets": "npm run gen:flatfile && npm run gen:sdk" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/rugpullindex/ERC4973.git" - }, - "keywords": [ - "account-bound", - "soulbound", - "tokens", - "ethereum", - "eip", - "badges", - "non-transferrable" - ], - "author": "Tim Daubenschütz (https://timdaub.github.io/)", - "license": "CC0-1.0", - "bugs": { - "url": "https://github.com/rugpullindex/ERC4973/issues" - }, - "homepage": "https://github.com/rugpullindex/ERC4973#readme", - "dependencies": { - "ethers": "5.7.2" - }, - "devDependencies": { - "ava": "4.3.1" - } -} diff --git a/assets/eip-4973/sdk/src/index.mjs b/assets/eip-4973/sdk/src/index.mjs deleted file mode 100644 index dc94ae324a214c..00000000000000 --- a/assets/eip-4973/sdk/src/index.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import { utils } from "ethers"; - -// See: https://docs.ethers.io/v5/api/signer/#Signer-signTypedData for more -// detailed instructions. -export async function generateSignature(signer, types, domain, agreement) { - const signature = await signer._signTypedData(domain, types, agreement); - const { r, s, v } = utils.splitSignature(signature); - return utils.solidityPack(["bytes32", "bytes32", "uint8"], [r, s, v]); -} diff --git a/assets/eip-4973/sdk/test/index_test.mjs b/assets/eip-4973/sdk/test/index_test.mjs deleted file mode 100644 index 2adf1fc7a12fc2..00000000000000 --- a/assets/eip-4973/sdk/test/index_test.mjs +++ /dev/null @@ -1,43 +0,0 @@ -// @format -import test from "ava"; - -import { Wallet, utils } from "ethers"; - -import { generateSignature } from "../src/index.mjs"; - -test("generating a compact signature for function give", async (t) => { - // from: https://docs.ethers.io/v5/api/signer/#Wallet--methods - const passiveAddress = "0x0f6A79A579658E401E0B81c6dde1F2cd51d97176"; - const passivePrivateKey = - "0xad54bdeade5537fb0a553190159783e45d02d316a992db05cbed606d3ca36b39"; - const signer = new Wallet(passivePrivateKey); - t.is(signer.address, passiveAddress); - - const types = { - Agreement: [ - { name: "active", type: "address" }, - { name: "passive", type: "address" }, - { name: "metadata", type: "bytes" }, - ], - }; - const domain = { - name: "Name", - version: "Version", - chainId: 31337, // the chainId of foundry - verifyingContract: "0xce71065d4017f316ec606fe4422e11eb2c47c246", - }; - - const agreement = { - active: "0xb4c79dab8f259c7aee6e5b2aa729821864227e84", - passive: passiveAddress, - metadata: utils.toUtf8Bytes("https://example.com/metadata.json"), - }; - - const signature = await generateSignature(signer, types, domain, agreement); - t.truthy(signature); - t.is(signature.length, 64 + 64 + 2 + 2); - t.is( - signature, - "0x4473afdec84287f10aa0b5eb608d360e2e9220bee657a4a5ca468e69a4de255c38691fca0c52f295d1831beaa0b7f079c1ab7959257578d2fb8d98740d9b0e111c" - ); -}); diff --git a/assets/eip-4974/ERC4974.sol b/assets/eip-4974/ERC4974.sol deleted file mode 100644 index 2619a2e4ec7821..00000000000000 --- a/assets/eip-4974/ERC4974.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; -import "./IERC4974.sol"; - -/** - * See {IERC4974} - * Implements the ERC4974 Metadata extension. - */ -contract LoyaltyPoints is IERC4974 { - - // The address of the operator that can assign ratings - address private _operator; - - // Mapping of customer addresses to their ratings - mapping (bytes32 => int8) private _ratings; - - // Initializes the contract by setting the operator to msg.sender - constructor () { - _operator = msg.sender; - } - - // Set the operator address - // Only the current operator or the contract owner can call this function - function setOperator(address newOperator) public override { - require(_operator == msg.sender || msg.sender == address(this), "Only the current operator or the contract owner can set the operator."); - _operator = newOperator; - emit NewOperator(_operator); - } - - // Rate a customer - // Only the operator can call this function - function rate(address customer, int8 rating) public override { - require(_operator == msg.sender, "Only the operator can assign ratings."); - bytes32 hash = keccak256(abi.encodePacked(customer)); - _ratings[hash] = rating; - emit Rating(customer, rating); - } - - // Remove a rating from a customer - // Only the operator can call this function - function removeRating(address customer) external override { - require(_operator == msg.sender, "Only the operator can remove ratings."); - bytes32 hash = keccak256(abi.encodePacked(customer)); - delete _ratings[hash]; - emit Removal(customer); - } - - // Get the rating for a customer - function getOperator() public view returns (address) { - return _operator; - } - - // Check if a customer has been rated - function hasBeenRated(address customer) public view returns (bool) { - // Hash the customer address - bytes32 hash = keccak256(abi.encodePacked(customer)); - - // Check if the hash exists in the mapping - return _ratings[hash] != 0; - } - - function ratingOf(address _rated) public view override returns (int8) { - bytes32 hash = keccak256(abi.encodePacked(_rated)); - // Check if the customer has been rated - require(hasBeenRated(_rated), "This customer has not been rated yet."); - // Return the customer's rating - return _ratings[hash]; - } - - // Award ETH to a customer based on their rating - function awardEth(address payable customer) public payable { - // Calculate the amount of ETH to award based on the customer's rating - int8 rating = ratingOf(customer); - require(rating > 0, "Sorry, this customer has a rating less than 0 and cannot be awarded."); - uint256 award = uint256(int256(rating)); - // Transfer the ETH to the customer - require(address(this).balance >= award, "Contract has insufficient balance to award ETH."); - customer.transfer(award); - } - - receive () external payable {} - -} \ No newline at end of file diff --git a/assets/eip-4974/IERC4974.sol b/assets/eip-4974/IERC4974.sol deleted file mode 100644 index 8043c6e0f5b6d6..00000000000000 --- a/assets/eip-4974/IERC4974.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/// @title EIP-4974 Ratings -/// @dev See https://eips.ethereum.org/EIPS/EIP-4974 -/// Note: the EIP-165 identifier for this interface is #######. -/// Must initialize contracts with an `operator` address that is not `address(0)`. -interface IERC4974 /* is ERC165 */ { - - /// @dev Emits when operator changes. - /// MUST emit when `operator` changes by any mechanism. - /// MUST ONLY emit by `setOperator`. - event NewOperator(address indexed _operator); - - /// @dev Emits when operator issues a rating. - /// MUST emit when rating is assigned by any mechanism. - /// MUST ONLY emit by `rate`. - event Rating(address _rated, int8 _rating); - - /// @dev Emits when operator removes a rating. - /// MUST emit when rating is removed by any mechanism. - /// MUST ONLY emit by `remove`. - event Removal(address _removed); - - /// @notice Appoint operator authority. - /// @dev MUST throw unless `msg.sender` is `operator`. - /// MUST throw if `operator` address is either already current `operator` - /// or is the zero address. - /// MUST emit an `Appointment` event. - /// @param _operator New operator of the smart contract. - function setOperator(address _operator) external; - - /// @notice Rate an address. - /// MUST emit a Rating event with each successful call. - /// @param _rated Address to be rated. - /// @param _rating Total EXP tokens to reallocate. - function rate(address _rated, int8 _rating) external; - - /// @notice Remove a rating from an address. - /// MUST emit a Remove event with each successful call. - /// @param _removed Address to be removed. - function removeRating(address _removed) external; - - /// @notice Return a rated address' rating. - /// @dev MUST register each time `Rating` emits. - /// SHOULD throw for queries about the zero address. - /// @param _rated An address for whom to query rating. - /// @return int8 The rating assigned. - function ratingOf(address _rated) external view returns (int8); -} - -interface IERC165 { - /// @notice Query if a contract implements an interface. - /// @dev Interface identification is specified in EIP-165. This function - /// uses less than 30,000 gas. - /// @param interfaceID The interface identifier, as specified in EIP-165. - /// @return bool `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise. - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-4974/IERC4974Metadata.sol b/assets/eip-4974/IERC4974Metadata.sol deleted file mode 100644 index fe75396e228e2f..00000000000000 --- a/assets/eip-4974/IERC4974Metadata.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC4974.sol"; - -/// @title ERC-4974 EXP Token Standard, optional metadata extension -/// @dev See https://eips.ethereum.org/EIPS/EIP-4974 -/// Note: the ERC-165 identifier for this interface is 0x74793a15. -interface IERC4974Metadata is IERC4974 { - /// @notice A descriptive name for the EXP in this contract. - function name() external view returns (string memory); - - /// @notice A one-line description of the EXP in this contract. - function description() external view returns (string memory); -} \ No newline at end of file diff --git a/assets/eip-4987/Consumer.sol b/assets/eip-4987/Consumer.sol deleted file mode 100644 index 77302c8d8df78f..00000000000000 --- a/assets/eip-4987/Consumer.sol +++ /dev/null @@ -1,84 +0,0 @@ -/* -Consumer - -SPDX-License-Identifier: CC0-1.0 -*/ - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -import "./IERC721Holder.sol"; - -/** - * @title Consumer - * - * @notice this contract implements an example "consumer" of the proposed - * held token ERC standard. - - * This example consumer contract will query ERC721 ownership and balances - * including any "held" tokens - */ -contract Consumer { - using Address for address; - - // members - IERC721 public token; - - /** - * @param token_ address of ERC721 token - */ - constructor(address token_) { - token = IERC721(token_); - } - - /** - * @notice get the functional owner of a token - * @param tokenId token id of interest - */ - function getOwner(uint256 tokenId) external view returns (address) { - // get raw owner - address owner = token.ownerOf(tokenId); - - // if owner is not contract, return - if (!owner.isContract()) { - return owner; - } - - // check for token holder interface support - try IERC165(owner).supportsInterface(0x16b900ff) returns (bool ret) { - if (!ret) return owner; - } catch { - return owner; - } - - // check for held owner - try IERC721Holder(owner).heldOwnerOf(address(token), tokenId) returns (address user) { - if (user != address(0)) return user; - } catch {} - - return owner; - } - - /** - * @notice get the total user balance including held tokens - * @param owner user address - * @param holders list of token holder addresses - */ - function getBalance(address owner, address[] calldata holders) - external - view - returns (uint256) - { - // start with raw token balance - uint256 balance = token.balanceOf(owner); - - // consider each provided token holder contract - for (uint256 i = 0; i < holders.length; i++) { - balance += IERC721Holder(holders[i]).heldBalanceOf(address(token), owner); - } - - return balance; - } -} diff --git a/assets/eip-4987/IERC1155Holder.sol b/assets/eip-4987/IERC1155Holder.sol deleted file mode 100644 index d98daee741fc84..00000000000000 --- a/assets/eip-4987/IERC1155Holder.sol +++ /dev/null @@ -1,56 +0,0 @@ -/* -IERC1155Holder - -SPDX-License-Identifier: CC0-1.0 -*/ - -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -pragma solidity ^0.8.0; - -/** - * @notice the ERC1155 holder standard provides a common interface to query - * token balance information - */ -interface IERC1155Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - * @param tokenAmount held token amount - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 tokenAmount - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - * @param tokenAmount held token amount - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId, - uint256 tokenAmount - ); - - /** - * @notice get the held balance of the token owner - * @param tokenAddress held token address - * @param owner functional token owner - * @param tokenId held token ID - * @return held token balance - */ - function heldBalanceOf( - address tokenAddress, - address owner, - uint256 tokenId - ) external view returns (uint256); -} diff --git a/assets/eip-4987/IERC20Holder.sol b/assets/eip-4987/IERC20Holder.sol deleted file mode 100644 index fe040a2277131f..00000000000000 --- a/assets/eip-4987/IERC20Holder.sol +++ /dev/null @@ -1,51 +0,0 @@ -/* -IERC20Holder - - -SPDX-License-Identifier: CC0-1.0 -*/ - -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -pragma solidity ^0.8.0; - -/** - * @notice the ERC20 holder standard provides a common interface to query - * token balance information - */ -interface IERC20Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenAmount held token amount - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 tokenAmount - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenAmount held token amount - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 tokenAmount - ); - - /** - * @notice get the held balance of the token owner - * @param tokenAddress held token address - * @param owner functional token owner - * @return held token balance - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - returns (uint256); -} diff --git a/assets/eip-4987/IERC721Holder.sol b/assets/eip-4987/IERC721Holder.sol deleted file mode 100644 index ed58be9fd288a6..00000000000000 --- a/assets/eip-4987/IERC721Holder.sol +++ /dev/null @@ -1,61 +0,0 @@ -/* -IERC721Holder - -SPDX-License-Identifier: CC0-1.0 -*/ - -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -pragma solidity ^0.8.0; - -/** - * @notice the ERC721 holder standard provides a common interface to query - * token ownership and balance information - */ -interface IERC721Holder is IERC165 { - /** - * @notice emitted when the token is transferred to the contract - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - */ - event Hold( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId - ); - - /** - * @notice emitted when the token is released back to the user - * @param owner functional token owner - * @param tokenAddress held token address - * @param tokenId held token ID - */ - event Release( - address indexed owner, - address indexed tokenAddress, - uint256 indexed tokenId - ); - - /** - * @notice get the functional owner of a held token - * @param tokenAddress held token address - * @param tokenId held token ID - * @return functional token owner - */ - function heldOwnerOf(address tokenAddress, uint256 tokenId) - external - view - returns (address); - - /** - * @notice get the held balance of the token owner - * @param tokenAddress held token address - * @param owner functional token owner - * @return held token balance - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - returns (uint256); -} diff --git a/assets/eip-4987/Vault.sol b/assets/eip-4987/Vault.sol deleted file mode 100644 index 3045cd65913037..00000000000000 --- a/assets/eip-4987/Vault.sol +++ /dev/null @@ -1,125 +0,0 @@ -/* -Vault - -SPDX-License-Identifier: CC0-1.0 -*/ - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -import "./IERC721Holder.sol"; - -/** - * @title Vault - * - * @notice this contract implements an example "holder" for the proposed - * held token ERC standard. - - * This example vault contract allows a user to lock up an ERC721 token for - * a specified period of time, while still reporting the functional owner - */ -contract Vault is ERC165, IERC721Holder { - // members - IERC721 public token; - uint256 public timelock; - mapping(uint256 => address) public owners; - mapping(uint256 => uint256) public locks; - mapping(address => uint256) public balances; - - /** - * @param token_ address of token to be stored in vault - * @param timelock_ duration in seconds that tokens will be locked - */ - constructor(address token_, uint256 timelock_) { - token = IERC721(token_); - timelock = timelock_; - } - - /** - * @inheritdoc IERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC165, IERC165) - returns (bool) - { - return - interfaceId == type(IERC721Holder).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @inheritdoc IERC721Holder - */ - function heldOwnerOf(address tokenAddress, uint256 tokenId) - external - view - override - returns (address) - { - require( - tokenAddress == address(token), - "ERC721Vault: invalid token address" - ); - return owners[tokenId]; - } - - /** - * @inheritdoc IERC721Holder - */ - function heldBalanceOf(address tokenAddress, address owner) - external - view - override - returns (uint256) - { - require( - tokenAddress == address(token), - "ERC721Vault: invalid token address" - ); - return balances[owner]; - } - - /** - * @notice deposit and lock a token for a period of time - * @param tokenId ID of token to deposit - */ - function deposit(uint256 tokenId) public { - require( - msg.sender == token.ownerOf(tokenId), - "ERC721Vault: sender does not own token" - ); - - owners[tokenId] = msg.sender; - locks[tokenId] = block.timestamp + timelock; - balances[msg.sender]++; - - emit Hold(msg.sender, address(token), tokenId); - - token.transferFrom(msg.sender, address(this), tokenId); - } - - /** - * @notice withdraw token after timelock has elapsed - * @param tokenId ID of token to withdraw - */ - function withdraw(uint256 tokenId) public { - require( - msg.sender == owners[tokenId], - "ERC721Vault: sender does not own token" - ); - require(block.timestamp > locks[tokenId], "ERC721Vault: token is locked"); - - delete owners[tokenId]; - delete locks[tokenId]; - balances[msg.sender]--; - - emit Release(msg.sender, address(token), tokenId); - - token.safeTransferFrom(address(this), msg.sender, tokenId); - } -} diff --git a/assets/eip-5006/.gitignore b/assets/eip-5006/.gitignore deleted file mode 100644 index e0da618c79fe11..00000000000000 --- a/assets/eip-5006/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -package-lock.json -typechain/ -cache/ -artifacts/ \ No newline at end of file diff --git a/assets/eip-5006/contracts/ERC5006.sol b/assets/eip-5006/contracts/ERC5006.sol deleted file mode 100644 index 1747438abf0c2e..00000000000000 --- a/assets/eip-5006/contracts/ERC5006.sol +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Receiver.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "./IERC5006.sol"; - -contract ERC5006 is ERC1155, ERC1155Receiver, IERC5006 { - using EnumerableSet for EnumerableSet.UintSet; - mapping(uint256 => mapping(address => uint256)) private _frozens; - mapping(uint256 => UserRecord) private _records; - mapping(uint256 => mapping(address => EnumerableSet.UintSet)) - private _userRecordIds; - uint256 _curRecordId; - uint256 recordLimit; - - constructor(string memory uri_, uint256 recordLimit_) ERC1155(uri_) { - recordLimit = recordLimit_; - } - - function isOwnerOrApproved(address owner) public view returns (bool) { - require( - owner == msg.sender || isApprovedForAll(owner, msg.sender), - "only owner or approved" - ); - return true; - } - - function usableBalanceOf(address account, uint256 tokenId) - public - view - override - returns (uint256 amount) - { - uint256[] memory recordIds = _userRecordIds[tokenId][account].values(); - for (uint256 i = 0; i < recordIds.length; i++) { - if (block.timestamp <= _records[recordIds[i]].expiry) { - amount += _records[recordIds[i]].amount; - } - } - } - - function frozenBalanceOf(address account, uint256 tokenId) - public - view - override - returns (uint256) - { - return _frozens[tokenId][account]; - } - - function userRecordOf(uint256 recordId) - public - view - override - returns (UserRecord memory) - { - return _records[recordId]; - } - - function createUserRecord( - address owner, - address user, - uint256 tokenId, - uint64 amount, - uint64 expiry - ) public override returns (uint256) { - require(isOwnerOrApproved(owner)); - require(user != address(0), "user cannot be the zero address"); - require(amount > 0, "amount must be greater than 0"); - require(expiry > block.timestamp, "expiry must after the block timestamp"); - require( - _userRecordIds[tokenId][user].length() < recordLimit, - "user cannot have more records" - ); - _safeTransferFrom(owner, address(this), tokenId, amount, ""); - _frozens[tokenId][owner] += amount; - _curRecordId++; - _records[_curRecordId] = UserRecord( - tokenId, - owner, - amount, - user, - expiry - ); - _userRecordIds[tokenId][user].add(_curRecordId); - emit CreateUserRecord( - _curRecordId, - tokenId, - amount, - owner, - user, - expiry - ); - return _curRecordId; - } - - function deleteUserRecord(uint256 recordId) public override { - UserRecord storage _record = _records[recordId]; - require(isOwnerOrApproved(_record.owner)); - _safeTransferFrom( - address(this), - _record.owner, - _record.tokenId, - _record.amount, - "" - ); - _frozens[_record.tokenId][_record.owner] -= _record.amount; - _userRecordIds[_record.tokenId][_record.user].remove(recordId); - delete _records[recordId]; - emit DeleteUserRecord(recordId); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(ERC1155, ERC1155Receiver) - returns (bool) - { - return - interfaceId == type(IERC5006).interfaceId || - ERC1155.supportsInterface(interfaceId) || - ERC1155Receiver.supportsInterface(interfaceId); - } - - function onERC1155Received( - address operator, - address from, - uint256 tokenId, - uint256 value, - bytes calldata data - ) external pure override returns (bytes4) { - return IERC1155Receiver.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external pure override returns (bytes4) { - return IERC1155Receiver.onERC1155BatchReceived.selector; - } -} diff --git a/assets/eip-5006/contracts/ERC5006Demo.sol b/assets/eip-5006/contracts/ERC5006Demo.sol deleted file mode 100644 index fd409e0024712f..00000000000000 --- a/assets/eip-5006/contracts/ERC5006Demo.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5006.sol"; - -contract ERC5006Demo is ERC5006 { - constructor(string memory uri_, uint256 recordLimit_) - ERC5006(uri_, recordLimit_) - {} - - function mint( - address to, - uint256 id, - uint256 amount - ) public { - _mint(to, id, amount, ""); - } - - function burn( - address from, - uint256 id, - uint256 amount - ) public { - _burn(from, id, amount); - } - - function getInterfaceId() public view returns (bytes4) { - return type(IERC5006).interfaceId; - } -} diff --git a/assets/eip-5006/contracts/IERC5006.sol b/assets/eip-5006/contracts/IERC5006.sol deleted file mode 100644 index 4d7a14784610e7..00000000000000 --- a/assets/eip-5006/contracts/IERC5006.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5006 { - struct UserRecord { - uint256 tokenId; - address owner; - uint64 amount; - address user; - uint64 expiry; - } - /** - * @dev Emitted when permission (for `user` to use `amount` of `tokenId` token owned by `owner` - * until `expiry`) is given. - */ - event CreateUserRecord( - uint256 recordId, - uint256 tokenId, - uint64 amount, - address owner, - address user, - uint64 expiry - ); - /** - * @dev Emitted when record of `recordId` is deleted. - */ - event DeleteUserRecord(uint256 recordId); - - /** - * @dev Returns the usable amount of `tokenId` tokens by `account`. - */ - function usableBalanceOf(address account, uint256 tokenId) - external - view - returns (uint256); - - /** - * @dev Returns the amount of frozen tokens of token type `id` by `account`. - */ - function frozenBalanceOf(address account, uint256 tokenId) - external - view - returns (uint256); - - /** - * @dev Returns the `UserRecord` of `recordId`. - */ - function userRecordOf(uint256 recordId) - external - view - returns (UserRecord memory); - - /** - * @dev Gives permission to `user` to use `amount` of `tokenId` token owned by `owner` until `expiry`. - * - * Emits a {CreateUserRecord} event. - * - * Requirements: - * - * - If the caller is not `owner`, it must be have been approved to spend ``owner``'s tokens - * via {setApprovalForAll}. - * - `owner` must have a balance of tokens of type `id` of at least `amount`. - * - `user` cannot be the zero address. - * - `amount` must be greater than 0. - * - `expiry` must after the block timestamp. - */ - function createUserRecord( - address owner, - address user, - uint256 tokenId, - uint64 amount, - uint64 expiry - ) external returns (uint256); - - /** - * @dev Atomically delete `record` of `recordId` by the caller. - * - * Emits a {DeleteUserRecord} event. - * - * Requirements: - * - * - the caller must have allowance. - */ - function deleteUserRecord(uint256 recordId) external; -} diff --git a/assets/eip-5006/hardhat.config.ts b/assets/eip-5006/hardhat.config.ts deleted file mode 100644 index 9b10629aa7ea8c..00000000000000 --- a/assets/eip-5006/hardhat.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HardhatUserConfig, task } from "hardhat/config"; -import "@nomiclabs/hardhat-waffle"; -import "@typechain/hardhat"; - -// This is a sample Hardhat task. To learn how to create your own go to -// https://hardhat.org/guides/create-task.html -task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { - const accounts = await hre.ethers.getSigners(); - - for (const account of accounts) { - console.log(account.address); - } -}); - - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.9", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - }, - -}; - - - -export default config; diff --git a/assets/eip-5006/package.json b/assets/eip-5006/package.json deleted file mode 100644 index a20ad3f9080765..00000000000000 --- a/assets/eip-5006/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "EIP-5006", - "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.5", - "@nomiclabs/hardhat-waffle": "^2.0.3", - "@openzeppelin/contracts": "^4.5.0", - "@typechain/ethers-v5": "^7.2.0", - "@typechain/hardhat": "^2.3.1", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "@types/node": "^12.20.47", - "chai": "^4.3.6", - "ethers": "^5.6.1", - "hardhat": "^2.9.2", - "solhint": "^3.3.7", - "ts-node": "^10.8.1", - "typechain": "^5.2.0", - "typescript": "^4.6.3" - } -} diff --git a/assets/eip-5006/test/test.ts b/assets/eip-5006/test/test.ts deleted file mode 100644 index 41fd7bda5bb92e..00000000000000 --- a/assets/eip-5006/test/test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "hardhat"; -import hre from "hardhat"; - -describe("Test 1155 User Role", function () { - let alice, bob, carl; - let contract; - let expiry; - - async function checkRecord(rid,tokenId,amount,owner,user,expiry_) { - let record = await contract.userRecordOf(rid); - expect(record[0]).equals(tokenId,"tokenId"); - expect(record[1]).equals(owner,"owner"); - expect(record[2]).equals(amount,"amount"); - expect(record[3]).equals(user,"user"); - expect(record[4]).equals(expiry_,"expiry_"); - } - - beforeEach(async function () { - [alice, bob, carl] = await ethers.getSigners(); - - const ERC5006Demo = await ethers.getContractFactory("ERC5006Demo"); - - contract = await ERC5006Demo.deploy("", 3); - - expiry = Math.floor(new Date().getTime() / 1000) + 3600; - }); - - - - describe("", function () { - - it("InterfaceId should equals 0xc26d96cc", async function () { - expect(await contract.getInterfaceId()).equals("0xc26d96cc"); - }); - - it("Should set user to bob success", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - - await checkRecord(1,1,10,alice.address,bob.address,expiry); - - expect(await contract.usableBalanceOf(bob.address, 1)).equals(10); - - expect(await contract.balanceOf(alice.address, 1)).equals(90); - - expect(await contract.frozenBalanceOf(alice.address, 1)).equals(10); - - }); - - it("Should set user to bob fail", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - await expect(contract.createUserRecord(alice.address, bob.address, 1, 10, expiry)).to.be.revertedWith("user cannot have more records"); - - }); - - it("Should set user to bob fail : balance is not enough", async function () { - - await contract.mint(alice.address, 1, 100); - - await expect(contract.createUserRecord(alice.address, bob.address, 1, 101, expiry)).to.be.revertedWith('ERC1155: insufficient balance for transfer'); - - }); - - it("Should set user to bob fail : only owner or approved", async function () { - - await contract.mint(alice.address, 1, 100); - await contract.mint(carl.address, 1, 100); - - await expect(contract.createUserRecord(carl.address, bob.address, 1, 110, expiry)).to.be.revertedWith('only owner or approved'); - - }); - - it("Should deleteUserRecord success", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - - // await hre.network.provider.send("hardhat_mine", ["0x5a0", "0x3c"]); - - await contract.deleteUserRecord(1); - - await checkRecord(1,0,0,"0x0000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000",0); - - expect(await contract.usableBalanceOf(bob.address, 1)).equals(0); - - expect(await contract.balanceOf(alice.address, 1)).equals(100); - - expect(await contract.frozenBalanceOf(alice.address, 1)).equals(0); - - }); - - - it("bob should deleteUserRecord fail", async function () { - - await contract.mint(alice.address, 1, 100); - - await contract.createUserRecord(alice.address, bob.address, 1, 10, expiry); - - await expect(contract.connect(bob).deleteUserRecord(1)).to.be.revertedWith("only owner or approved"); - - }); - - - }); - - -}); diff --git a/assets/eip-5006/tsconfig.json b/assets/eip-5006/tsconfig.json deleted file mode 100644 index c458030680cbc4..00000000000000 --- a/assets/eip-5006/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": false, - "esModuleInterop": true, - "outDir": "dist", - "declaration": true - }, - "include": ["./test", "./typechain"], - "files": ["./hardhat.config.ts"] -} diff --git a/assets/eip-5007/.gitignore b/assets/eip-5007/.gitignore deleted file mode 100644 index d5f19d89b308d3..00000000000000 --- a/assets/eip-5007/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -package-lock.json diff --git a/assets/eip-5007/README.md b/assets/eip-5007/README.md deleted file mode 100644 index 5e4da67d5811de..00000000000000 --- a/assets/eip-5007/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# EIP-5007 -This standard is an extension of [ERC-721](../../EIPS/eip-721.md). It proposes some additional functions (`startTime`, `endTime`) to help with on-chain time management. - -## Tools -* [Truffle](https://truffleframework.com/) - a development framework for Ethereum - -## Install -``` -npm install truffle -g -npm install -``` - -## Test -``` -truffle test -``` diff --git a/assets/eip-5007/contracts/ERC5007.sol b/assets/eip-5007/contracts/ERC5007.sol deleted file mode 100644 index 0b330bad32c07e..00000000000000 --- a/assets/eip-5007/contracts/ERC5007.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5007.sol"; - -abstract contract ERC5007 is ERC721, IERC5007 { - struct TimeNftInfo { - uint64 startTime; - uint64 endTime; - } - - mapping(uint256 => TimeNftInfo) internal _timeNftMapping; - - /** - * @dev See {IERC5007-startTime}. - */ - function startTime(uint256 tokenId) - public - view - virtual - override - returns (uint64) { - require(_exists(tokenId), "ERC5007: invalid tokenId"); - return _timeNftMapping[tokenId].startTime; - } - - /** - * @dev See {IERC5007-endTime}. - */ - function endTime(uint256 tokenId) - public - view - virtual - override - returns (uint64) { - require(_exists(tokenId), "ERC5007: invalid tokenId"); - return _timeNftMapping[tokenId].endTime; - } - - /** - * @dev mint a new time NFT. - * - * Requirements: - * - * - `tokenId_` must not exist. - * - `to_` cannot be the zero address. - * - `endTime_` should be equal or greater than `startTime_` - */ - function _mintTimeNft( - address to_, - uint256 tokenId_, - uint64 startTime_, - uint64 endTime_ - ) internal virtual { - require(endTime_ >= startTime_, 'ERC5007: invalid endTime'); - _mint(to_, tokenId_); - TimeNftInfo storage info = _timeNftMapping[tokenId_]; - info.startTime = startTime_; - info.endTime = endTime_; - } - - - /** - * @dev Destroys `tokenId`. - * - * Requirements: - * - * - `tokenId` must exist. - * - */ - function _burn(uint256 tokenId) internal virtual override { - super._burn(tokenId); - delete _timeNftMapping[tokenId]; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) { - return - interfaceId == type(IERC5007).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5007/contracts/ERC5007Composable.sol b/assets/eip-5007/contracts/ERC5007Composable.sol deleted file mode 100644 index eb2fab64339ac2..00000000000000 --- a/assets/eip-5007/contracts/ERC5007Composable.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./ERC5007.sol"; -import "./IERC5007Composable.sol"; - -abstract contract ERC5007Composable is ERC5007, IERC5007Composable { - mapping(uint256 => uint256) internal _assetIdMapping; - - /** - * @dev See {IERC5007Composable-assetId}. - */ - function assetId(uint256 tokenId) public view returns (uint256) - { - require(_exists(tokenId), "ERC5007: invalid tokenId"); - return _assetIdMapping[tokenId]; - } - - /** - * @dev See {IERC5007Composable-split}. - */ - function split( - uint256 oldTokenId, - uint256 newToken1Id, - address newToken1Owner, - uint256 newToken2Id, - address newToken2Owner, - uint64 splitTime - ) public virtual override { - require(_isApprovedOrOwner(_msgSender(), oldTokenId), "ERC5007: caller is not owner nor approved"); - - uint64 oldTokenStartTime = _timeNftMapping[oldTokenId].startTime; - uint64 oldTokenEndTime = _timeNftMapping[oldTokenId].endTime; - require( - oldTokenStartTime <= splitTime && - splitTime < oldTokenEndTime, - "ERC5007: invalid newTokenStartTime" - ); - - uint256 assetId_ = _assetIdMapping[oldTokenId]; - _mintTimeNftWithAssetId( - newToken1Owner, - newToken1Id, - assetId_, - oldTokenStartTime, - splitTime - ); - - _mintTimeNftWithAssetId( - newToken2Owner, - newToken2Id, - assetId_, - splitTime + 1, - oldTokenEndTime - ); - - _burn(oldTokenId); - } - - /** - * @dev See {IERC5007Composable-merge}. - */ - function merge( - uint256 firstTokenId, - uint256 secondTokenId, - address newTokenOwner, - uint256 newTokenId - ) public virtual { - require( - _isApprovedOrOwner(_msgSender(), firstTokenId) && - _isApprovedOrOwner(_msgSender(), secondTokenId), - "ERC5007: caller is not owner nor approved" - ); - - TimeNftInfo memory firstToken = _timeNftMapping[firstTokenId]; - TimeNftInfo memory secondToken = _timeNftMapping[secondTokenId]; - require( - _assetIdMapping[firstTokenId] == _assetIdMapping[secondTokenId] && - firstToken.startTime <= firstToken.endTime && - (firstToken.endTime + 1) == secondToken.startTime && - secondToken.startTime <= secondToken.endTime, - "ERC5007: invalid data" - ); - - _mintTimeNftWithAssetId( - newTokenOwner, - newTokenId, - _assetIdMapping[firstTokenId], - firstToken.startTime, - secondToken.endTime - ); - - _burn(firstTokenId); - _burn(secondTokenId); - } - - /** - * @dev mint a new common time NFT - * - * Requirements: - * - * - `to_` cannot be the zero address. - * - `tokenId_` must not exist. - * - `rootId_` must exist. - * - `endTime_` should be equal or greater than `startTime_` - */ - function _mintTimeNftWithAssetId( - address to_, - uint256 tokenId_, - uint256 assetId_, - uint64 startTime_, - uint64 endTime_ - ) internal virtual { - super._mintTimeNft(to_, tokenId_, startTime_, endTime_); - _assetIdMapping[tokenId_] = assetId_; - } - - /** - * @dev Destroys `tokenId`. - * - * Requirements: - * - * - `tokenId` must exist. - * - */ - function _burn(uint256 tokenId) internal virtual override { - super._burn(tokenId); - delete _assetIdMapping[tokenId]; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5007Composable).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5007/contracts/ERC5007ComposableTest.sol b/assets/eip-5007/contracts/ERC5007ComposableTest.sol deleted file mode 100644 index 7fd945fc1a3237..00000000000000 --- a/assets/eip-5007/contracts/ERC5007ComposableTest.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5007Composable.sol"; - -contract ERC5007ComposableTest is ERC5007Composable { - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - /// @notice mint a new root time NFT - /// @param to_ The owner of the new token - /// @param tokenId_ The id of the new token - /// @param assetId_ The asset id of the new token - /// @param startTime_ The start time of the new token - /// @param endTime_ The end time of the new token - function mint( - address to_, - uint256 tokenId_, - uint256 assetId_, - uint64 startTime_, - uint64 endTime_ - ) public { - _mintTimeNftWithAssetId(to_, tokenId_, assetId_, startTime_, endTime_); - } - - /** - * @dev Returns the interfaceId of IERC5007Composable. - */ - function getInterfaceId() public pure returns (bytes4) { - return type(IERC5007Composable).interfaceId; - } -} diff --git a/assets/eip-5007/contracts/ERC5007Demo.sol b/assets/eip-5007/contracts/ERC5007Demo.sol deleted file mode 100644 index fdfaeae5d62748..00000000000000 --- a/assets/eip-5007/contracts/ERC5007Demo.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5007.sol"; - -contract ERC5007Demo is ERC5007 { - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){} - - /** - * @dev mint a new time NFT - * - * Requirements: - * - * - `to_` cannot be the zero address. - * - `tokenId_` must not exist. - * - `endTime_` should be equal or greater than `startTime_` - */ - function mint( - address to_, - uint256 tokenId_, - uint64 startTime_, - uint64 endTime_ - ) public { - _mintTimeNft(to_, tokenId_, startTime_, endTime_); - } - - /** - * @dev Returns the interfaceId of IERC5007. - */ - function getInterfaceId() public pure returns (bytes4) { - return type(IERC5007).interfaceId; - } -} diff --git a/assets/eip-5007/contracts/IERC5007.sol b/assets/eip-5007/contracts/IERC5007.sol deleted file mode 100644 index 77c37e24af9765..00000000000000 --- a/assets/eip-5007/contracts/IERC5007.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5007 /* is IERC1155 */ { - /** - * @dev Returns the start time of the NFT. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function startTime(uint256 tokenId) external view returns (uint64); - - /** - * @dev Returns the end time of the NFT. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function endTime(uint256 tokenId) external view returns (uint64); -} diff --git a/assets/eip-5007/contracts/IERC5007Composable.sol b/assets/eip-5007/contracts/IERC5007Composable.sol deleted file mode 100644 index 26bd078b952fd3..00000000000000 --- a/assets/eip-5007/contracts/IERC5007Composable.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - - -interface IERC5007Composable /* is IERC5007 */ { - /** - * @dev Returns the asset id of the time NFT. - * Only NFTs with same asset id can be merged. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function assetId(uint256 tokenId) external view returns (uint256); - - /** - * @dev Split an old token to two new tokens. - * The assetId of the new token is the same as the assetId of the old token - * - * Requirements: - * - * - `oldTokenId` must exist. - * - `newToken1Id` must not exist. - * - `newToken1Owner` cannot be the zero address. - * - `newToken2Id` must not exist. - * - `newToken2Owner` cannot be the zero address. - * - `splitTime` require(oldToken.startTime <= splitTime && splitTime < oldToken.EndTime) - */ - function split( - uint256 oldTokenId, - uint256 newToken1Id, - address newToken1Owner, - uint256 newToken2Id, - address newToken2Owner, - uint64 splitTime - ) external; - - /** - * @dev Merge the first token and second token into the new token. - * - * Requirements: - * - * - `firstTokenId` must exist. - * - `secondTokenId` must exist. - * - require((firstToken.endTime + 1) == secondToken.startTime) - * - require((firstToken.assetId()) == secondToken.assetId()) - * - `newTokenOwner` cannot be the zero address. - * - `newTokenId` must not exist. - */ - function merge( - uint256 firstTokenId, - uint256 secondTokenId, - address newTokenOwner, - uint256 newTokenId - ) external; -} diff --git a/assets/eip-5007/migrations/1_initial_migration.js b/assets/eip-5007/migrations/1_initial_migration.js deleted file mode 100644 index 31c03a350482ea..00000000000000 --- a/assets/eip-5007/migrations/1_initial_migration.js +++ /dev/null @@ -1,7 +0,0 @@ -const ERC5007Demo = artifacts.require("ERC5007Demo"); -const ERC5007ComposableTest = artifacts.require("ERC5007ComposableTest"); - -module.exports = function (deployer) { - deployer.deploy(ERC5007Demo,'ERC5007Demo','ERC5007Demo'); - deployer.deploy(ERC5007ComposableTest,'ERC5007ComposableTest','ERC5007ComposableTest'); -}; diff --git a/assets/eip-5007/package.json b/assets/eip-5007/package.json deleted file mode 100644 index eb917b70cb4119..00000000000000 --- a/assets/eip-5007/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "ERC5007", - "dependencies": { - "@openzeppelin/contracts": "^4.3.3", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "bignumber.js": "^9.0.1", - "chai": "^4.3.6" - } -} diff --git a/assets/eip-5007/test/test.js b/assets/eip-5007/test/test.js deleted file mode 100644 index b96d6f62e6df1a..00000000000000 --- a/assets/eip-5007/test/test.js +++ /dev/null @@ -1,107 +0,0 @@ -const { assert } = require("chai"); - -const { BigNumber } = require("bignumber.js") - -const ERC5007Demo = artifacts.require("ERC5007Demo"); -const ERC5007ComposableTest = artifacts.require("ERC5007ComposableTest"); - -contract("test ERC5007", async accounts => { - - it("test ERC5007", async () => { - const Alice = accounts[0]; - - const instance = await ERC5007Demo.deployed("ERC5007Demo", "ERC5007Demo"); - const demo = instance; - - let now = Math.floor(new Date().getTime()/1000); - let inputStartTime1 = new BigNumber(now - 10000); - let inputEndTime1 = new BigNumber(now + 10000); - let id1 = 1; - - await demo.mint(Alice, id1, inputStartTime1.toFixed(0), inputEndTime1.toFixed(0)); - - - let outputStartTime1 = await demo.startTime(id1); - let outputEndTime1 = await demo.endTime(id1); - assert.equal(inputStartTime1.comparedTo(outputStartTime1) == 0 && inputEndTime1.comparedTo(outputEndTime1) == 0, true, "wrong data"); - - - console.log("IERC5007 InterfaceId:", await demo.getInterfaceId()) - let isSupport = await demo.supportsInterface('0x7a0cdf92'); - assert.equal(isSupport, true , "supportsInterface error"); - - }); - - it("test ERC5007Composable", async () => { - const Alice = accounts[0]; - const Bob = accounts[1]; - const Carl = accounts[2]; - - const instance = await ERC5007ComposableTest.deployed("ERC5007ComposableTest", "ERC5007ComposableTest"); - const demo = instance; - - let now = Math.floor(new Date().getTime()/1000); - let token1InputStartTime = new BigNumber(now - 10000); - let token1InputEndTime = new BigNumber(now + 10000); - let id1 = 1; - let assetId = 1000; - - console.log("mint NFT:") - await demo.mint(Alice, id1, assetId, token1InputStartTime.toFixed(0), - token1InputEndTime.toFixed(0)); - - let token1OutputStartTime = new BigNumber( await demo.startTime(id1)); - let token1OutputEndTime = new BigNumber( await demo.endTime(id1)); - let token1assetId = new BigNumber( await demo.assetId(id1)); - assert.equal(token1InputStartTime.comparedTo(token1OutputStartTime) == 0 - && token1InputEndTime.comparedTo(token1OutputEndTime) == 0 - && token1assetId.comparedTo(assetId) == 0, - true, "wrong data"); - - let id2 = 2; - let id3 = 3; - let splitTime = token1InputStartTime.plus(5000); - console.log("split NFT:") - await demo.split(id1, id2, Bob, id3, Carl, splitTime.toFixed(0)); - - let token2StartTime = new BigNumber( await demo.startTime(id2)); - let token2EndTime = new BigNumber( await demo.endTime(id2)); - - let token3StartTime = new BigNumber( await demo.startTime(id3)); - let token3EndTime = new BigNumber( await demo.endTime(id3)); - - assert.equal(token1InputStartTime.comparedTo(token2StartTime) == 0 - && token2EndTime.comparedTo(splitTime) == 0, true, "wrong data"); - - assert.equal(token3StartTime.comparedTo(splitTime.plus(1)) == 0 - && token3EndTime.comparedTo(token1InputEndTime) == 0, true, "wrong data"); - - let token2assetId = await demo.assetId(id2); - let token3assetId = await demo.assetId(id3); - assert.equal(token2assetId == assetId && token3assetId == assetId, true, 'wrong data'); - - - console.log("merge NFT:") - let id4 = 4; - await demo.setApprovalForAll(Alice, true,{from: Bob}); - await demo.setApprovalForAll(Alice, true,{from: Carl}); - await demo.merge(id2, id3, Alice, id4); - - let token4StartTime = new BigNumber( await demo.startTime(id4)); - let token4EndTime = new BigNumber( await demo.endTime(id4)); - let token4assetId = await demo.assetId(id4); - let token4Owner = await demo.ownerOf(id4); - - assert.equal(token1InputStartTime.comparedTo(token4StartTime) == 0 - && token4EndTime.comparedTo(token1InputEndTime) == 0, true, "wrong start time or end time"); - - assert.equal(token4assetId == assetId, true, 'wrong rootId'); - assert.equal(token4Owner == Alice, true, 'wrong owner'); - - - console.log("IERC5007Composable InterfaceId:", await demo.getInterfaceId()) - let isSupport = await demo.supportsInterface('0x75cf3842'); - assert.equal(isSupport, true , "supportsInterface error"); - }); - -}); diff --git a/assets/eip-5007/truffle-config.js b/assets/eip-5007/truffle-config.js deleted file mode 100644 index ccc194a481b5f6..00000000000000 --- a/assets/eip-5007/truffle-config.js +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Use this file to configure your truffle project. It's seeded with some - * common settings for different networks and features like migrations, - * compilation and testing. Uncomment the ones you need or modify - * them to suit your project as necessary. - * - * More information about configuration can be found at: - * - * trufflesuite.com/docs/advanced/configuration - * - * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider) - * to sign your transactions before they're sent to a remote public node. Infura accounts - * are available for free at: infura.io/register. - * - * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate - * public/private key pairs. If you're publishing your code to GitHub make sure you load this - * phrase from a file you've .gitignored so it doesn't accidentally become public. - * - */ - -// const HDWalletProvider = require('@truffle/hdwallet-provider'); -// -// const fs = require('fs'); -// const mnemonic = fs.readFileSync(".secret").toString().trim(); - -module.exports = { - /** - * Networks define how you connect to your ethereum client and let you set the - * defaults web3 uses to send transactions. If you don't specify one truffle - * will spin up a development blockchain for you on port 9545 when you - * run `develop` or `test`. You can ask a truffle command to use a specific - * network from the command line, e.g - * - * $ truffle test --network - */ - - networks: { - // Useful for testing. The `development` name is special - truffle uses it by default - // if it's defined here and no other network is specified at the command line. - // You should run a client (like ganache-cli, geth or parity) in a separate terminal - // tab if you use this network and you must also set the `host`, `port` and `network_id` - // options below to some value. - // - // development: { - // host: "127.0.0.1", // Localhost (default: none) - // port: 8545, // Standard Ethereum port (default: none) - // network_id: "*", // Any network (default: none) - // }, - // Another network with more advanced options... - // advanced: { - // port: 8777, // Custom port - // network_id: 1342, // Custom network - // gas: 8500000, // Gas sent with each transaction (default: ~6700000) - // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) - // from:
, // Account to send txs from (default: accounts[0]) - // websocket: true // Enable EventEmitter interface for web3 (default: false) - // }, - // Useful for deploying to a public network. - // NB: It's important to wrap the provider as a function. - // ropsten: { - // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), - // network_id: 3, // Ropsten's id - // gas: 5500000, // Ropsten has a lower block limit than mainnet - // confirmations: 2, // # of confs to wait between deployments. (default: 0) - // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) - // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) - // }, - // Useful for private networks - // private: { - // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), - // network_id: 2111, // This network is yours, in the cloud. - // production: true // Treats this network as if it was a public net. (default: false) - // } - }, - - // Set default mocha options here, use special reporters etc. - mocha: { - // timeout: 100000 - }, - - // Configure your compilers - compilers: { - solc: { - version: "0.8.10", // Fetch exact version from solc-bin (default: truffle's version) - // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) - settings: { // See the solidity docs for advice about optimization and evmVersion - optimizer: { - enabled: false, - runs: 200 - } - // , - // evmVersion: "byzantium" - // } - } - }, - - // Truffle DB is currently disabled by default; to enable it, change enabled: - // false to enabled: true. The default storage location can also be - // overridden by specifying the adapter settings, as shown in the commented code below. - // - // NOTE: It is not possible to migrate your contracts to truffle DB and you should - // make a backup of your artifacts to a safe location before enabling this feature. - // - // After you backed up your artifacts you can utilize db by running migrate as follows: - // $ truffle migrate --reset --compile-all - // - // db: { - // enabled: false, - // host: "127.0.0.1", - // adapter: { - // name: "sqlite", - // settings: { - // directory: ".db" - // } - // } - } -}; diff --git a/assets/eip-5008/.gitignore b/assets/eip-5008/.gitignore deleted file mode 100644 index b55321bbe1c241..00000000000000 --- a/assets/eip-5008/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -package-lock.json -typechain/ -cache/ -artifacts/ diff --git a/assets/eip-5008/contracts/ERC5008.sol b/assets/eip-5008/contracts/ERC5008.sol deleted file mode 100644 index 99fa042b157d5a..00000000000000 --- a/assets/eip-5008/contracts/ERC5008.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5008.sol"; - -contract ERC5008 is ERC721, IERC5008 { - mapping(uint256 => uint256) private _tokenNonce; - - constructor(string memory name_, string memory symbol_)ERC721(name_, symbol_){ - } - - /// @notice Get the nonce of an NFT - /// Throws if `tokenId` is not a valid NFT - /// @param tokenId The NFT to get the nonce for - /// @return The nonce of this NFT - function nonce(uint256 tokenId) public virtual override view returns(uint256) { - require(_exists(tokenId), "Error: query for nonexistent token"); - - return _tokenNonce[tokenId]; - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId); - _tokenNonce[tokenId]++; - emit NonceChanged(tokenId, _tokenNonce[tokenId]); - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC5008).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5008/contracts/ERC5008Demo.sol b/assets/eip-5008/contracts/ERC5008Demo.sol deleted file mode 100644 index 89a80de8c51a26..00000000000000 --- a/assets/eip-5008/contracts/ERC5008Demo.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5008.sol"; - -contract ERC5008Demo is ERC5008{ - - constructor(string memory name_, string memory symbol_)ERC5008(name_, symbol_){ - } - - /// @notice mint a new NFT - /// @param to The owner of the new token - /// @param tokenId The id of the new token - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } - - function getInterfaceId() public pure returns (bytes4) { - return type(IERC5008).interfaceId; - } -} diff --git a/assets/eip-5008/contracts/IERC5008.sol b/assets/eip-5008/contracts/IERC5008.sol deleted file mode 100644 index a7b3741ed3e97d..00000000000000 --- a/assets/eip-5008/contracts/IERC5008.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5008 /* is IERC165 */ { - /// @notice Emitted when the `nonce` of an NFT is changed - event NonceChanged(uint256 tokenId, uint256 nonce); - - /// @notice Get the nonce of an NFT - /// Throws if `tokenId` is not a valid NFT - /// @param tokenId The id of the NFT - /// @return The nonce of the NFT - function nonce(uint256 tokenId) external view returns(uint256); -} diff --git a/assets/eip-5008/hardhat.config.ts b/assets/eip-5008/hardhat.config.ts deleted file mode 100644 index 9b10629aa7ea8c..00000000000000 --- a/assets/eip-5008/hardhat.config.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HardhatUserConfig, task } from "hardhat/config"; -import "@nomiclabs/hardhat-waffle"; -import "@typechain/hardhat"; - -// This is a sample Hardhat task. To learn how to create your own go to -// https://hardhat.org/guides/create-task.html -task("accounts", "Prints the list of accounts", async (taskArgs, hre) => { - const accounts = await hre.ethers.getSigners(); - - for (const account of accounts) { - console.log(account.address); - } -}); - - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.9", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - }, - -}; - - - -export default config; diff --git a/assets/eip-5008/package.json b/assets/eip-5008/package.json deleted file mode 100644 index 8d51880b185c16..00000000000000 --- a/assets/eip-5008/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "EIP-5008", - "scripts": { - "test": "npx hardhat test" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.7.3", - "@nomiclabs/hardhat-ethers": "^2.0.5", - "@nomiclabs/hardhat-waffle": "^2.0.3", - "@typechain/ethers-v5": "^7.2.0", - "@typechain/hardhat": "^2.3.1", - "@types/chai": "^4.3.0", - "@types/mocha": "^9.1.0", - "@types/node": "^12.20.47", - "chai": "^4.3.6", - "ethers": "^5.6.1", - "hardhat": "^2.9.2", - "solhint": "^3.3.7", - "ts-node": "^10.8.1", - "typechain": "^5.2.0", - "typescript": "^4.6.3" - } -} diff --git a/assets/eip-5008/test/test.ts b/assets/eip-5008/test/test.ts deleted file mode 100644 index 5d85e2becf934e..00000000000000 --- a/assets/eip-5008/test/test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("Test ERC5008 ", function () { - - it("test nonce", async function () { - let [alice, bob] = await ethers.getSigners(); - - const ERC5008Demo = await ethers.getContractFactory("ERC5008Demo"); - - let contract = await ERC5008Demo.deploy("ERC5008Demo","ERC5008Demo"); - - let tokenId = 1; - await expect(contract.mint(alice.address, tokenId)).to.emit(contract, "NonceChanged").withArgs(tokenId, 1); - - expect(await contract.nonce(tokenId)).equals(1); - - - await expect(contract.transferFrom(alice.address, bob.address, tokenId)).to.emit(contract, "NonceChanged").withArgs(tokenId, 2); - - - expect(await contract.nonce(tokenId)).equals(2); - - console.log("IERC5008 InterfaceId:", await contract.getInterfaceId()) - let isSupport = await contract.supportsInterface('0xce03fdab'); - expect(isSupport).equals(true , "supportsInterface error"); - - }); -}); diff --git a/assets/eip-5008/tsconfig.json b/assets/eip-5008/tsconfig.json deleted file mode 100644 index c458030680cbc4..00000000000000 --- a/assets/eip-5008/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "strict": false, - "esModuleInterop": true, - "outDir": "dist", - "declaration": true - }, - "include": ["./test", "./typechain"], - "files": ["./hardhat.config.ts"] -} diff --git a/assets/eip-5050/ActionsSet.sol b/assets/eip-5050/ActionsSet.sol deleted file mode 100644 index 36be830a48d3bc..00000000000000 --- a/assets/eip-5050/ActionsSet.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Based on OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol) - -pragma solidity ^0.8.0; - -library ActionsSet { - struct Set { - // Storage of action names - string[] _names; - // Storage of action selectors - bytes4[] _selectors; - // Position of the value in the `values` array, plus 1 because index 0 - // means a value is not in the set. - mapping(bytes4 => uint256) _indexes; - } - - /** - * @dev Add a value to a set. O(1). - * - * Returns true if the value was added to the set, that is if it was not - * already present. - */ - function add(Set storage set, string memory name) internal returns (bool) { - bytes4 selector = bytes4(keccak256(bytes(name))); - if (!contains(set, selector)) { - set._selectors.push(selector); - set._names.push(name); - // The value is stored at length-1, but we add 1 to all indexes - // and use 0 as a sentinel value - set._indexes[selector] = set._selectors.length; - return true; - } else { - return false; - } - } - - /** - * @dev Removes a value from a set. O(1). - * - * Returns true if the value was removed from the set, that is if it was - * present. - */ - function remove(Set storage set, bytes4 value) internal returns (bool) { - // We read and store the value's index to prevent multiple reads from the same storage slot - uint256 valueIndex = set._indexes[value]; - - if (valueIndex != 0) { - // Equivalent to contains(set, value) - // To delete an element from the _selectors array in O(1), we swap the element to delete with the last one in - // the array, and then remove the last element (sometimes called as 'swap and pop'). - // This modifies the order of the array, as noted in {at}. - - uint256 toDeleteIndex = valueIndex - 1; - uint256 lastIndex = set._selectors.length - 1; - - if (lastIndex != toDeleteIndex) { - bytes4 lastValue = set._selectors[lastIndex]; - - // Move the last value to the index where the value to delete is - set._selectors[toDeleteIndex] = lastValue; - // Update the index for the moved value - set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex - } - - // Delete the slot where the moved value was stored - set._selectors.pop(); - - // Delete the index for the deleted slot - delete set._indexes[value]; - - return true; - } else { - return false; - } - } - - /** - * @dev Returns true if the value is in the set. O(1). - */ - function contains(Set storage set, bytes4 value) - internal - view - returns (bool) - { - return set._indexes[value] != 0; - } - - /** - * @dev Returns the number of values on the set. O(1). - */ - function length(Set storage set) internal view returns (uint256) { - return set._selectors.length; - } - - /** - * @dev Returns the value stored at position `index` in the set. O(1). - * - * Note that there are no guarantees on the ordering of values inside the - * array, and it may change when more values are added or removed. - * - * Requirements: - * - * - `index` must be strictly less than {length}. - */ - function at(Set storage set, uint256 index) internal view returns (bytes4) { - return set._selectors[index]; - } - - /** - * @dev Return the entire set of action names - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function names(Set storage set) internal view returns (string[] memory) { - return set._names; - } - - /** - * @dev Return the entire set of action selectors - * - * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed - * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that - * this function has an unbounded cost, and using it as part of a state-changing function may render the function - * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. - */ - function selectors(Set storage set) internal view returns (bytes4[] memory) { - return set._selectors; - } - - -} diff --git a/assets/eip-5050/ERC5050.sol b/assets/eip-5050/ERC5050.sol deleted file mode 100644 index e2608d9f9b62dd..00000000000000 --- a/assets/eip-5050/ERC5050.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5050Sender.sol"; -import "./ERC5050Receiver.sol"; - -contract ERC5050 is ERC5050Sender, ERC5050Receiver { - function _registerAction(bytes4 action) internal { - _registerReceivable(action); - _registerSendable(action); - } -} diff --git a/assets/eip-5050/ERC5050Receiver.sol b/assets/eip-5050/ERC5050Receiver.sol deleted file mode 100644 index 254d01d7051c08..00000000000000 --- a/assets/eip-5050/ERC5050Receiver.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {IERC5050Sender, IERC5050Receiver, Action} from "./IERC5050.sol"; -import {ActionsSet} from "./ActionsSet.sol"; - -contract ERC5050Receiver is IERC5050Receiver { - using ActionsSet for ActionsSet.Set; - - ActionsSet.Set _receivableActions; - - modifier onlyReceivableAction(Action calldata action, uint256 nonce) { - require( - action.to._address == address(this), - "ERC5050: invalid receiver" - ); - require( - _receivableActions.contains(action.selector), - "ERC5050: invalid action" - ); - require( - action.from._address == address(0) || - action.from._address == msg.sender, - "ERC5050: invalid sender" - ); - require( - (action.from._address != address(0) && action.user == tx.origin) || - action.user == msg.sender, - "ERC5050: invalid sender" - ); - _; - } - - function receivableActions() external view returns (string[] memory) { - return _receivableActions.names(); - } - - function onActionReceived(Action calldata action, uint256 nonce) - external - payable - virtual - override - onlyReceivableAction(action, nonce) - { - _onActionReceived(action, nonce); - } - - function _onActionReceived(Action calldata action, uint256 nonce) - internal - virtual - { - if (action.state != address(0)) { - require(action.state.isContract(), "ERC5050: invalid state"); - try - IERC5050Receiver(action.state).onActionReceived{ - value: msg.value - }(action, nonce) - {} catch (bytes memory reason) { - if (reason.length == 0) { - revert("ERC5050: call to non ERC5050Receiver"); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - emit ActionReceived( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data - ); - } - - function _registerReceivable(string memory action) internal { - _receivableActions.add(action); - } -} diff --git a/assets/eip-5050/ERC5050Sender.sol b/assets/eip-5050/ERC5050Sender.sol deleted file mode 100644 index 3eb67309865a55..00000000000000 --- a/assets/eip-5050/ERC5050Sender.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {IERC5050Sender, IERC5050Receiver, Action} from "./IERC5050.sol"; -import {ActionsSet} from "./ActionsSet.sol"; - -contract ERC5050Sender is IERC5050Sender { - using ActionsSet for ActionsSet.Set; - - ActionsSet.Set _sendableActions; - - uint256 private _nonce; - bytes32 private _hash; - - mapping(address => mapping(bytes4 => address)) actionApprovals; - mapping(address => mapping(address => bool)) operatorApprovals; - - function sendAction(Action memory action) - external - payable - virtual - override - { - _sendAction(action); - } - - function isValid(bytes32 actionHash, uint256 nonce) - external - view - returns (bool) - { - return actionHash == _hash && nonce == _nonce; - } - - function sendableActions() external view returns (string[] memory) { - return _sendableActions.names(); - } - - modifier onlySendableAction(Action memory action) { - require( - _sendableActions.contains(action.selector), - "ERC5050: invalid action" - ); - require( - _isApprovedOrSelf(action.user, action.selector), - "ERC5050: unapproved sender" - ); - _; - } - - function approveForAction( - address _account, - bytes4 _action, - address _approved - ) public virtual override returns (bool) { - require(_approved != _account, "ERC5050: approve to caller"); - - require( - msg.sender == _account || - isApprovedForAllActions(_account, msg.sender), - "ERC5050: approve caller is not account nor approved for all" - ); - - actionApprovals[_account][_action] = _approved; - emit ApprovalForAction(_account, _action, _approved); - - return true; - } - - function setApprovalForAllActions(address _operator, bool _approved) - public - virtual - override - { - require(msg.sender != _operator, "ERC5050: approve to caller"); - - operatorApprovals[msg.sender][_operator] = _approved; - - emit ApprovalForAllActions(msg.sender, _operator, _approved); - } - - function getApprovedForAction(address _account, bytes4 _action) - public - view - returns (address) - { - return actionApprovals[_account][_action]; - } - - function isApprovedForAllActions(address _account, address _operator) - public - view - returns (bool) - { - return operatorApprovals[_account][_operator]; - } - - function _sendAction(Action memory action) internal { - action.from._address = address(this); - bool toIsContract = action.to._address.isContract(); - bool stateIsContract = action.state.isContract(); - address next; - if (toIsContract) { - next = action.to._address; - } else if (stateIsContract) { - next = action.state; - } - uint256 nonce; - if (toIsContract && stateIsContract) { - _validate(action); - nonce = _nonce; - } - if (next.isContract()) { - try - IERC5050Receiver(next).onActionReceived{value: msg.value}( - action, - nonce - ) - {} catch Error(string memory err) { - revert(err); - } catch (bytes memory returnData) { - if (returnData.length > 0) { - revert(string(returnData)); - } - } - } - emit SendAction( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data - ); - } - - function _validate(Action memory action) internal { - ++_nonce; - _hash = bytes32( - keccak256( - abi.encodePacked( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data, - _nonce - ) - ) - ); - } - - function _isApprovedOrSelf(address account, bytes4 action) - internal - view - returns (bool) - { - return (msg.sender == account || - isApprovedForAllActions(account, msg.sender) || - getApprovedForAction(account, action) == msg.sender); - } - - function _registerSendable(string memory action) internal { - _sendableActions.add(action); - } -} diff --git a/assets/eip-5050/ERC5050State.sol b/assets/eip-5050/ERC5050State.sol deleted file mode 100644 index 55567a38224a2b..00000000000000 --- a/assets/eip-5050/ERC5050State.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {IERC5050Sender, IERC5050Receiver, Action} from "./IERC5050.sol"; -import {ActionsSet} from "./ActionsSet.sol"; - -contract ERC5050State is IERC5050Receiver { - using ActionsSet for ActionsSet.Set; - - ActionsSet.Set private _receivableActions; - - function onActionReceived(Action calldata action, uint256 nonce) - external - payable - virtual - override - onlyReceivableAction(action, nonce) - { - _onActionReceived(action, nonce); - } - - function receivableActions() external view returns (string[] memory) { - return _receivableActions.names(); - } - - modifier onlyReceivableAction(Action calldata action, uint256 nonce) { - require( - _receivableActions.contains(action.selector), - "ERC5050: invalid action" - ); - require(action.state == address(this), "ERC5050: invalid state"); - require( - action.user == address(0) || action.user == tx.origin, - "ERC5050: invalid user" - ); - - address expectedSender = action.to._address; - if (expectedSender == address(0)) { - if (action.from._address != address(0)) { - expectedSender = action.from._address; - } else { - expectedSender = action.user; - } - } - require(msg.sender == expectedSender, "ERC5050: invalid sender"); - - // State contracts must validate the action with the `from` contract in - // the case of a 3-contract chain (`from`, `to` and `state`) all set to - // valid contract addresses. - if ( - action.to._address.isContract() && action.from._address.isContract() - ) { - uint256 actionHash = uint256( - keccak256( - abi.encodePacked( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data, - nonce - ) - ) - ); - try - IERC5050Sender(action.from._address).isValid(actionHash, nonce) - returns (bool ok) { - require(ok, "ERC5050: action not validated"); - } catch (bytes memory reason) { - if (reason.length == 0) { - revert("ERC5050: call to non ERC5050Sender"); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - _; - } - - function _onActionReceived(Action calldata action, uint256 nonce) - internal - virtual - { - emit ActionReceived( - action.selector, - action.user, - action.from._address, - action.from._tokenId, - action.to._address, - action.to._tokenId, - action.state, - action.data - ); - } - - function _registerReceivable(string memory action) internal { - _receivableActions.add(action); - } -} diff --git a/assets/eip-5050/ExampleStateContract.sol b/assets/eip-5050/ExampleStateContract.sol deleted file mode 100644 index a609bc9e695f71..00000000000000 --- a/assets/eip-5050/ExampleStateContract.sol +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {ERC5050State, Action} from "./ERC5050State.sol"; -import {ERC5050, Action} from "./ERC5050.sol"; - -struct TokenInfo { - uint256 health; - uint256 healthRemaining; - uint256 power; - uint256 blockedAt; - uint256 blockPower; - uint256 lockedUntilBlock; - uint256 wins; - bool hasRegistered; -} - -interface IFightGame { - function getStats(address _contract, uint256 _tokenId) external view returns (TokenInfo); -} - -contract FightGame is IFightGame, ERC5050State { - - bytes4 constant LIGHT_ATTACK_SELECTOR = bytes4(keccak256("fg.light-attack")); - bytes4 constant HEAVY_ATTACK_SELECTOR = bytes4(keccak256("fg.heavy-attack")); - bytes4 constant BLOCK_SELECTOR = bytes4(keccak256("fg.block")); - - uint256 constant BLOCK_DECAY = 100; - uint256 constant LIGHT_ATTACK_DECAY = 200; - uint256 constant HEAVY_ATTACK_DECAY = 500; - - mapping(address => mapping(uint256 => TokenInfo)) state; - - constructor() { - _registerReceivable("fg.light-attack"); - _registerReceivable("fg.heavy-attack"); - _registerReceivable("fg.block"); - } - - function register(address _contract, uint256 _tokenId) external { - require(msg.sender == ownerOf(_contract, _tokenId), "sender not token owner"); - require(!state[_contract][_tokenId].hasRegistered, "token already registered"); - state[_contract][_tokenId] = TokenInfo(100, 100, 5, 0, 0, 0, true); - } - - function getStats(address _contract, uint256 _tokenId) external view returns (TokenInfo){ - return state[_contract][_tokenId]; - } - - function onActionReceived(Action calldata action, uint256 _nonce) - external - payable - override - onlyReceivableAction(action, _nonce) - { - TokenInfo storage from = state[action.from._address][action.from._tokenId]; - require(from.healthRemaining > 0, "health 0"); - require(block.number > from.lockedUntilBlock, "token locked"); - if (action.selector == BLOCK_SELECTOR) { - from.blockPower = from.power * 3; - from.blockedAt = block.number; - from.lockedUntilBlock = block.number + BLOCK_DECAY; - return; - } - - TokenInfo storage to = state[action.to._address][action.to._tokenId]; - require(to.healthRemaining > 0, "target health 0"); - - uint256 damage; - if (action.selector == LIGHT_ATTACK_SELECTOR ) { - damage = from.power; - from.lockedUntilBlock = block.number + LIGHT_ATTACK_DECAY; - } - - if (action.selector == HEAVY_ATTACK_SELECTOR) { - damage = from.power * 3; - from.lockedUntilBlock = block.number + HEAVY_ATTACK_DECAY; - } - if(to.blockedAt + BLOCK_DECAY > block.number) { - if(to.blockPower >= damage){ - to.blockPower -= damage; - return; - } - damage -= to.blockPower; - } - if(to.healthRemaining > damage){ - to.healthRemaining -= damage; - return; - } - - // Winner gains loser's power and some health - from.power += to.power; - from.healthRemaining += to.power; - from.wins++; - to.healthRemaining = 0; - } -} - -contract Fighter is ERC5050, ERC721 { - - IFightGame stateContract; - - constructor(address _stateContract) { - _registerAction("fg.light-attack"); - _registerAction("fg.heavy-attack"); - _registerSendable("fg.block"); - stateContract = IFightGame(_stateContract); - } - - // Update NFT render / metadata based on game stats - function tokenURICharacterEmoji(uint256 tokenId) - public - view - override - returns (string memory) - { - TokenInfo memory stats = stateContract.getStats(address(this), tokenId); - if(stats.healthRemaining == 0){ - return unicode"😵"; - } - if(stats.power > 100){ - return unicode"🦾"; - } - if(stats.power > 50){ - return unicode"💪"; - } - if(stats.power > 20){ - return unicode"🤩"; - } - if(stats.power > 5){ - return unicode"😃"; - } - return unicode"😀"; - } -} \ No newline at end of file diff --git a/assets/eip-5050/ExampleToken2Token.sol b/assets/eip-5050/ExampleToken2Token.sol deleted file mode 100644 index 53e0b0243f176e..00000000000000 --- a/assets/eip-5050/ExampleToken2Token.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import {ERC5050, Action} from "./ERC5050.sol"; - -contract Spells is ERC5050, ERC721 { - - bytes4 constant CAST_SELECTOR = bytes4(keccak256("cast")); - bytes4 constant ATTUNE_SELECTOR = bytes4(keccak256("attune")); - - mapping(uint256 => uint256) spellDust; - mapping(uint256 => string) attunement; - - constructor() ERC721("Spells", unicode"🔮") { - _registerSendable("cast"); - _registerReceivable("attune"); - } - - function sendAction(Action memory action) - external - payable - override - onlySendableAction(action) - { - require( - msg.sender == ownerOf(action.from._tokenId), - "Spells: invalid sender" - ); - _sendAction(action); - } - - function onActionReceived(Action calldata action, uint256 _nonce) - external - payable - override - onlyReceivableAction(action, _nonce) - { - if (action.selector == ATTUNE_SELECTOR) { - string memory unicodeChar; - bytes memory _data = action.data; - assembly { - // Read unicode character from first 6 bytes (\u5050) - unicodeChar := shr(208, _data) - } - attunement[action.to._tokenId] = unicodeChar; - } - // Pass action to state receiver if specified - _onActionReceived(action, _nonce); - } - - string[12] private dust = [ - unicode"․", - unicode"∴", - unicode"`" - ]; - - string[5] private spells = [ - "Conjuring", - "Divining", - "Transforming", - "Hexing", - "Banishing" - ]; - - function tokenURI(uint256 tokenId) - public - view - override - returns (string memory) - { - string - memory out = ''; - - out = string.concat( - out, - string.concat(spells[_spellType(tokenId)], " Spell"), - '', - attunement[tokenId] - ); - out = string.concat(out, ""); - string memory json = Base64.encode( - bytes( - string( - abi.encodePacked( - '{"name": "Spell #', - Strings.toString(tokenId), - '", "description": "Cast spells, attune spells.", "image": "data:image/svg+xml;base64,', - Base64.encode(bytes(out)), - '"}' - ) - ) - ) - ); - return string(abi.encodePacked("data:application/json;base64,", json)); - } - - function _spellType(uint256 tokenId) internal pure returns (uint256) { - uint256 rand = _random(Strings.toString(tokenId)); - return rand % 6; - } - - function _random(string memory input) internal pure returns (uint256) { - return uint256(keccak256(abi.encodePacked(input))); - } -} \ No newline at end of file diff --git a/assets/eip-5050/IERC5050.sol b/assets/eip-5050/IERC5050.sol deleted file mode 100644 index 95cf778335c6be..00000000000000 --- a/assets/eip-5050/IERC5050.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/// @title ERC-xxxx Token Interaction Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-xxx -interface IERC5050Sender { - /// @notice Send an action to the target address - /// @dev The action's `fromContract` is automatically set to `address(this)`, - /// and the `from` parameter is set to `msg.sender`. - /// @param action The action to send - function sendAction(Action memory action) external payable; - - /// @notice Check if an action is valid based on its hash and nonce - /// @dev When an action passes through all three possible contracts - /// (`fromContract`, `to`, and `state`) the `state` contract validates the - /// action with the initiating `fromContract` using a nonced action hash. - /// This hash is calculated and saved to storage on the `fromContract` before - /// action handling is initiated. The `state` contract calculates the hash - /// and verifies it and nonce with the `fromContract`. - /// @param _hash The hash to validate - /// @param _nonce The nonce to validate - function isValid(bytes32 _hash, uint256 _nonce) external returns (bool); - - /// @notice Retrieve list of actions that can be sent. - /// @dev Intended for use by off-chain applications to query compatible contracts. - function sendableActions() external view returns (string[] memory); - - /// @notice Change or reaffirm the approved address for an action - /// @dev The zero address indicates there is no approved address. - /// Throws unless `msg.sender` is the `_account`, or an authorized - /// operator of the `_account`. - /// @param _account The account of the account-action pair to approve - /// @param _action The action of the account-action pair to approve - /// @param _approved The new approved account-action controller - function approveForAction( - address _account, - bytes4 _action, - address _approved - ) external returns (bool); - - /// @notice Enable or disable approval for a third party ("operator") to conduct - /// all actions on behalf of `msg.sender` - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// multiple operators per owner. - /// @param _operator Address to add to the set of authorized operators - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAllActions(address _operator, bool _approved) - external; - - /// @notice Get the approved address for an account-action pair - /// @dev Throws if `_tokenId` is not a valid NFT. - /// @param _account The account of the account-action to find the approved address for - /// @param _action The action of the account-action to find the approved address for - /// @return The approved address for this account-action, or the zero address if - /// there is none - function getApprovedForAction(address _account, bytes4 _action) - external - view - returns (address); - - /// @notice Query if an address is an authorized operator for another address - /// @param _account The address on whose behalf actions are performed - /// @param _operator The address that acts on behalf of the account - /// @return True if `_operator` is an approved operator for `_account`, false otherwise - function isApprovedForAllActions(address _account, address _operator) - external - view - returns (bool); - - /// @dev This emits when an action is sent (`sendAction()`) - event SendAction( - bytes4 indexed name, - address _from, - address indexed _fromContract, - uint256 _tokenId, - address indexed _to, - uint256 _toTokenId, - address _state, - bytes _data - ); - - /// @dev This emits when the approved address for an account-action pair - /// is changed or reaffirmed. The zero address indicates there is no - /// approved address. - event ApprovalForAction( - address indexed _account, - bytes4 indexed _action, - address indexed _approved - ); - - /// @dev This emits when an operator is enabled or disabled for an account. - /// The operator can conduct all actions on behalf of the account. - event ApprovalForAllActions( - address indexed _account, - address indexed _operator, - bool _approved - ); -} - -interface IERC5050Receiver { - /// @notice Handle an action - /// @dev Both the `to` contract and `state` contract are called via - /// `onActionReceived()`. - /// @param action The action to handle - function onActionReceived(Action calldata action, uint256 _nonce) - external - payable; - - /// @notice Retrieve list of actions that can be received. - /// @dev Intended for use by off-chain applications to query compatible contracts. - function receivableActions() external view returns (string[] memory); - - /// @dev This emits when a valid action is received. - event ActionReceived( - bytes4 indexed name, - address _from, - address indexed _fromContract, - uint256 _tokenId, - address indexed _to, - uint256 _toTokenId, - address _state, - bytes _data - ); -} - -/// @param _address The address of the interactive object -/// @param tokenId The token that is interacting (optional) -struct Object { - address _address; - uint256 _tokenId; -} - -/// @param name The name of the action -/// @param user The address of the sender -/// @param from The initiating object -/// @param to The receiving object -/// @param state The state contract -/// @param data Additional data with no specified format -struct Action { - bytes4 selector; - address user; - Object from; - Object to; - address state; - bytes data; -} diff --git a/assets/eip-5058/ERC5058.sol b/assets/eip-5058/ERC5058.sol deleted file mode 100644 index 90fc3efcdd9594..00000000000000 --- a/assets/eip-5058/ERC5058.sol +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC5058.sol"; - -/** - * @dev Implementation ERC721 Lockable Token - */ -abstract contract ERC5058 is ERC721, IERC5058 { - // Mapping from token ID to unlock time - mapping(uint256 => uint256) public lockedTokens; - - // Mapping from token ID to lock approved address - mapping(uint256 => address) private _lockApprovals; - - // Mapping from owner to lock operator approvals - mapping(address => mapping(address => bool)) private _lockOperatorApprovals; - - /** - * @dev See {IERC5058-lockApprove}. - */ - function lockApprove(address to, uint256 tokenId) public virtual override { - require(!isLocked(tokenId), "ERC5058: token is locked"); - address owner = ERC721.ownerOf(tokenId); - require(to != owner, "ERC5058: lock approval to current owner"); - - require( - _msgSender() == owner || isLockApprovedForAll(owner, _msgSender()), - "ERC5058: lock approve caller is not owner nor approved for all" - ); - - _lockApprove(owner, to, tokenId); - } - - /** - * @dev See {IERC5058-getLockApproved}. - */ - function getLockApproved(uint256 tokenId) public view virtual override returns (address) { - require(_exists(tokenId), "ERC5058: lock approved query for nonexistent token"); - - return _lockApprovals[tokenId]; - } - - /** - * @dev See {IERC5058-lockerOf}. - */ - function lockerOf(uint256 tokenId) public view virtual override returns (address) { - require(_exists(tokenId), "ERC5058: locker query for nonexistent token"); - require(isLocked(tokenId), "ERC5058: locker query for non-locked token"); - - return _lockApprovals[tokenId]; - } - - /** - * @dev See {IERC5058-setLockApprovalForAll}. - */ - function setLockApprovalForAll(address operator, bool approved) public virtual override { - _setLockApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @dev See {IERC5058-isLockApprovedForAll}. - */ - function isLockApprovedForAll(address owner, address operator) public view virtual override returns (bool) { - return _lockOperatorApprovals[owner][operator]; - } - - /** - * @dev See {IERC5058-isLocked}. - */ - function isLocked(uint256 tokenId) public view virtual override returns (bool) { - return lockedTokens[tokenId] > block.number; - } - - /** - * @dev See {IERC5058-lockExpiredTime}. - */ - function lockExpiredTime(uint256 tokenId) public view virtual override returns (uint256) { - return lockedTokens[tokenId]; - } - - /** - * @dev See {IERC5058-lock}. - */ - function lock(uint256 tokenId, uint256 expired) public virtual override { - //solhint-disable-next-line max-line-length - require(_isLockApprovedOrOwner(_msgSender(), tokenId), "ERC5058: lock caller is not owner nor approved"); - require(expired > block.number, "ERC5058: expired time must be greater than current block number"); - require(!isLocked(tokenId), "ERC5058: token is locked"); - - _lock(_msgSender(), tokenId, expired); - } - - /** - * @dev See {IERC5058-unlock}. - */ - function unlock(uint256 tokenId) public virtual override { - require(lockerOf(tokenId) == _msgSender(), "ERC5058: unlock caller is not lock operator"); - - address from = ERC721.ownerOf(tokenId); - - _beforeTokenLock(_msgSender(), from, tokenId, 0); - - delete lockedTokens[tokenId]; - - emit Unlocked(_msgSender(), from, tokenId); - - _afterTokenLock(_msgSender(), from, tokenId, 0); - } - - /** - * @dev Locks `tokenId` from `from` until `expired`. - * - * Requirements: - * - * - `tokenId` token must be owned by `from`. - * - * Emits a {Locked} event. - */ - function _lock( - address operator, - uint256 tokenId, - uint256 expired - ) internal virtual { - address owner = ERC721.ownerOf(tokenId); - - _beforeTokenLock(operator, owner, tokenId, expired); - - lockedTokens[tokenId] = expired; - _lockApprovals[tokenId] = operator; - - emit Locked(operator, owner, tokenId, expired); - - _afterTokenLock(operator, owner, tokenId, expired); - } - - /** - * @dev Safely mints `tokenId` and transfers it to `to`, but the `tokenId` is locked and cannot be transferred. - * - * Requirements: - * - * - `tokenId` must not exist. - * - * Emits {Locked} and {Transfer} event. - */ - function _safeLockMint( - address to, - uint256 tokenId, - uint256 expired, - bytes memory _data - ) internal virtual { - require(expired > block.number, "ERC5058: lock mint for invalid lock block number"); - - _safeMint(to, tokenId, _data); - - _lock(_msgSender(), tokenId, expired); - } - - /** - * @dev See {ERC721-_burn}. This override additionally clears the lock approvals for the token. - */ - function _burn(uint256 tokenId) internal virtual override { - address owner = ERC721.ownerOf(tokenId); - super._burn(tokenId); - - _beforeTokenLock(_msgSender(), owner, tokenId, 0); - - // clear lock approvals - delete lockedTokens[tokenId]; - delete _lockApprovals[tokenId]; - - _afterTokenLock(_msgSender(), owner, tokenId, 0); - } - - /** - * @dev Approve `to` to lock operate on `tokenId` - * - * Emits a {LockApproval} event. - */ - function _lockApprove( - address owner, - address to, - uint256 tokenId - ) internal virtual { - _lockApprovals[tokenId] = to; - emit LockApproval(owner, to, tokenId); - } - - /** - * @dev Approve `operator` to lock operate on all of `owner` tokens - * - * Emits a {LockApprovalForAll} event. - */ - function _setLockApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "ERC5058: lock approve to caller"); - _lockOperatorApprovals[owner][operator] = approved; - emit LockApprovalForAll(owner, operator, approved); - } - - /** - * @dev Returns whether `spender` is allowed to lock `tokenId`. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function _isLockApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { - require(_exists(tokenId), "ERC5058: lock operator query for nonexistent token"); - address owner = ERC721.ownerOf(tokenId); - return (spender == owner || isLockApprovedForAll(owner, spender) || getLockApproved(tokenId) == spender); - } - - /** - * @dev See {ERC721-_beforeTokenTransfer}. - * - * Requirements: - * - * - the `tokenId` must not be locked. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._beforeTokenTransfer(from, to, tokenId); - - require(!isLocked(tokenId), "ERC5058: token transfer while locked"); - } - - /** - * @dev Hook that is called before any token lock/unlock. - * - * Calling conditions: - * - * - `owner` is non-zero. - * - When `expired` is zero, `tokenId` will be unlock for `from`. - * - When `expired` is non-zero, ``from``'s `tokenId` will be locked. - * - */ - function _beforeTokenLock( - address operator, - address owner, - uint256 tokenId, - uint256 expired - ) internal virtual {} - - /** - * @dev Hook that is called after any lock/unlock of tokens. - * - * Calling conditions: - * - * - `owner` is non-zero. - * - When `expired` is zero, `tokenId` will be unlock for `from`. - * - When `expired` is non-zero, ``from``'s `tokenId` will be locked. - * - */ - function _afterTokenLock( - address operator, - address owner, - uint256 tokenId, - uint256 expired - ) internal virtual {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return interfaceId == type(IERC5058).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5058/IERC5058.sol b/assets/eip-5058/IERC5058.sol deleted file mode 100644 index 4f8846eff2c412..00000000000000 --- a/assets/eip-5058/IERC5058.sol +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @dev ERC-721 Non-Fungible Token Standard, optional lockable extension - * ERC721 Token that can be locked for a certain period and cannot be transferred. - * This is designed for a non-escrow staking contract that comes later to lock a user's NFT - * while still letting them keep it in their wallet. - * This extension can ensure the security of user tokens during the staking period. - * If the nft lending protocol is compatible with this extension, the trouble caused by the NFT - * airdrop can be avoided, because the airdrop is still in the user's wallet - */ -interface IERC5058 is IERC721 { - /** - * @dev Emitted when `tokenId` token is locked by `operator` from `owner`. - */ - event Locked(address indexed operator, address indexed owner, uint256 indexed tokenId, uint256 expired); - - /** - * @dev Emitted when `tokenId` token is unlocked by `operator` from `owner`. - */ - event Unlocked(address indexed operator, address indexed owner, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables `approved` to lock the `tokenId` token. - */ - event LockApproval(address indexed owner, address indexed approved, uint256 indexed tokenId); - - /** - * @dev Emitted when `owner` enables or disables (`approved`) `operator` to lock all of its tokens. - */ - event LockApprovalForAll(address indexed owner, address indexed operator, bool approved); - - /** - * @dev Returns the locker who is locking the `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function lockerOf(uint256 tokenId) external view returns (address locker); - - /** - * @dev Lock `tokenId` token until the block number is greater than `expired` to be unlocked. - * - * Requirements: - * - * - `tokenId` token must be owned by `owner`. - * - `expired` must be greater than block.number - * - If the caller is not `from`, it must be approved to lock this token - * by either {lockApprove} or {setLockApprovalForAll}. - * - * Emits a {Locked} event. - */ - function lock(uint256 tokenId, uint256 expired) external; - - /** - * @dev Unlock `tokenId` token. - * - * Requirements: - * - * - `tokenId` token must be owned by `from`. - * - the caller must be the operator who locks the token by {lock} - * - * Emits a {Unlocked} event. - */ - function unlock(uint256 tokenId) external; - - /** - * @dev Gives permission to `to` to lock `tokenId` token. - * - * Requirements: - * - * - The caller must own the token or be an approved lock operator. - * - `tokenId` must exist. - * - * Emits an {LockApproval} event. - */ - function lockApprove(address to, uint256 tokenId) external; - - /** - * @dev Approve or remove `operator` as an lock operator for the caller. - * Operators can call {lock} for any token owned by the caller. - * - * Requirements: - * - * - The `operator` cannot be the caller. - * - * Emits an {LockApprovalForAll} event. - */ - function setLockApprovalForAll(address operator, bool approved) external; - - /** - * @dev Returns the account lock approved for `tokenId` token. - * - * Requirements: - * - * - `tokenId` must exist. - */ - function getLockApproved(uint256 tokenId) external view returns (address operator); - - /** - * @dev Returns if the `operator` is allowed to lock all of the assets of `owner`. - * - * See {setLockApprovalForAll} - */ - function isLockApprovedForAll(address owner, address operator) external view returns (bool); - - /** - * @dev Returns if the `tokenId` token is locked. - */ - function isLocked(uint256 tokenId) external view returns (bool); - - /** - * @dev Returns the `tokenId` token lock expired time. - */ - function lockExpiredTime(uint256 tokenId) external view returns (uint256); -} diff --git a/assets/eip-5058/extensions/ERC5058Bound.sol b/assets/eip-5058/extensions/ERC5058Bound.sol deleted file mode 100644 index e0eebb614a2929..00000000000000 --- a/assets/eip-5058/extensions/ERC5058Bound.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../factory/IERC5058Factory.sol"; -import "../factory/IERC721Bound.sol"; -import "../ERC5058.sol"; - -abstract contract ERC5058Bound is ERC5058 { - address public bound; - - function _setFactory(address _factory) internal { - bound = IERC5058Factory(_factory).boundOf(address(this)); - } - - function _setBoundBaseTokenURI(string memory uri) internal { - IERC721Bound(bound).setBaseTokenURI(uri); - } - - function _setBoundContractURI(string memory uri) internal { - IERC721Bound(bound).setContractURI(uri); - } - - function burnBound(uint256 tokenId) external { - IERC721Bound(bound).burn(tokenId); - } - - // NOTE: - // - // this will be called when `lock` or `unlock` - function _afterTokenLock( - address operator, - address from, - uint256 tokenId, - uint256 expired - ) internal virtual override { - super._afterTokenLock(operator, from, tokenId, expired); - - if (bound != address(0)) { - if (expired != 0) { - // lock mint - if (operator != address(0)) { - IERC721Bound(bound).safeMint(msg.sender, tokenId, ""); - } - } else { - // unlock - if (IERC721Bound(bound).exists(tokenId)) { - IERC721Bound(bound).burn(tokenId); - } - } - } - } -} diff --git a/assets/eip-5058/factory/ERC5058Factory.sol b/assets/eip-5058/factory/ERC5058Factory.sol deleted file mode 100644 index 3fda931920aa95..00000000000000 --- a/assets/eip-5058/factory/ERC5058Factory.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC721Bound.sol"; -import "./IERC5058Factory.sol"; - -contract ERC5058Factory is IERC5058Factory { - address[] private _allBounds; - - // Mapping from preimage to bound - mapping(address => address) private _bounds; - - function allBoundsLength() public view virtual override returns (uint256) { - return _allBounds.length; - } - - function boundByIndex(uint256 index) public view virtual override returns (address) { - require(index < _allBounds.length, "ERC5058Factory: index out of bounds"); - - return _allBounds[index]; - } - - function existBound(address preimage) public view virtual override returns (bool) { - return _bounds[preimage] != address(0); - } - - function boundOf(address preimage) public view virtual override returns (address) { - require(existBound(preimage), "ERC5058Factory: query for nonexistent bound"); - return _bounds[preimage]; - } - - function boundDeploy(address preimage) public virtual override returns (address) { - require(!existBound(preimage), "ERC5058Factory: bound nft is already deployed"); - - return _deploy(preimage, keccak256(abi.encode(preimage)), "Bound"); - } - - function _deploy( - address preimage, - bytes32 salt, - bytes memory prefix - ) internal returns (address) { - IERC721Metadata collection = IERC721Metadata(preimage); - bytes memory code = type(ERC721Bound).creationCode; - bytes memory bytecode = abi.encodePacked( - code, - abi.encode( - preimage, - abi.encodePacked(prefix, " ", collection.name()), - abi.encodePacked(prefix, collection.symbol()) - ) - ); - - address addr; - assembly { - addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) - } - - emit DeployedBound(preimage, addr); - - _bounds[preimage] = addr; - _allBounds.push(addr); - - return addr; - } -} diff --git a/assets/eip-5058/factory/ERC721Bound.sol b/assets/eip-5058/factory/ERC721Bound.sol deleted file mode 100644 index 28e9f17ee761bc..00000000000000 --- a/assets/eip-5058/factory/ERC721Bound.sol +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC721Bound.sol"; - -interface IPreimage { - /** - * @dev Returns if the `tokenId` token of preimage is locked. [MUST] - */ - function isLocked(uint256 tokenId) external view returns (bool); - - /** - * @dev Opensea-contract-level metadata. [OPTIONAL] - * Details: https://docs.opensea.io/docs/contract-level-metadata - */ - function contractURI() external view returns (string memory); -} - -/** - * @dev This implements an optional extension of {ERC5058} defined in the EIP. - * The bound token is exactly the same as the locked token metadata, the bound token can be transferred, - * but it is guaranteed that only one bound token and the original token can be traded in the market at - * the same time. When the original token lock expires, the bound token must be destroyed. - */ -contract ERC721Bound is ERC721Enumerable, IERC2981, IERC721Bound { - address private _preimage; - - string private _contractURI; - - string private _baseTokenURI; - - constructor( - address preimage_, - string memory name_, - string memory symbol_ - ) ERC721(name_, symbol_) { - _preimage = preimage_; - } - - /** - * @dev Throws if called by any account other than the preimage. - */ - modifier onlyPreimage() { - require(_preimage == msg.sender, "ERC721Bound: caller is not the preimage"); - _; - } - - function preimage() public view virtual override returns (address) { - return _preimage; - } - - /** - * @dev See {ERC721-_baseURI}. - */ - function _baseURI() internal view virtual override returns (string memory) { - return _baseTokenURI; - } - - /** - * @dev See {IERC721Metadata-tokenURI}. - */ - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - if (bytes(_baseTokenURI).length > 0) { - return super.tokenURI(tokenId); - } - - return IERC721Metadata(_preimage).tokenURI(tokenId); - } - - /** - * @dev See {IERC2981-royaltyInfo}. - */ - function royaltyInfo(uint256 tokenId, uint256 salePrice) public view virtual override returns (address, uint256) { - return IERC2981(_preimage).royaltyInfo(tokenId, salePrice); - } - - /** - * @dev See {IPreimage-contractURI}. - */ - function contractURI() public view returns (string memory) { - if (bytes(_contractURI).length > 0) { - return _contractURI; - } - - if (IERC165(_preimage).supportsInterface(IPreimage.contractURI.selector)) { - return IPreimage(_preimage).contractURI(); - } - - return ""; - } - - /** - * @dev Returns whether `tokenId` exists. - */ - function exists(uint256 tokenId) public view returns (bool) { - return _exists(tokenId); - } - - // @dev Sets the base token URI prefix. - function setBaseTokenURI(string memory baseTokenURI) public virtual override onlyPreimage { - _baseTokenURI = baseTokenURI; - } - - // @dev Sets the contract URI. - function setContractURI(string memory uri) public virtual override onlyPreimage { - _contractURI = uri; - } - - /** - * @dev Mints bound `tokenId` and transfers it to `to`. - * - * Requirements: - * - * - `tokenId` must not exist. - * - `to` cannot be the zero address. - * caller must be preimage contract. - * - * Emits a {Transfer} event. - */ - function safeMint( - address to, - uint256 tokenId, - bytes memory data - ) public virtual override onlyPreimage { - _safeMint(to, tokenId, data); - } - - /** - * @dev Destroys `tokenId`. - * The approval is cleared when the token is burned. - * - * Requirements: - * - * - `tokenId` must exist. - * caller must be preimage contract. - * - * Emits a {Transfer} event. - */ - function burn(uint256 tokenId) public virtual override onlyPreimage { - _burn(tokenId); - } - - /** - * @dev See {ERC721-_beforeTokenTransfer}. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._beforeTokenTransfer(from, to, tokenId); - - if (from == address(0)) { - require(IPreimage(_preimage).isLocked(tokenId), "ERC721Bound: token mint while preimage not locked"); - } - if (to == address(0)) { - require(!IPreimage(_preimage).isLocked(tokenId), "ERC721Bound: token burn while preimage locked"); - } - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(IERC165, ERC721Enumerable) - returns (bool) - { - return - interfaceId == type(IERC721Bound).interfaceId || - interfaceId == type(IERC2981).interfaceId || - interfaceId == IPreimage.contractURI.selector || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5058/factory/IERC5058Factory.sol b/assets/eip-5058/factory/IERC5058Factory.sol deleted file mode 100644 index 61ecb19b9af776..00000000000000 --- a/assets/eip-5058/factory/IERC5058Factory.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5058Factory { - event DeployedBound(address indexed preimage, address bound); - - function allBoundsLength() external view returns (uint256); - - function boundByIndex(uint256 index) external view returns (address); - - function existBound(address preimage) external view returns (bool); - - function boundOf(address preimage) external view returns (address); - - function boundDeploy(address preimage) external returns (address); -} diff --git a/assets/eip-5058/factory/IERC721Bound.sol b/assets/eip-5058/factory/IERC721Bound.sol deleted file mode 100644 index c8b86a9a00f375..00000000000000 --- a/assets/eip-5058/factory/IERC721Bound.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC721Bound is IERC721 { - function preimage() external view returns (address); - - function contractURI() external view returns (string memory); - - function exists(uint256 tokenId) external view returns (bool); - - function setBaseTokenURI(string memory _baseTokenURI) external; - - function setContractURI(string memory uri) external; - - function safeMint( - address to, - uint256 tokenId, - bytes memory data - ) external; - - function burn(uint256 tokenId) external; -} diff --git a/assets/eip-5058/mock/EIP5058Mock.sol b/assets/eip-5058/mock/EIP5058Mock.sol deleted file mode 100644 index 0d02a748eaec1a..00000000000000 --- a/assets/eip-5058/mock/EIP5058Mock.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5058.sol"; - -contract EIP5058Mock is ERC721Enumerable, ERC5058 { - constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - - function exists(uint256 tokenId) public view returns (bool) { - return _exists(tokenId); - } - - function lockMint( - address to, - uint256 tokenId, - uint256 expired - ) external { - _safeLockMint(to, tokenId, expired, ""); - } - - function mint(address to, uint256 tokenId) external { - _mint(to, tokenId); - } - - function burn(uint256 tokenId) external { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not owner nor approved"); - - _burn(tokenId); - } - - function _burn(uint256 tokenId) internal virtual override(ERC721, ERC5058) { - super._burn(tokenId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override(ERC721Enumerable, ERC5058) { - super._beforeTokenTransfer(from, to, tokenId); - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721Enumerable, ERC5058) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5058/test/test.ts b/assets/eip-5058/test/test.ts deleted file mode 100644 index 4b728647987364..00000000000000 --- a/assets/eip-5058/test/test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import "@nomiclabs/hardhat-ethers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { EIP5058Mock } from "typechain-types"; - -describe("ERC5058 contract", function() { - let owner: SignerWithAddress; - let alice: SignerWithAddress; - let EIP5058: EIP5058Mock; - - beforeEach(async () => { - [owner, alice] = await ethers.getSigners(); - - const ERC5058Factory = await ethers.getContractFactory("EIP5058Mock"); - - EIP5058 = await ERC5058Factory.deploy("ERC5058Mock", "ERC5058"); - }); - - it("Deployment should assign the total supply of tokens to the owner", async function() { - const ownerBalance = await EIP5058.balanceOf(owner.address); - expect(await EIP5058.totalSupply()).to.equal(ownerBalance); - }); - - it("lockMint works", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(alice.address, NFTId, block + 2); - - expect(await EIP5058.lockExpiredTime(NFTId)).eq(block + 2); - expect(await EIP5058.isLocked(NFTId)).eq(true); - expect(await EIP5058.lockerOf(NFTId)).eq(owner.address); - }); - - it("Can not transfer when token is locked", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 3); - - expect(await EIP5058.isLocked(NFTId)).eq(true); - // can not transfer when token is locked - await expect(EIP5058.transferFrom(owner.address, alice.address, NFTId)).to.be.revertedWith( - "ERC5058: token transfer while locked", - ); - - // can transfer when token is unlocked - await ethers.provider.send("evm_mine", []); - - expect(await EIP5058.isLocked(NFTId)).eq(false); - await EIP5058.transferFrom(owner.address, alice.address, NFTId); - expect(await EIP5058.ownerOf(NFTId)).eq(alice.address); - }); - - it("isLocked works", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 2); - - // isLocked works - expect(await EIP5058.isLocked(NFTId)).eq(true); - await ethers.provider.send("evm_mine", []); - expect(await EIP5058.isLocked(NFTId)).eq(false); - }); - - it("lock works", async function() { - const NFTId = 0; - let block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 3); - - expect(await EIP5058.isLocked(NFTId)).eq(true); - await expect(EIP5058.lock(NFTId, block + 5)).to.be.revertedWith( - "ERC5058: token is locked", - ); - - await ethers.provider.send("evm_mine", []); - expect(await EIP5058.isLocked(NFTId)).eq(false); - await EIP5058.lock(NFTId, block + 5); - }); - - it("unlock works with lockMint", async function() { - const NFTId = 0; - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lockMint(owner.address, NFTId, block + 3); - - // unlock works - expect(await EIP5058.isLocked(NFTId)).eq(true); - expect(await EIP5058.lockerOf(NFTId)).eq(owner.address); - await EIP5058.unlock(NFTId); - expect(await EIP5058.isLocked(NFTId)).eq(false); - }); - - it("unlock works", async function() { - const NFTId = 0; - - await EIP5058.mint(owner.address, NFTId); - await expect(EIP5058.unlock(NFTId)).to.be.revertedWith( - "ERC5058: locker query for non-locked token", - ); - const block = await ethers.provider.getBlockNumber(); - await EIP5058.lock(NFTId, block + 3); - expect(await EIP5058.isLocked(NFTId)).eq(true); - await EIP5058.unlock(NFTId); - expect(await EIP5058.isLocked(NFTId)).eq(false); - }); - - it("lockApprove works", async function() { - const NFTId = 0; - await EIP5058.mint(alice.address, NFTId); - let block = await ethers.provider.getBlockNumber(); - - await expect(EIP5058.lock(NFTId, block + 4)).to.be.revertedWith( - "ERC5058: lock caller is not owner nor approved", - ); - await EIP5058.connect(alice).lockApprove(owner.address, NFTId); - expect(await EIP5058.getLockApproved(NFTId)).eq(owner.address); - - await EIP5058.lock(NFTId, block + 8); - expect(await EIP5058.isLocked(NFTId)).eq(true); - - await expect(EIP5058.lockApprove(alice.address, NFTId)).to.be.revertedWith( - "ERC5058: token is locked", - ); - }); - - it("setLockApproveForAll works", async function() { - const NFTId = 0; - - await EIP5058.mint(alice.address, NFTId); - const block = await ethers.provider.getBlockNumber(); - await expect(EIP5058.lock(NFTId, block + 2)).to.be.revertedWith( - "ERC5058: lock caller is not owner nor approved", - ); - - await EIP5058.connect(alice).setLockApprovalForAll(owner.address, true); - expect(await EIP5058.isLockApprovedForAll(alice.address, owner.address)).eq(true); - - await EIP5058.lock(NFTId, block + 6); - - await EIP5058.connect(alice).setLockApprovalForAll(owner.address, false); - expect(await EIP5058.isLockApprovedForAll(alice.address, owner.address)).eq(false); - }); -}); diff --git a/assets/eip-5139/AUTHORS.md b/assets/eip-5139/AUTHORS.md deleted file mode 100644 index 24cf6a25e6fd7c..00000000000000 --- a/assets/eip-5139/AUTHORS.md +++ /dev/null @@ -1,37 +0,0 @@ -SemVer Authors -============== - -The following people have modified the Semantic Versioning 2.0.0 specification: - - - Tom Preston-Werner - - Phil Haack - - Haacked - - isaacs - - Thijs Schreijer - - jeffhandley - - Alexandr Tovmach - - Adam Ralph - - Eddie Garmon - - Jeff Handley - - Krzysztof Piasecki - - Doug Beck - - Gert de Pagter - - Guillermo Calvo - - Iulian Onofrei - - Ivan Bessarabov - - Jo Liss - - Johanan Liebermann - - Joseph Donahue - - Konstantin - - Kristian Glass - - Mark Amery - - OGINO Masanori - - Oguz Bilgic - - Slipp Douglas - - Thomas Schraitle - - Tim Vergenz - - Todd Reed - - Tristram Oaten - - Wincent Colaiuta - - alexandrtovmach - - wolf99 diff --git a/assets/eip-5139/semver.md b/assets/eip-5139/semver.md deleted file mode 100644 index 95cf203cefaa20..00000000000000 --- a/assets/eip-5139/semver.md +++ /dev/null @@ -1,373 +0,0 @@ -Semantic Versioning 2.0.0 -============================== - -Summary -------- - -Given a version number MAJOR.MINOR.PATCH, increment the: - -1. MAJOR version when you make incompatible API changes, -1. MINOR version when you add functionality in a backwards compatible - manner, and -1. PATCH version when you make backwards compatible bug fixes. - -Additional labels for pre-release and build metadata are available as extensions -to the MAJOR.MINOR.PATCH format. - -Introduction ------------- - -In the world of software management there exists a dreaded place called -"dependency hell." The bigger your system grows and the more packages you -integrate into your software, the more likely you are to find yourself, one -day, in this pit of despair. - -In systems with many dependencies, releasing new package versions can quickly -become a nightmare. If the dependency specifications are too tight, you are in -danger of version lock (the inability to upgrade a package without having to -release new versions of every dependent package). If dependencies are -specified too loosely, you will inevitably be bitten by version promiscuity -(assuming compatibility with more future versions than is reasonable). -Dependency hell is where you are when version lock and/or version promiscuity -prevent you from easily and safely moving your project forward. - -As a solution to this problem, we propose a simple set of rules and -requirements that dictate how version numbers are assigned and incremented. -These rules are based on but not necessarily limited to pre-existing -widespread common practices in use in both closed and open-source software. -For this system to work, you first need to declare a public API. This may -consist of documentation or be enforced by the code itself. Regardless, it is -important that this API be clear and precise. Once you identify your public -API, you communicate changes to it with specific increments to your version -number. Consider a version format of X.Y.Z (Major.Minor.Patch). Bug fixes not -affecting the API increment the patch version, backwards compatible API -additions/changes increment the minor version, and backwards incompatible API -changes increment the major version. - -We call this system "Semantic Versioning." Under this scheme, version numbers -and the way they change convey meaning about the underlying code and what has -been modified from one version to the next. - -Semantic Versioning Specification (SemVer) ------------------------------------------- - -The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", -"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be -interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119). - -1. Software using Semantic Versioning MUST declare a public API. This API -could be declared in the code itself or exist strictly in documentation. -However it is done, it SHOULD be precise and comprehensive. - -1. A normal version number MUST take the form X.Y.Z where X, Y, and Z are -non-negative integers, and MUST NOT contain leading zeroes. X is the -major version, Y is the minor version, and Z is the patch version. -Each element MUST increase numerically. For instance: 1.9.0 -> 1.10.0 -> 1.11.0. - -1. Once a versioned package has been released, the contents of that version -MUST NOT be modified. Any modifications MUST be released as a new version. - -1. Major version zero (0.y.z) is for initial development. Anything MAY change -at any time. The public API SHOULD NOT be considered stable. - -1. Version 1.0.0 defines the public API. The way in which the version number -is incremented after this release is dependent on this public API and how it -changes. - -1. Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards -compatible bug fixes are introduced. A bug fix is defined as an internal -change that fixes incorrect behavior. - -1. Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards -compatible functionality is introduced to the public API. It MUST be -incremented if any public API functionality is marked as deprecated. It MAY be -incremented if substantial new functionality or improvements are introduced -within the private code. It MAY include patch level changes. Patch version -MUST be reset to 0 when minor version is incremented. - -1. Major version X (X.y.z | X > 0) MUST be incremented if any backwards -incompatible changes are introduced to the public API. It MAY also include minor -and patch level changes. Patch and minor versions MUST be reset to 0 when major -version is incremented. - -1. A pre-release version MAY be denoted by appending a hyphen and a -series of dot separated identifiers immediately following the patch -version. Identifiers MUST comprise only ASCII alphanumerics and hyphens -[0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric identifiers MUST -NOT include leading zeroes. Pre-release versions have a lower -precedence than the associated normal version. A pre-release version -indicates that the version is unstable and might not satisfy the -intended compatibility requirements as denoted by its associated -normal version. Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, -1.0.0-x.7.z.92, 1.0.0-x-y-z.--. - -1. Build metadata MAY be denoted by appending a plus sign and a series of dot -separated identifiers immediately following the patch or pre-release version. -Identifiers MUST comprise only ASCII alphanumerics and hyphens [0-9A-Za-z-]. -Identifiers MUST NOT be empty. Build metadata MUST be ignored when determining -version precedence. Thus two versions that differ only in the build metadata, -have the same precedence. Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, -1.0.0-beta+exp.sha.5114f85, 1.0.0+21AF26D3----117B344092BD. - -1. Precedence refers to how versions are compared to each other when ordered. - - 1. Precedence MUST be calculated by separating the version into major, - minor, patch and pre-release identifiers in that order (Build metadata - does not figure into precedence). - - 1. Precedence is determined by the first difference when comparing each of - these identifiers from left to right as follows: Major, minor, and patch - versions are always compared numerically. - - Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1. - - 1. When major, minor, and patch are equal, a pre-release version has lower - precedence than a normal version: - - Example: 1.0.0-alpha < 1.0.0. - - 1. Precedence for two pre-release versions with the same major, minor, and - patch version MUST be determined by comparing each dot separated identifier - from left to right until a difference is found as follows: - - 1. Identifiers consisting of only digits are compared numerically. - - 1. Identifiers with letters or hyphens are compared lexically in ASCII - sort order. - - 1. Numeric identifiers always have lower precedence than non-numeric - identifiers. - - 1. A larger set of pre-release fields has a higher precedence than a - smaller set, if all of the preceding identifiers are equal. - - Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < - 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0. - -Backus–Naur Form Grammar for Valid SemVer Versions --------------------------------------------------- -``` - ::= - | "-" - | "+" - | "-" "+" - - ::= "." "." - - ::= - - ::= - - ::= - - ::= - - ::= - | "." - - ::= - - ::= - | "." - - ::= - | - - ::= - | - - ::= - | - | - | - - ::= "0" - | - | - - ::= - | - - ::= - | - - ::= - | "-" - - ::= - | - - ::= "0" - | - - ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" - - ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" - | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" - | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" - | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" - | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" - | "y" | "z" -``` - -Why Use Semantic Versioning? ----------------------------- - -This is not a new or revolutionary idea. In fact, you probably do something -close to this already. The problem is that "close" isn't good enough. Without -compliance to some sort of formal specification, version numbers are -essentially useless for dependency management. By giving a name and clear -definition to the above ideas, it becomes easy to communicate your intentions -to the users of your software. Once these intentions are clear, flexible (but -not too flexible) dependency specifications can finally be made. - -A simple example will demonstrate how Semantic Versioning can make dependency -hell a thing of the past. Consider a library called "Firetruck." It requires a -Semantically Versioned package named "Ladder." At the time that Firetruck is -created, Ladder is at version 3.1.0. Since Firetruck uses some functionality -that was first introduced in 3.1.0, you can safely specify the Ladder -dependency as greater than or equal to 3.1.0 but less than 4.0.0. Now, when -Ladder version 3.1.1 and 3.2.0 become available, you can release them to your -package management system and know that they will be compatible with existing -dependent software. - -As a responsible developer you will, of course, want to verify that any -package upgrades function as advertised. The real world is a messy place; -there's nothing we can do about that but be vigilant. What you can do is let -Semantic Versioning provide you with a sane way to release and upgrade -packages without having to roll new versions of dependent packages, saving you -time and hassle. - -If all of this sounds desirable, all you need to do to start using Semantic -Versioning is to declare that you are doing so and then follow the rules. Link -to this website from your README so others know the rules and can benefit from -them. - -FAQ ---- - -### How should I deal with revisions in the 0.y.z initial development phase? - -The simplest thing to do is start your initial development release at 0.1.0 -and then increment the minor version for each subsequent release. - -### How do I know when to release 1.0.0? - -If your software is being used in production, it should probably already be -1.0.0. If you have a stable API on which users have come to depend, you should -be 1.0.0. If you're worrying a lot about backwards compatibility, you should -probably already be 1.0.0. - -### Doesn't this discourage rapid development and fast iteration? - -Major version zero is all about rapid development. If you're changing the API -every day you should either still be in version 0.y.z or on a separate -development branch working on the next major version. - -### If even the tiniest backwards incompatible changes to the public API require a major version bump, won't I end up at version 42.0.0 very rapidly? - -This is a question of responsible development and foresight. Incompatible -changes should not be introduced lightly to software that has a lot of -dependent code. The cost that must be incurred to upgrade can be significant. -Having to bump major versions to release incompatible changes means you'll -think through the impact of your changes, and evaluate the cost/benefit ratio -involved. - -### Documenting the entire public API is too much work! - -It is your responsibility as a professional developer to properly document -software that is intended for use by others. Managing software complexity is a -hugely important part of keeping a project efficient, and that's hard to do if -nobody knows how to use your software, or what methods are safe to call. In -the long run, Semantic Versioning, and the insistence on a well defined public -API can keep everyone and everything running smoothly. - -### What do I do if I accidentally release a backwards incompatible change as a minor version? - -As soon as you realize that you've broken the Semantic Versioning spec, fix -the problem and release a new minor version that corrects the problem and -restores backwards compatibility. Even under this circumstance, it is -unacceptable to modify versioned releases. If it's appropriate, -document the offending version and inform your users of the problem so that -they are aware of the offending version. - -### What should I do if I update my own dependencies without changing the public API? - -That would be considered compatible since it does not affect the public API. -Software that explicitly depends on the same dependencies as your package -should have their own dependency specifications and the author will notice any -conflicts. Determining whether the change is a patch level or minor level -modification depends on whether you updated your dependencies in order to fix -a bug or introduce new functionality. We would usually expect additional code -for the latter instance, in which case it's obviously a minor level increment. - -### What if I inadvertently alter the public API in a way that is not compliant with the version number change (i.e. the code incorrectly introduces a major breaking change in a patch release)? - -Use your best judgment. If you have a huge audience that will be drastically -impacted by changing the behavior back to what the public API intended, then -it may be best to perform a major version release, even though the fix could -strictly be considered a patch release. Remember, Semantic Versioning is all -about conveying meaning by how the version number changes. If these changes -are important to your users, use the version number to inform them. - -### How should I handle deprecating functionality? - -Deprecating existing functionality is a normal part of software development and -is often required to make forward progress. When you deprecate part of your -public API, you should do two things: (1) update your documentation to let -users know about the change, (2) issue a new minor release with the deprecation -in place. Before you completely remove the functionality in a new major release -there should be at least one minor release that contains the deprecation so -that users can smoothly transition to the new API. - -### Does SemVer have a size limit on the version string? - -No, but use good judgment. A 255 character version string is probably overkill, -for example. Also, specific systems may impose their own limits on the size of -the string. - -### Is "v1.2.3" a semantic version? - -No, "v1.2.3" is not a semantic version. However, prefixing a semantic version -with a "v" is a common way (in English) to indicate it is a version number. -Abbreviating "version" as "v" is often seen with version control. Example: -`git tag v1.2.3 -m "Release version 1.2.3"`, in which case "v1.2.3" is a tag -name and the semantic version is "1.2.3". - -### Is there a suggested regular expression (RegEx) to check a SemVer string? - -There are two. One with named groups for those systems that support them -(PCRE [Perl Compatible Regular Expressions, i.e. Perl, PHP and R], Python -and Go). - -See: - -``` -^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ -``` - -And one with numbered capture groups instead (so cg1 = major, cg2 = minor, -cg3 = patch, cg4 = prerelease and cg5 = buildmetadata) that is compatible -with ECMA Script (JavaScript), PCRE (Perl Compatible Regular Expressions, -i.e. Perl, PHP and R), Python and Go. - -See: - -``` -^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ -``` - -About ------ - -The Semantic Versioning specification was originally authored by [Tom -Preston-Werner](https://tom.preston-werner.com), inventor of Gravatar and -cofounder of GitHub. - -If you'd like to leave feedback, please [open an issue on -GitHub](https://github.com/semver/semver/issues). - -License -------- - -[Creative Commons ― CC BY 3.0](https://creativecommons.org/licenses/by/3.0/) diff --git a/assets/eip-5169/contract/ExampleContract.sol b/assets/eip-5169/contract/ExampleContract.sol deleted file mode 100644 index 298c1c2538ac0a..00000000000000 --- a/assets/eip-5169/contract/ExampleContract.sol +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -library AddressUtil { - /** - * @dev Returns true if `account` is a contract. - * - * [IMPORTANT] - * ==== - * It is unsafe to assume that an address for which this function returns - * false is an externally-owned account (EOA) and not a contract. - * - * Among others, `isContract` will return false for the following - * types of addresses: - * - * - an externally-owned account - * - a contract in construction - * - an address where a contract will be created - * - an address where a contract lived, but was destroyed - * ==== - */ - 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; - } -} - -abstract contract MultiOwnable is Ownable { - mapping(address => bool) private _admins; - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - constructor() Ownable() { - _admins[_msgSender()] = true; - } - - function addAdmin(address newAdmin) public onlyOwner { - _admins[newAdmin] = true; - } - - function revokeAdmin(address currentAdmin) public onlyOwner { - delete _admins[currentAdmin]; - } - - function isAdmin(address sender) public view returns(bool) { - return _admins[sender]; - } - - /** - * @dev Throws if called by a non-admin - */ - modifier onlyAdmins() { - require(_admins[_msgSender()] == true, "Ownable: caller is not an admin"); - _; - } -} - -interface IERC5169 { - /// @dev This event emits when the scriptURI is updated, - /// so wallets implementing this interface can update a cached script - event ScriptUpdate(string newScriptURI); - - /// @notice Get the scriptURI for the contract - /// @return The scriptURI - function scriptURI() external view returns(string memory); - - /// @notice Update the scriptURI - /// emits event ScriptUpdate(string memory newScriptURI); - function updateScriptURI(string memory newScriptURI) external; -} - -contract STLDoor is ERC721, MultiOwnable, IERC5169 { - using AddressUtil for address; - using Strings for uint256; - using Counters for Counters.Counter; - - Counters.Counter public _tokenIdCounter; - Counters.Counter public _topTokenIdCounter; - Counters.Counter public _stlTokenIdCounter; - - uint256 private constant _topTokenId = 10000; - - string private _scriptURI; - - constructor() ERC721("STL HQ Door", "OFFICE") { - _tokenIdCounter.increment(); - _scriptURI = "ipfs://QmXXLFBeSjXAwAhbo1344wJSjLgoUrfUK9LE57oVubaRRp"; - mintUsingSequentialTokenId(); - } - - function contractURI() public pure returns (string memory) { - return "ipfs://QmUgdLvPvjuHGfMsuK1H2jFpg5r1QNc8JeWyXyRwKP8pTf"; - } - - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - require(_exists(tokenId), "tokenURI: URI query for nonexistent token"); - if (tokenId < _topTokenId) { - return "ipfs://QmW948aN4Tjh4eLkAAo8os1AcM2FJjA46qtaEfFAnyNYzY"; - } else if (tokenId < _topTokenId * 2) { - return "ipfs://QmR31f2AUokC5QyLXzDYUjy5tVibkjbW4voVuMBZfrNVU8"; - } else { - return "ipfs://QmdaSTaF6WXpYWiL5ck7csmTy5EWHzYVGykJZN7TR95dSS"; - } - } - - function scriptURI() public view override returns (string memory) { - return _scriptURI; - } - - function updateScriptURI(string memory newScriptURI) public override onlyAdmins { - _scriptURI = newScriptURI; - emit ScriptUpdate(newScriptURI); - } - - function mintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) { - tokenId = _tokenIdCounter.current(); - require(tokenId < _topTokenId, "Hit upper mint limit"); - _mint(msg.sender, tokenId); - _tokenIdCounter.increment(); - } - - function topMintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) { - tokenId = _topTokenIdCounter.current() + _topTokenId; - require(tokenId < _topTokenId*2, "Hit upper mint limit"); - _mint(msg.sender, tokenId); - _topTokenIdCounter.increment(); - } - - function stlMintUsingSequentialTokenId() public onlyAdmins returns (uint256 tokenId) { - tokenId = _stlTokenIdCounter.current() + _topTokenId*2; - _mint(msg.sender, tokenId); - _stlTokenIdCounter.increment(); - } - - function burnToken(uint256 tokenId) public onlyAdmins { - require(_exists(tokenId), "burn: nonexistent token"); - _burn(tokenId); - } - - // Only allow owners to transfer tokens - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) public override onlyAdmins { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); - _safeTransfer(from, to, tokenId, _data); - } - - function transferFrom( - address from, - address to, - uint256 tokenId - ) public override onlyAdmins { - //solhint-disable-next-line max-line-length - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); - - _transfer(from, to, tokenId); - } - - function selfDestruct() public payable onlyOwner { - selfdestruct(payable(owner())); - } -} diff --git a/assets/eip-5169/tokenscript/ExampleScript.xml b/assets/eip-5169/tokenscript/ExampleScript.xml deleted file mode 100644 index 5fb54cc96efc83..00000000000000 --- a/assets/eip-5169/tokenscript/ExampleScript.xml +++ /dev/null @@ -1,421 +0,0 @@ - - - - - STL Office Token - STL Office Tokens - - - Boleto de admisión - Boleto de admisiónes - - - 入場券 - 入場券 - - - - 0xB424e50674a38e83c7Eca39945fe5B45B4cd3705 - - - - - - - - - - - Unlock - 开锁 - Abrir - - - - - - - - - - Lock - 关锁 - Cerrar - - - - - - - - - - Mint Ape 1 - - - - - - - - - - - - - - - Mint Ape 2 - - - - - - - - - - - - - - - Mint STL Token - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/eip-5173/Arithmetic_Sequence_FR_Payout_Distribution.png b/assets/eip-5173/Arithmetic_Sequence_FR_Payout_Distribution.png deleted file mode 100644 index 5ebce00dc81adf..00000000000000 Binary files a/assets/eip-5173/Arithmetic_Sequence_FR_Payout_Distribution.png and /dev/null differ diff --git a/assets/eip-5173/Implementation/InFR.sol b/assets/eip-5173/Implementation/InFR.sol deleted file mode 100644 index a9327fe4879798..00000000000000 --- a/assets/eip-5173/Implementation/InFR.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/* - * - * @dev Interface for the Future Rewards Token Standard. - * - * A standardized way to receive future rewards for non-fungible tokens (NFTs.) - * - */ -interface InFR is IERC165 { - - event FRClaimed(address indexed account, uint256 indexed amount); - - event FRDistributed(uint256 indexed tokenId, uint256 indexed soldPrice, uint256 indexed allocatedFR); - - function list(uint256 tokenId, uint256 salePrice) external; - - function unlist(uint256 tokenId) external; - - function buy(uint256 tokenId) payable external; - - function releaseFR(address payable account) external; - - function retrieveFRInfo(uint256 tokenId) external returns(uint8, uint256, uint256, uint256, uint256, address[] memory); - - function retrieveAllottedFR(address account) external returns(uint256); - - function retrieveListInfo(uint256 tokenId) external returns(uint256, address, bool); - -} diff --git a/assets/eip-5173/Implementation/nFR.sol b/assets/eip-5173/Implementation/nFR.sol deleted file mode 100644 index 9364af8e77a2ff..00000000000000 --- a/assets/eip-5173/Implementation/nFR.sol +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./InFR.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/math/Math.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@prb/math/contracts/PRBMathUD60x18.sol"; -import "@prb/math/contracts/PRBMathSD59x18.sol"; - -import "hardhat/console.sol"; - -abstract contract nFR is InFR, ERC721 { - - using Address for address; - - using PRBMathUD60x18 for uint256; - using PRBMathSD59x18 for int256; - - struct FRInfo { - uint8 numGenerations; // Number of generations corresponding to that Token ID - uint256 percentOfProfit; // Percent of profit allocated for FR, scaled by 1e18 - uint256 successiveRatio; // The common ratio of successive in the geometric sequence, used for distribution calculation - uint256 lastSoldPrice; // Last sale price in ETH mantissa - uint256 ownerAmount; // Amount of owners the Token ID has seen - bool isValid; // Updated by contract and signifies if an FR Info for a given Token ID is valid - } - - struct ListInfo { - uint256 salePrice; // ETH mantissa of the listed selling price - address lister; // Owner/Lister of the Token - bool isListed; // Boolean indicating whether the Token is listed or not - } - - FRInfo private _defaultFRInfo; - - // Takes Token ID and returns corresponding FR Info - mapping(uint256 => FRInfo) private _tokenFRInfo; - - // Takes Token ID and returns the addresses currently in the FR cycle - mapping(uint256 => address[]) private _addressesInFR; - - // Takes Address and returns amount of ether available to address from FR payments - mapping(address => uint256) private _allottedFR; - - // Takes Token ID and returns corresponding ListInfo - mapping(uint256 => ListInfo) private _tokenListInfo; - - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return interfaceId == type(InFR).interfaceId || super.supportsInterface(interfaceId); - } - - function retrieveFRInfo(uint256 tokenId) public view virtual override returns(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio, uint256 lastSoldPrice, uint256 ownerAmount, address[] memory addressesInFR) { - return (_tokenFRInfo[tokenId].numGenerations, _tokenFRInfo[tokenId].percentOfProfit, _tokenFRInfo[tokenId].successiveRatio, _tokenFRInfo[tokenId].lastSoldPrice, _tokenFRInfo[tokenId].ownerAmount, _addressesInFR[tokenId]); - } - - function retrieveListInfo(uint256 tokenId) public view virtual override returns(uint256, address, bool) { - return (_tokenListInfo[tokenId].salePrice, _tokenListInfo[tokenId].lister, _tokenListInfo[tokenId].isListed); - } - - function retrieveAllottedFR(address account) public view virtual override returns(uint256) { - return _allottedFR[account]; - } - - function _transferFrom(address from, address to, uint256 tokenId, uint256 soldPrice) internal virtual { - ERC721._transfer(from, to, tokenId); - require(_checkERC721Received(from, to, tokenId, ""), "ERC721: transfer to non ERC721Receiver implementer"); - - if (soldPrice <= _tokenFRInfo[tokenId].lastSoldPrice) { // NFT sold for a loss, meaning no FR distribution, but we still shift generations, and update price. We return ALL of the received ETH to the msg.sender as no FR chunk was needed. - _tokenFRInfo[tokenId].lastSoldPrice = soldPrice; - _tokenFRInfo[tokenId].ownerAmount++; - _shiftGenerations(to, tokenId); - (bool sent, ) = payable(_tokenListInfo[tokenId].lister).call{value: soldPrice}(""); - require(sent, "ERC5173: Failed to send msg.value to lister"); - } else { - _distributeFR(tokenId, soldPrice); - _tokenFRInfo[tokenId].lastSoldPrice = soldPrice; - _tokenFRInfo[tokenId].ownerAmount++; - _shiftGenerations(to, tokenId); - } - - delete _tokenListInfo[tokenId]; - } - - function list(uint256 tokenId, uint256 salePrice) public virtual override { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC5173: list caller is not owner nor approved"); - - _tokenListInfo[tokenId] = ListInfo(salePrice, _msgSender(), true); - } - - function unlist(uint256 tokenId) public virtual override { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC5173: unlist caller is not owner nor approved"); - - delete _tokenListInfo[tokenId]; - } - - function buy(uint256 tokenId) public virtual override payable { - require(_tokenListInfo[tokenId].isListed == true, "Token is not listed"); - require(_tokenListInfo[tokenId].salePrice == msg.value, "salePrice and msg.value mismatch"); - - _transferFrom(_tokenListInfo[tokenId].lister, _msgSender(), tokenId, _tokenListInfo[tokenId].salePrice); - } - - function _transfer(address from, address to, uint256 tokenId) internal virtual override { - super._transfer(from, to, tokenId); - - if (_tokenListInfo[tokenId].isListed == true) { - delete _tokenListInfo[tokenId]; - } - - _tokenFRInfo[tokenId].lastSoldPrice = 0; - _tokenFRInfo[tokenId].ownerAmount++; - _shiftGenerations(to, tokenId); - } - - function _mint(address to, uint256 tokenId) internal virtual override { - require(_defaultFRInfo.isValid, "No Default FR Info has been set"); - - super._mint(to, tokenId); - - _tokenFRInfo[tokenId] = FRInfo(_defaultFRInfo.numGenerations, _defaultFRInfo.percentOfProfit, _defaultFRInfo.successiveRatio, 0, 1, true); - - _addressesInFR[tokenId].push(to); - } - - function _burn(uint256 tokenId) internal virtual override { - super._burn(tokenId); - - delete _tokenFRInfo[tokenId]; - delete _addressesInFR[tokenId]; - delete _tokenListInfo[tokenId]; - } - - function _mint(address to, uint256 tokenId, uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) internal virtual { - require(numGenerations > 0 && percentOfProfit > 0 && percentOfProfit <= 1e18 && successiveRatio > 0, "Invalid Data Passed"); - - ERC721._mint(to, tokenId); - require(_checkERC721Received(address(0), to, tokenId, ""), "ERC721: transfer to non ERC721Receiver implementer"); - - _tokenFRInfo[tokenId] = FRInfo(numGenerations, percentOfProfit, successiveRatio, 0, 1, true); - - _addressesInFR[tokenId].push(to); - } - - function _distributeFR(uint256 tokenId, uint256 soldPrice) internal virtual { - uint256 profit = soldPrice - _tokenFRInfo[tokenId].lastSoldPrice; - uint256[] memory FR = _calculateFR(profit, _tokenFRInfo[tokenId].percentOfProfit, _tokenFRInfo[tokenId].successiveRatio, _tokenFRInfo[tokenId].ownerAmount, _tokenFRInfo[tokenId].numGenerations); - - for (uint owner = 0; owner < FR.length; owner++) { - _allottedFR[_addressesInFR[tokenId][owner]] += FR[owner]; - } - - uint256 allocatedFR = 0; - - for (uint reward = 0; reward < FR.length; reward++) { - allocatedFR += FR[reward]; - } - - (bool sent, ) = payable(_tokenListInfo[tokenId].lister).call{value: soldPrice - allocatedFR}(""); - require(sent, "Failed to send ETH after FR distribution to lister"); - - emit FRDistributed(tokenId, soldPrice, allocatedFR); - } - - function _shiftGenerations(address to, uint256 tokenId) internal virtual { - if (_addressesInFR[tokenId].length < _tokenFRInfo[tokenId].numGenerations) { // We just want to push to the array - _addressesInFR[tokenId].push(to); - } else { // We want to remove the first element in the array and then push to the end of the array - for (uint i = 0; i < _addressesInFR[tokenId].length-1; i++) { - _addressesInFR[tokenId][i] = _addressesInFR[tokenId][i+1]; - } - - _addressesInFR[tokenId].pop(); - - _addressesInFR[tokenId].push(to); - } - } - - function _setDefaultFRInfo(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) internal virtual { - require(numGenerations > 0 && percentOfProfit > 0 && percentOfProfit <= 1e18 && successiveRatio > 0, "Invalid Data Passed"); - - _defaultFRInfo.numGenerations = numGenerations; - _defaultFRInfo.percentOfProfit = percentOfProfit; - _defaultFRInfo.successiveRatio = successiveRatio; - _defaultFRInfo.isValid = true; - } - - function releaseFR(address payable account) public virtual override { - require(_allottedFR[account] > 0, "No FR Payment due"); - - uint256 FRAmount = _allottedFR[account]; - - _allottedFR[account] = 0; - - (bool sent, ) = account.call{value: FRAmount}(""); - require(sent, "Failed to release FR"); - - emit FRClaimed(account, FRAmount); - } - - function _calculateFR(uint256 totalProfit, uint256 buyerReward, uint256 successiveRatio, uint256 ownerAmount, uint256 windowSize) pure internal virtual returns(uint256[] memory) { - uint256 n = Math.min(ownerAmount, windowSize); - uint256[] memory FR = new uint256[](n); - - for (uint256 i = 1; i < n + 1; i++) { - uint256 pi = 0; - - if (successiveRatio != 1e18) { - int256 v1 = 1e18 - int256(successiveRatio).powu(n); - int256 v2 = int256(successiveRatio).powu(i - 1); - int256 v3 = int256(totalProfit).mul(int256(buyerReward)); - int256 v4 = v3.mul(1e18 - int256(successiveRatio)); - pi = uint256(v4 * v2 / v1); - } else { - pi = totalProfit.mul(buyerReward).div(n); - } - - FR[n - i] = pi; - } - - return FR; - } - - function _checkERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) private returns (bool) { - if (to.isContract()) { - try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert("ERC721: transfer to non ERC721Receiver implementer"); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - -} \ No newline at end of file diff --git a/assets/eip-5173/Implementation/nFRImplementation.sol b/assets/eip-5173/Implementation/nFRImplementation.sol deleted file mode 100644 index b9f5125fcafe83..00000000000000 --- a/assets/eip-5173/Implementation/nFRImplementation.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Contract based on [https://docs.openzeppelin.com/contracts/3.x/erc721](https://docs.openzeppelin.com/contracts/3.x/erc721) -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "./nFR.sol"; - -contract MyNFT is ERC721URIStorage, Ownable, nFR { - using Counters for Counters.Counter; - Counters.Counter private _tokenIds; - - constructor() ERC721("MyNFT", "NFT") {} - - function mintNFT(address recipient, uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio, string memory tokenURI) - public onlyOwner - returns (uint256) - { - _tokenIds.increment(); - - uint256 newItemId = _tokenIds.current(); - _mint(recipient, newItemId, numGenerations, percentOfProfit, successiveRatio); - _setTokenURI(newItemId, tokenURI); - - return newItemId; - } - - function mintERC721(address recipient, string memory tokenURI) public onlyOwner { - _tokenIds.increment(); - - uint256 newItemId = _tokenIds.current(); - _mint(recipient, newItemId); - _setTokenURI(newItemId, tokenURI); - } - - function setDefaultFRInfo(uint8 numGenerations, uint256 percentOfProfit, uint256 successiveRatio) public onlyOwner { - _setDefaultFRInfo(numGenerations, percentOfProfit, successiveRatio); - } - - function burnNFT(uint256 tokenId) public onlyOwner { - _burn(tokenId); - } - - function _burn(uint256 tokenId) internal override(nFR, ERC721URIStorage) { - super._burn(tokenId); - } - - function _transfer(address from, address to, uint256 tokenId) internal override(ERC721, nFR) { - super._transfer(from, to, tokenId); - } - - function _mint(address to, uint256 tokenId) internal virtual override(ERC721, nFR) { - super._mint(to, tokenId); - } - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, nFR) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function tokenURI(uint256 tokenId) - public - view - override(ERC721, ERC721URIStorage) - returns (string memory) - { - return super.tokenURI(tokenId); - } -} diff --git a/assets/eip-5173/Losing_owners.jpeg b/assets/eip-5173/Losing_owners.jpeg deleted file mode 100644 index 26878c175b3023..00000000000000 Binary files a/assets/eip-5173/Losing_owners.jpeg and /dev/null differ diff --git a/assets/eip-5173/Same_owner_using_different_wallets.jpeg b/assets/eip-5173/Same_owner_using_different_wallets.jpeg deleted file mode 100644 index 7cca2da108a395..00000000000000 Binary files a/assets/eip-5173/Same_owner_using_different_wallets.jpeg and /dev/null differ diff --git a/assets/eip-5173/Total_FR_Payout_Distribution-flat.png b/assets/eip-5173/Total_FR_Payout_Distribution-flat.png deleted file mode 100644 index 8756b130633c7b..00000000000000 Binary files a/assets/eip-5173/Total_FR_Payout_Distribution-flat.png and /dev/null differ diff --git a/assets/eip-5173/Total_FR_Payout_Distribution-geo.png b/assets/eip-5173/Total_FR_Payout_Distribution-geo.png deleted file mode 100644 index e0b618b818c2ae..00000000000000 Binary files a/assets/eip-5173/Total_FR_Payout_Distribution-geo.png and /dev/null differ diff --git a/assets/eip-5173/animate-1920x1080-1750-frames.gif b/assets/eip-5173/animate-1920x1080-1750-frames.gif deleted file mode 100644 index b6a7a883277e45..00000000000000 Binary files a/assets/eip-5173/animate-1920x1080-1750-frames.gif and /dev/null differ diff --git a/assets/eip-5173/nFR_Standard_Outline.jpeg b/assets/eip-5173/nFR_Standard_Outline.jpeg deleted file mode 100644 index 1d0fc2a84549ee..00000000000000 Binary files a/assets/eip-5173/nFR_Standard_Outline.jpeg and /dev/null differ diff --git a/assets/eip-5173/nFR_distribution_formula.png b/assets/eip-5173/nFR_distribution_formula.png deleted file mode 100644 index b4f42acd4aad62..00000000000000 Binary files a/assets/eip-5173/nFR_distribution_formula.png and /dev/null differ diff --git a/assets/eip-5216/ERC1155ApprovalByAmount.sol b/assets/eip-5216/ERC1155ApprovalByAmount.sol deleted file mode 100644 index ee94e35458c13f..00000000000000 --- a/assets/eip-5216/ERC1155ApprovalByAmount.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.15; - -import "IERC1155.sol"; -import "ERC1155.sol"; - -/** - * @title ERC-1155 Approval By Amount Extension - * Note: the ERC-165 identifier for this interface is 0x1be07d74 - */ -interface IERC1155ApprovalByAmount is IERC1155 { - - /** - * @notice Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to - * `id` and with an amount: `amount`. - */ - event ApprovalByAmount(address indexed account, address indexed operator, uint256 id, uint256 amount); - - /** - * @notice Grants permission to `operator` to transfer the caller's tokens, according to `id`, and an amount: `amount`. - * Emits an {ApprovalByAmount} event. - * - * Requirements: - * - `operator` cannot be the caller. - */ - function approve(address operator, uint256 id, uint256 amount) external; - - /** - * @notice Returns the amount allocated to `operator` approved to transfer `account`'s tokens, according to `id`. - */ - function allowance(address account, address operator, uint256 id) external view returns (uint256); - -} - -/** - * @dev Extension of {ERC1155} that allows you to approve your tokens by amount and id. - */ -abstract contract ERC1155ApprovalByAmount is ERC1155, IERC1155ApprovalByAmount { - - // Mapping from account to operator approvals by id and amount. - mapping(address => mapping(address => mapping(uint256 => uint256))) internal _allowances; - - /** - * @dev See {IERC1155ApprovalByAmount} - */ - function approve(address operator, uint256 id, uint256 amount) public virtual { - _approve(msg.sender, operator, id, amount); - } - - /** - * @dev See {IERC1155ApprovalByAmount} - */ - function allowance(address account, address operator, uint256 id) public view virtual returns (uint256) { - return _allowances[account][operator][id]; - } - - /** - * @dev safeTransferFrom implementation for using ApprovalByAmount extension - */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public override(IERC1155, ERC1155) { - require( - from == msg.sender || isApprovedForAll(from, msg.sender) || allowance(from, msg.sender, id) >= amount, - "ERC1155: caller is not owner nor approved nor approved for amount" - ); - unchecked { - _allowances[from][msg.sender][id] -= amount; - } - _safeTransferFrom(from, to, id, amount, data); - } - - /** - * @dev safeBatchTransferFrom implementation for using ApprovalByAmount extension - */ - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public virtual override(IERC1155, ERC1155) { - require( - from == msg.sender || isApprovedForAll(from, msg.sender) || _checkApprovalForBatch(from, msg.sender, ids, amounts), - "ERC1155: transfer caller is not owner nor approved nor approved for some amount" - ); - _safeBatchTransferFrom(from, to, ids, amounts, data); - } - - /** - * @dev Checks if all ids and amounts are permissioned for `to`. - * - * Requirements: - * - `ids` and `amounts` length should be equal. - */ - function _checkApprovalForBatch( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts - ) internal virtual returns (bool) { - uint256 idsLength = ids.length; - uint256 amountsLength = amounts.length; - - require(idsLength == amountsLength, "ERC1155ApprovalByAmount: ids and amounts length mismatch"); - for (uint256 i = 0; i < idsLength;) { - require(allowance(from, to, ids[i]) >= amounts[i], "ERC1155ApprovalByAmount: operator is not approved for that id or amount"); - unchecked { - _allowances[from][to][ids[i]] -= amounts[i]; - ++i; - } - } - return true; - } - - /** - * @dev Approve `operator` to operate on all of `owner` tokens by id and amount. - * Emits a {ApprovalByAmount} event. - */ - function _approve( - address owner, - address operator, - uint256 id, - uint256 amount - ) internal virtual { - require(owner != operator, "ERC1155ApprovalByAmount: setting approval status for self"); - _allowances[owner][operator][id] = amount; - emit ApprovalByAmount(owner, operator, id, amount); - } -} - -contract ExampleToken is ERC1155ApprovalByAmount { - constructor() ERC1155("") {} - - function mint(address account, uint256 id, uint256 amount, bytes memory data) public { - _mint(account, id, amount, data); - } - - function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) public { - _mintBatch(to, ids, amounts, data); - } -} diff --git a/assets/eip-5218/contracts/README.md b/assets/eip-5218/contracts/README.md deleted file mode 100644 index 705973cacd525b..00000000000000 --- a/assets/eip-5218/contracts/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# EIP-5218 Reference Implementations - -This is the source code for a reference implementation of EIP-5218. - -## Build and Test - -The repo expects a [Foundry](https://github.com/foundry-rs/foundry/tree/master/forge) build system, optionally using visual studio code for editing. You can run the test suite with: - -```bash -forge test -vvvvv -``` - diff --git a/assets/eip-5218/contracts/foundry.toml b/assets/eip-5218/contracts/foundry.toml deleted file mode 100644 index d7dd144bc71e5b..00000000000000 --- a/assets/eip-5218/contracts/foundry.toml +++ /dev/null @@ -1,6 +0,0 @@ -[default] -src = 'src' -out = 'out' -libs = ['lib'] - -# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/assets/eip-5218/contracts/remappings.txt b/assets/eip-5218/contracts/remappings.txt deleted file mode 100644 index 1e11e4da15120a..00000000000000 --- a/assets/eip-5218/contracts/remappings.txt +++ /dev/null @@ -1,3 +0,0 @@ -forge-std/=lib/forge-std/src/ -ds-test/=lib/forge-std/lib/ds-test/src/ -@openzeppelin/=lib/openzeppelin-contracts/ \ No newline at end of file diff --git a/assets/eip-5218/contracts/src/IERC5218.sol b/assets/eip-5218/contracts/src/IERC5218.sol deleted file mode 100644 index 334f23556d3919..00000000000000 --- a/assets/eip-5218/contracts/src/IERC5218.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/// @title EIP-5218: NFT Rights Management -interface IERC5218 is IERC721 { - - /// @dev This emits when a new license is created by any mechanism. - event CreateLicense(uint256 _licenseId, uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string _uri, address _revoker); - - /// @dev This emits when a license is revoked. Note that under some - /// license terms, the sublicenses may be `implicitly` revoked following the - /// revocation of some ancestral license. In that case, your smart contract - /// may only emit this event once for the ancestral license, and the revocation - /// of all its sublicenses can be implied without consuming additional gas. - event RevokeLicense(uint256 _licenseId); - - /// @dev This emits when the a license is transferred to a new holder. The - /// root license of an NFT should be transferred with the NFT in an ERC721 - /// `transfer` function call. - event TransferLicense(uint256 _licenseId, address _licenseHolder); - - /// @notice Check if a license is active. - /// @dev A non-existing or revoked license is inactive and this function must - /// return `false` upon it. Under some license terms, a license may become - /// inactive because some ancestral license has been revoked. In that case, - /// this function should return `false`. - /// @param _licenseId The identifier for the queried license - /// @return Whether the queried license is active - function isLicenseActive(uint256 _licenseId) external view returns (bool); - - /// @notice Retrieve the token identifier a license was issued upon. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The token identifier the queried license was issued upon - function getLicenseTokenId(uint256 _licenseId) external view returns (uint256); - - /// @notice Retrieve the parent license identifier of a license. - /// @dev Throws unless the license is active. If a license doesn't have a - /// parent license, return a special identifier not referring to any license - /// (such as 0). - /// @param _licenseId The identifier for the queried license - /// @return The parent license identifier of the queried license - function getParentLicenseId(uint256 _licenseId) external view returns (uint256); - - /// @notice Retrieve the holder of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The holder address of the queried license - function getLicenseHolder(uint256 _licenseId) external view returns (address); - - /// @notice Retrieve the URI of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The URI of the queried license - function getLicenseURI(uint256 _licenseId) external view returns (string memory); - - /// @notice Retrieve the revoker address of a license. - /// @dev Throws unless the license is active. - /// @param _licenseId The identifier for the queried license - /// @return The revoker address of the queried license - function getLicenseRevoker(uint256 _licenseId) external view returns (address); - - /// @notice Retrieve the root license identifier of an NFT. - /// @dev Throws unless the queried NFT exists. If the NFT doesn't have a root - /// license tethered to it, return a special identifier not referring to any - /// license (such as 0). - /// @param _tokenId The identifier for the queried NFT - /// @return The root license identifier of the queried NFT - function getLicenseIdByTokenId(uint256 _tokenId) external view returns (uint256); - - /// @notice Create a new license. - /// @dev Throws unless the NFT `_tokenId` exists. Throws unless the parent - /// license `_parentLicenseId` is active, or `_parentLicenseId` is a special - /// identifier not referring to any license (such as 0) and the NFT - /// `_tokenId` doesn't have a root license tethered to it. Throws unless the - /// message sender is eligible to create the license, i.e., either the - /// license to be created is a root license and `msg.sender` is the NFT owner, - /// or the license to be created is a sublicense and `msg.sender` is the holder - /// of the parent license. - /// @param _tokenId The identifier for the NFT the license is issued upon - /// @param _parentLicenseId The identifier for the parent license - /// @param _licenseHolder The address of the license holder - /// @param _uri The URI of the license terms - /// @param _revoker The revoker address - /// @return The identifier of the created license - function createLicense(uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string memory _uri, address _revoker) external returns (uint256); - - /// @notice Revoke a license. - /// @dev Throws unless the license is active and the message sender is the - /// eligible revoker. This function should be used for revoking both root - /// licenses and sublicenses. Note that if a root license is revoked, the - /// NFT should be transferred back to its creator. - /// @param _licenseId The identifier for the queried license - function revokeLicense(uint256 _licenseId) external; - - /// @notice Transfer a sublicense. - /// @dev Throws unless the sublicense is active and `msg.sender` is the license - /// holder. Note that the root license of an NFT should be tethered to and - /// transferred with the NFT. Whenever an NFT is transferred by calling the - /// ERC721 `transfer` function, the holder of the root license should be - /// changed to the new NFT owner. - /// @param _licenseId The identifier for the queried license - /// @param _licenseHolder The new license holder - function transferSublicense(uint256 _licenseId, address _licenseHolder) external; -} - diff --git a/assets/eip-5218/contracts/src/RightsManagement.sol b/assets/eip-5218/contracts/src/RightsManagement.sol deleted file mode 100644 index 179b1cdaa2129e..00000000000000 --- a/assets/eip-5218/contracts/src/RightsManagement.sol +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC5218.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; - - -contract RightsManagement is IERC5218, ERC721URIStorage, Ownable { - struct License { - bool active; // whether the current license is active - uint256 tokenId; - uint256 parentLicenseId; - address licenseHolder; - string uri; - address revoker; - } - mapping(uint256 => License) private _licenses; - mapping(uint256 => uint256) private _licenseIds; - - using Counters for Counters.Counter; - Counters.Counter private _tokenCounter; - Counters.Counter private _licenseCounter; - - constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) { - return - interfaceId == type(IERC5218).interfaceId || - super.supportsInterface(interfaceId); - } - - function isLicenseActive(uint256 licenseId) public view virtual override(IERC5218) returns (bool) { - if (licenseId == 0) return false; - while (licenseId != 0) { - if (!_licenses[licenseId].active) return false; - licenseId = _licenses[licenseId].parentLicenseId; - } - return true; - } - - modifier isActiveLicense(uint256 licenseId) { - require(isLicenseActive(licenseId), "The queried license is not active"); - _; - } - - function getLicenseTokenId(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (uint256) { - return _licenses[licenseId].tokenId; - } - - function getParentLicenseId(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (uint256) { - return _licenses[licenseId].parentLicenseId; - } - - function getLicenseHolder(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (address) { - return _licenses[licenseId].licenseHolder; - } - - function getLicenseURI(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (string memory) { - return _licenses[licenseId].uri; - } - - function getLicenseRevoker(uint256 licenseId) public view virtual override(IERC5218) isActiveLicense(licenseId) returns (address) { - return _licenses[licenseId].revoker; - } - - function getLicenseIdByTokenId(uint256 tokenId) public view virtual override(IERC5218) returns (uint256) { - require (_exists(tokenId), "The token doesn't exist"); - return _licenseIds[tokenId]; - } - - function safeMint( - address recipient, - string memory tokenURI, - string memory licenseURI, - address licenseRevoker - ) - public virtual onlyOwner - returns (uint256) - { - return safeMint(recipient, tokenURI, licenseURI, licenseRevoker, ""); - } - - function safeMint( - address recipient, - string memory tokenURI, - string memory licenseURI, - address licenseRevoker, - bytes memory _data - ) - public virtual onlyOwner - returns (uint256) - { - _tokenCounter.increment(); - uint256 newItemId = _tokenCounter.current(); - - _safeMint(recipient, newItemId, _data); - _setTokenURI(newItemId, tokenURI); - _createLicense(newItemId, 0, recipient, licenseURI, licenseRevoker); - - return newItemId; - } - - function safeIssue( - address recipient, - uint256 tokenId, - string memory licenseURI, - address licenseRevoker - ) - public virtual - returns (uint256) - { - return safeIssue(recipient, tokenId, licenseURI, licenseRevoker, ""); - } - - function safeIssue( - address recipient, - uint256 tokenId, - string memory licenseURI, - address licenseRevoker, - bytes memory data - ) - public virtual - returns (uint256) - { - require(_licenseIds[tokenId] == 0, "The token has an active license"); - require(ownerOf(tokenId) == owner(), "The creator doesn't own the NFT"); - - uint256 licenseId = createLicense(tokenId, 0, owner(), licenseURI, licenseRevoker); - safeTransferFrom(owner(), recipient, tokenId, data); - - return licenseId; - } - - function createLicense( - uint256 tokenId, - uint256 parentLicenseId, - address licenseHolder, - string memory uri, - address revoker - ) - public virtual override(IERC5218) - returns (uint256) - { - require(_exists(tokenId), "The NFT doesn't exists"); - require(parentLicenseId == 0 || isLicenseActive(parentLicenseId), "The parent license is not active"); - require(parentLicenseId != 0 || getLicenseIdByTokenId(tokenId) == 0, "The NFT already has a root license"); - require( - (parentLicenseId == 0 && msg.sender == owner()) || - (parentLicenseId != 0 && msg.sender == _licenses[parentLicenseId].licenseHolder), - "Sender is not eligible to grant a new license" - ); - - return _createLicense(tokenId, parentLicenseId, licenseHolder, uri, revoker); - } - - function revokeLicense(uint256 licenseId) public virtual override(IERC5218) { - require(isLicenseActive(licenseId), "The license is not active"); - require(msg.sender == _licenses[licenseId].revoker, "The msg sender is not an eligible revoker"); - - if (_licenses[licenseId].parentLicenseId == 0) { - _transfer(ownerOf(_licenses[licenseId].tokenId), owner(), _licenses[licenseId].tokenId); - } - - _revokeLicense(licenseId); - } - - function transferSublicense(uint256 licenseId, address licenseHolder) public virtual override(IERC5218) { - require(isLicenseActive(licenseId), "The license is not active"); - require(_licenses[licenseId].parentLicenseId != 0, "The license is a root license"); - require(msg.sender == _licenses[licenseId].licenseHolder, "The msg sender is not the license holder"); - - _updateLicenseHolder(licenseId, licenseHolder); - } - - function _transfer(address from, address to, uint256 tokenId) internal virtual override(ERC721) { - require(_licenseIds[tokenId] != 0 && isLicenseActive(_licenseIds[tokenId]), "The token has no active license tethered to it"); - require(_licenses[_licenseIds[tokenId]].licenseHolder == ownerOf(tokenId), "The license holder and the NFT owner are inconsistent"); - - super._transfer(from, to, tokenId); - _updateLicenseHolder(_licenseIds[tokenId], to); - } - - function _updateLicenseHolder(uint256 licenseId, address licenseHolder) internal virtual { - _licenses[licenseId].licenseHolder = licenseHolder; - emit TransferLicense(licenseId, licenseHolder); - } - - function _createLicense( - uint256 tokenId, - uint256 parentLicenseId, - address licenseHolder, - string memory uri, - address revoker - ) - internal virtual - returns (uint256) - { - _licenseCounter.increment(); - uint256 licenseId = _licenseCounter.current(); - - _licenses[licenseId].active = true; - _licenses[licenseId].tokenId = tokenId; - _licenses[licenseId].parentLicenseId = parentLicenseId; // tyler: it seems like a security problem that children are able to overwrite their parents - _licenses[licenseId].licenseHolder = licenseHolder; - _licenses[licenseId].uri = uri; - _licenses[licenseId].revoker = revoker; - - if (parentLicenseId == 0) { - _licenseIds[tokenId] = licenseId; - } - - emit CreateLicense(licenseId, tokenId, parentLicenseId, licenseHolder, uri, revoker); - return licenseId; - } - - function _revokeLicense(uint256 licenseId) internal virtual { - if (_licenses[licenseId].parentLicenseId == 0) { - _licenseIds[_licenses[licenseId].tokenId] = 0; - } - - delete _licenses[licenseId]; - - emit RevokeLicense(licenseId); - } -} - - - diff --git a/assets/eip-5218/contracts/test/Contract.t.sol b/assets/eip-5218/contracts/test/Contract.t.sol deleted file mode 100644 index 84a2e6c4ce1ea9..00000000000000 --- a/assets/eip-5218/contracts/test/Contract.t.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import "../src/RightsManagement.sol"; - -contract ContractTest is Test { - - event CreateLicense( - uint256 licenseId, - uint256 tokenId, - uint256 parentLicenseId, - address licenseHolder, - string uri, - address revoker - ); - event RevokeLicense(uint256 licenseId); - event TransferLicense(uint256 licenseId, address licenseHolder); - - //Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - RightsManagement rm; - - address add1 = address(0xadd1); - address add2 = address(0xadd2); - address add3 = address(0xadd3); - string tokenURI = "tokenURI"; - string licenseURI = "licenseURI"; - string sublicenseURI = "sublicenseURI"; - - function setUp() public { - vm.deal(add1, 12 ether); - vm.deal(add2, 12 ether); - vm.deal(add3, 12 ether); - - rm = new RightsManagement("MyNFT", "NFT"); - } - - function testMint() public { - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(1, 1, 0, add1, licenseURI, add3); // the expected log you expect to see emitted - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - address tokenOwner = rm.ownerOf(tokenId); - assertEq(add1, tokenOwner, "tokenOwner should match"); - uint256 licenseId = rm.getLicenseIdByTokenId(tokenId); - assertEq(rm.isLicenseActive(0), false, "License 0 should be inactive"); - assertEq(rm.isLicenseActive(licenseId), true, "License should be active"); - assertEq(rm.getLicenseURI(licenseId), licenseURI, "License should match"); - assertEq(rm.getLicenseHolder(licenseId), add1, "LicenseHolder should match"); - assertEq(rm.getLicenseTokenId(licenseId), 1, "TokenId should match"); - assertEq(rm.getParentLicenseId(licenseId), 0, "Parent License Id should match"); - assertEq(rm.getLicenseRevoker(licenseId), add3, "License revoker should match"); - } - - function testCreateLicense() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - uint parentLicenseId = rm.getLicenseIdByTokenId(tokenId); - - vm.startPrank(add1); - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 1, add2, sublicenseURI, add3); // the expected log you expect to see emitted - uint licenseId = rm.createLicense(tokenId, parentLicenseId, add2, sublicenseURI, add3); - vm.stopPrank(); - - vm.expectRevert("Sender is not eligible to grant a new license"); - licenseId = rm.createLicense(tokenId, parentLicenseId, add2, sublicenseURI, add3); - } - - function testRevokeLicense() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - uint parentLicenseId = rm.getLicenseIdByTokenId(tokenId); - - vm.startPrank(add1); - uint licenseId = rm.createLicense(tokenId, parentLicenseId, add2, sublicenseURI, add3); - vm.stopPrank(); - - vm.startPrank(add2); - licenseId = rm.createLicense(tokenId, licenseId, add3, sublicenseURI, add3); - vm.stopPrank(); - - vm.startPrank(add3); - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit RevokeLicense(2); // the expected log you expect to see emitted - rm.revokeLicense(2); - vm.stopPrank(); - - vm.startPrank(add3); - vm.expectRevert("The license is not active"); - rm.revokeLicense(3); - vm.stopPrank(); - - - vm.startPrank(add1); - vm.expectRevert("The msg sender is not an eligible revoker"); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - assertEq(rm.ownerOf(1), address(this), "The token should be returned to creator after revoking its license"); - - assertEq(rm.getLicenseIdByTokenId(1), 0, "The token should not have an active license"); - } - - function testTransfer() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.expectRevert("The token has no active license tethered to it"); - rm.safeTransferFrom(address(this), add1, 1); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 0, address(this), licenseURI, add3); // the expected log you expect to see emitted - rm.createLicense(tokenId, 0, address(this), licenseURI, add3); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit TransferLicense(2, add2); // the expected log you expect to see emitted - rm.safeTransferFrom(address(this), add2, 1); - assertEq(rm.getLicenseIdByTokenId(1), 2, "License Id linked to tokenId"); - assertEq(rm.getLicenseHolder(2), add2, "License holder updated"); - } - - function testIssue() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 0, address(this), licenseURI, add3); // the expected log you expect to see emitted - emit TransferLicense(2, add2); // the expected log you expect to see emitted - rm.safeIssue(add2, tokenId, licenseURI, add3); - assertEq(rm.getLicenseIdByTokenId(1), 2, "License Id linked to tokenId"); - assertEq(rm.getLicenseHolder(2), add2, "License holder updated"); - } - - function testTransferSublicense() public { - uint tokenId = rm.safeMint(add1, tokenURI, licenseURI, add3); - - vm.startPrank(add3); - rm.revokeLicense(1); - vm.stopPrank(); - - vm.expectRevert("The license is not active"); - rm.transferSublicense(1, add2); - - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit CreateLicense(2, 1, 0, add1, licenseURI, add3); // the expected log you expect to see emitted - rm.createLicense(tokenId, 0, add1, licenseURI, add3); - - vm.expectRevert("The license is a root license"); - rm.transferSublicense(2, add2); - - vm.startPrank(add1); - rm.createLicense(tokenId, 2, add2, licenseURI, add3); - vm.stopPrank(); - - vm.expectRevert("The msg sender is not the license holder"); - rm.transferSublicense(3, add1); - - vm.startPrank(add2); - vm.expectEmit(true,true,true,true); // put this two lines before you actually call the function - emit TransferLicense(3, add1); // the expected log you expect to see emitted - rm.transferSublicense(3, add1); - vm.stopPrank(); - } -} diff --git a/assets/eip-5218/ic3license/ic3license.pdf b/assets/eip-5218/ic3license/ic3license.pdf deleted file mode 100644 index 351a73fe5d79a4..00000000000000 Binary files a/assets/eip-5218/ic3license/ic3license.pdf and /dev/null differ diff --git a/assets/eip-5218/ic3license/ic3license.tex b/assets/eip-5218/ic3license/ic3license.tex deleted file mode 100644 index 2c665ddd75f6bc..00000000000000 --- a/assets/eip-5218/ic3license/ic3license.tex +++ /dev/null @@ -1,355 +0,0 @@ -\documentclass{article} - -\usepackage[hidelinks]{hyperref} -\usepackage{libertine} -\usepackage{authblk} - -\title{The \iccclicense\\} -\author{The Institute for Cryptocurrencies and Contracts (IC3) \and The Coalition of Automated Legal Applications (COALA)} -\date{Version 1.0\\\today} - -\usepackage{xspace} - -\newcommand{\eiplicense}{EIP-5218\xspace} -\newcommand{\iccclicense}{Token-Bound NFT License\xspace} - -\newcommand{\keyword}[1]{\textbf{#1}\xspace} - -\newcommand{\publiclicense}{\keyword{Public-License}} -\newcommand{\nopubliclicense}{\keyword{No-Public-License}} - -\newcommand{\commercial}{\keyword{Commercial}} -\newcommand{\noncommercial}{\keyword{Non-Commercial}} - -\newcommand{\noderivative}{\keyword{No-Derivatives}} -\newcommand{\derivative}{\keyword{Derivatives}} -\newcommand{\derivativetracking}{\keyword{Derivatives-NFT}} -\newcommand{\sharealike}{\keyword{Derivatives-NFT-Share-Alike}} - -\newcommand{\ledger}{\keyword{Ledger-Authoritative}} -\newcommand{\legal}{\keyword{Legal-Authoritative}} - -\newcommand{\personal}{\keyword{Personal}} -\newcommand{\conditional}{\keyword{Conditional}} - - -\usepackage{semantic-markup} - -\newcommand{\sect}[1]{\vspace{12pt}\noindent{\strong{#1}}} -\newcommand{\subsect}[1]{\vspace{12pt}\noindent{\em{#1}}} - - -\usepackage{xcolor} - -\definecolor{light-gray}{gray}{0.9} - -\renewcommand{\code}[1]{\colorbox{light-gray}{\texttt{#1}}} - -\newcommand{\iflicenseoption}[2]{[\colorbox{light-gray}{If #1:} #2]} -\newcommand{\ifnotlicenseoption}[2]{[\colorbox{light-gray}{Unless #1:} #2]} - -\begin{document} - -\maketitle - -\tableofcontents - -\section{Introduction} - -The \iccclicense is a copyright license specifically designed for use with NFTs. It links a creative work to an NFT so that when the NFT is transferred, so is the license. It is intended to be compatible with the NFT licensing standard defined in \eiplicense. - -The \iccclicense has four optional features that a licensor can use or not when choosing a license: -\begin{itemize} -\item The license can be \commercial (C) or \noncommercial (NC). A \commercial license allows the user to make money from merchandise and other uses of the work; a \noncommercial license does not. -\item The license can be \derivative (D), \derivativetracking (DT), \sharealike (DTSA), or \noderivative (ND). A \derivative license allows the user to make derivative works and adaptations (like remixes and videos) without restriction. A \derivativetracking license allows derivative works but requires them to be registered and tracked as NFTs themselves. A \sharealike license is like \derivativetracking but also requires that the derivative works be relicensed under the same license. And a \noderivative license prohibits derivative works entirely. (These options are ordered from least restrictive to most restrictive.) -\item The license can be \publiclicense (PL) or \nopubliclicense (NPL). A \publiclicense license, in addition to the specific rights granted to the user, grants to the public a broad copyright license to reproduce the work (but not to make derivative works). A \nopubliclicense license grants only the specific rights to the user. -\item The license can be \ledger (Ledger) or \legal (Legal). A \ledger license means that the current state of the blockchain ledger is always authoritative for who owns the NFT and has rights under the license, even if the NFT is stolen or transferred by mistake. A \legal license gives the courts flexibility to correct ownership of the license in clear cases of theft and fraud. -\end{itemize} -All four options are independent, so that they can be set for a choice of thirty-two total licenses, e.g. NC-D-NPL-Ledger means the license version that allows only Non-Commercial uses, allows Derivative works, includes No Public License, and makes the Ledger authoritative for the state of the license. Once an \iccclicense has been used on an NFT, that license stays with it. The licensor should not expect to be able to choose a different license after the initial release. - -The simplest combination of license options is C-D-NPL-Ledger, which provides a straightforward license that gives the current owner of the NFT (as defined by the blockchain) full rights over the associated work. This is the default \iccclicense; the other license options can be thought of as variations on it. - -The rest of this document describes the design choices of the \iccclicense. Appendix \ref{sec:human} provides a short human-readable summary of the license terms. Appendix \ref{sec:text} contains the actual text of the license family. - - -\section{Design Goals} - - -The \iccclicense has been specifically designed to work with blockchain-based NFTs, e.g. with smart contracts that implement the standard defined in \href{https://eips.ethereum.org/EIPS/eip-721}{EIP-721}. But it is not restricted to blockchain-based NFTs. The license uses the technology-neutral term ``Ledger'' to refer to the system that records information about NFTs and their owners. This could be a present-day blockchain, or another chain or system that could be created in the future. The license can be used with any underlying technology as long as it: -\begin{enumerate} -\item Supports NFTs based on persistent unique identifiers, -\item Associates those NFTs with specific owners, -\item Allows the owner of an NFT to control it with a cryptographic key, and -\item Allows an NFT to be linked to a creative work. -\end{enumerate} -This system can be widely distributed (like a public blockchain), moderately distributed (like a private blockchain), or fully centralized (like a database with a single administrator). Use of the \iccclicense for other kinds of licensing is strongly discouraged. - -The \iccclicense has also been specifically designed to work with NFTs that implement the interface defined in \eiplicense. This is a general smart-contract interface that provides modular support for common features in copyright licensing, including transfer, sublicensing, and revocation. The \iccclicense can also be used with NFTs that implement the generic NFT interface defined in EIP-721, or with other NFTs, but the \eiplicense interface is recommended to reduce ambiguity about when there has been a transfer, sublicense, or revocation. - - -\section{Taking Effect} - -The \iccclicense is structured as a license, rather than a contract. The licensor unilaterally grants a copyright license to the current owner of the NFT. The owner does not need to do anything to accept the license; they receive it automatically. If the owner transfers the NFT to someone else, the license transfers with it. The old owner is no longer licensed; the new owner receives a license on exactly the same terms. This too is automatic, the new owner does not need to do anything to accept the license. - -Standard techniques used by lawyers to create binding terms of use for websites do not work for NFTs on blockchains. Consider the clickthrough agreements used by websites: when you create an account, you must check a box indicating that you agree to the website's terms of use. This works because you have taken an action (checking the box) that is clearly and specifically linked to the legal terms and nothing else. There is no question about whether you meant to agree when you checked the box, because there is no other reason to check the box. - -But a blockchain does not have a checkbox or even a website. Someone who receives an EIP-721 NFT need not have done anything at all. They may never have visited the NFT sponsor's website, or even know that there is a website. An NFT could migrate from one resale platform to another; someone who has agreed to one platform's terms might not have agreed to the other's. This means that any promises by the \emph{licensee} in an NFT license contract are illusory. The licensor has no guarantee that a downstream NFT owner has actually agreed to the license terms. Thus, the \iccclicense does not give the licensee any duties, so there is no need for them to agree to anything. - -On the other hand, the \iccclicense does attempt to ensure that the \emph{licensor} has clearly indicated their intent to be bound by the license terms. It does so by saying that the license becomes effective when the licensor ``Invokes'' it through a ``Licensing Process,'' i.e., uses a technical process to connect an NFT with the \iccclicense. This definition includes (but is not limited to) the \eiplicense interface. - -The essential idea of \eiplicense is to make it explicit when a licensor intends to create a copyright license. This signal is clearest when the licensor takes an act that (a) says that it creates a license, and (b) does not do anything else. Signing a licensing contract on paper meets this test, because the paper says that it creates a license and the only reason to sign it is to create the license. \eiplicense is intended to be a smart-contract version of that piece of paper. - -The \iccclicense can be used with any smart contract or other technical process that meets the definition of a Licensing Process. It is not intended to be used on a website or in other off-chain settings where on-chain transactions do not link out to the license. It is not recommended to use the \iccclicense as part of a larger multi-step architecture unless there is some individual step in the system that meets the definition of a Licensing Process in which the licensor clearly indicates their intent to create a license. - - -\section{License Terms} - -We have tried in \iccclicense to capture the most common and important use cases in light of the NFT community's responses to previous licenses. But our goal has been simplicity rather than perfection. We hope and expect that over time, others will build on and remix the ideas in the license to terms for more advanced use cases. - -The \iccclicense applies to ``Licensed Material'' that is ``Linked'' to an NFT. The definition of Licensed Material is deliberately broad: it can include highly creative works like images or videos, but it also includes ``other material'' to include datasets, software, or other works that are not primarily artistic. The definition of Linked is also broad: it can be by hyperlink, by description, by IPFS CID, by hash value, by an on-chain reference, etc. What is important is that the NFT must be connected to specific licensed material in a way that cannot change over time. We do not attempt to solve the (very difficult) problems of creating a technical standard or a license for an NFT whose contents can change over time. - -The core license grant in the \iccclicense is that the NFT owner can ``Use'' the licensed material, i.e. exercise all of the usual rights under copyright to make copies of the work and share them with the public. This is an unlimited grant: it covers all media, digital and analog, on-chain and off-chain. - -The license grant is non-exclusive. It is not currently possible to guarantee that only one NFT has been made of a work, or that there are no other off-chain uses of the work. - -Drawing on the success of the Creative Commons license suite, the \iccclicense can be customized in four ways: -\begin{enumerate} -\item To be either \commercial or \noncommercial. -\item To allow \derivative works, to allow them but require that they be \derivativetracking on chain, to allow them but require that they be relicensed under \sharealike terms, or to specify that \noderivative works are allowed at all. -\item To grant a \publiclicense to members of the public besides the NFT owner, or to grant \nopubliclicense. -\item To track ownership of the NFT entirely on-chain (\ledger) or to follow the legal system's rules on ownership (\legal). (This option is discussed in section~\ref{sec:trans}.) -\end{enumerate} -These four options can be set independently, for a total of 32 theoertically possible license variations. However, it does not generally make sense to use both \noderivative and \publiclicense options, because then the NFT owner doesn't receive any useful rights beyond what everyone receives under the public license grant. - -Regardless of which license options are used, all versions of the \iccclicense include two specific uses that are always allowed. First, the material can be used to sell the NFT, e.g. on an online NFT marketplace listing. This is an essential part of truth in advertising; someone considering buying the NFT typically needs to be able to see what creative work they will actually be getting. A similar clause is present in many other NFT licenses, although we have attempted to generalize it so that it is less tied to the specifics of how any particular NFT marketplace works. Second, the \iccclicense generally allows free use of the material to identify the NFT owner publicly, e.g. in a social-media profile. This too is widespread in the NFT community and widely allowed by other NFT licenses. - - -\subsection{Commercial Uses} - -If the license is \noncommercial, it excludes any uses directed to ``commercial advantage or monetary compensation,'' which includes any cases in which people are required to pay to get a copy of the work. The definition of the \noncommercial license option specifically \emph{allows} the sale of the NFT itself. The resale of NFTs, like the resale of unique works of fine art, is a recognized and important use case. What the \noncommercial license option prohibits is the commercialization of the work by making and selling \emph{other} copies of the work, such as selling posters of an image attached to an NFT, or creating a video series based on a character depicted in a work associated with an NFT. - -Whether the license is \commercial or \noncommercial, no royalties are required. Under the \commercial license option, the NFT owner is allowed to keep all of their revenues from commercializing the work. Under the \noncommercial license option, such commercialization is not allowed at all, and constitutes a breach of the license allowing the licensor to sue for infringement. Royalties pose complicated computation and drafting problems, which we have not attempted to solve. If a platform charges a fee for an NFT listing or sale, this is neither required nor prohibited by the \iccclicense; it is an issue between the NFT owner and the platform, not between the NFT owner and the licensor. - -Similarly, if the NFT owner sells the NFT, this is not an event that entitles the NFT owner to any royalties under this license. This does not prevent the \iccclicense from being used with a smart contract that requires royalty payments. We merely have not attempted to make payment of royalties part of the copyright license. For example, a smart contract could tie invocation of NFT transfer functions to payment of required royalties. (But see \href{https://eips.ethereum.org/EIPS/eip-2981}{EIP-2981} (NFT Royalty Standard) for discussion of the reasons why such requirements may not be effective in practice.) - -\subsection{Derivative Works} - -If the licensor chooses to allow derivative works with \derivative, \derivativetracking, or \sharealike, the license grant also allows the NFT owner to use ``Adapted Material,'' which uses language from the Creative Commons license suite to define what counts as a derivative work. To reflect the customs of the NFT community, we added language to make clear that simply reproducing the work in a different medium -- e.g., printing T-shirts of a JPEG -- doesn't count as making a derivative work. Only new projects -- such as modifying artwork, remixing a song, or making a TV series based on a character -- are derivative works. - -The options to enable derivative works are cumulative: \derivative enables the creation of derivative works in all circumstances, \derivativetracking includes the derivative conditions, and introduces additional requirements concerning the technical properties of the NFT associated with the derivative work; and \sharealike includes all of the above conditions, and introduces an additional requirement concerning the legal licensing terms of the derivative work. - -When the license requires \derivativetracking, the grant of permission to make derivative works is conditional on creating an NFT for the derivative work that (1) subsists on the same ledger as the original NFT; (2) has materially the same properties and functionality as the original NFT; (3) causes the derivative NFT to reference the original NFT in such a way as to ensure that for each NFT, it is technically straightforward to identify all of its derivative NFTs. This option implements a \emph{technical} compatibility condition, one that aims to keep derivative works on the \emph{same technical standard} and on the \emph{same ledger} as the original NFT. - -When the license requires Share-Alike, the requirement of using the same technical mechanism (as prescribed by \derivativetracking) is supplemented with the further requirement that the derivative work be licensed using the same licensing terms. This is a \emph{legal compatibility} condition; it closely corresponds to the Share-Alike license option in the Creative Commons license suite. Note that \sharealike implies \derivativetracking because in the context of NFTs, the point of a Share-Alike license could be defeated by tying the license to a derivative-work NFT whose technical implementation departed too significantly from the implementation of the underlying NFT. This means that not only must the NFT associated with the derivative work implement the same technical standards, but it must also adopt the same properties and parameters as the original-work NFT in order to comply with the terms of the legal license. - -\subsection{Public Licenses} - -One common use case for NFTs is to reserve a few privileges for the NFT owner while giving the public a license to use the creative work associated with the NFT. This could be accomplished through dual licensing, in which the creator explicitly uses one license for the NFT owner and another for the public. But this approach is unsatisfactory; dual licensing gives up some of the convenience and clarity of having all of the relevant license terms clearly located in one place. Nor is it appropriate to include a public license in every version of the NFT license. The public-license model for NFTs is not universal; many NFTs give rights in the creative work only to the NFT owner. - -Thus, the \iccclicense includes a public license as a license option. When \publiclicense is selected, the license grants described above, which give specific broad rights to the NFT owner, are accompanied by a public license grant (specifically, a Creative Commons Attribution-NoDerivatives license) that gives the public the right to use the work, but not to make derivative works. - - - -\section{Transfer} -\label{sec:trans} - -The \iccclicense tries to deal sensibly with the many complications that can arise due to the unrestricted transferability of NFTs. Most existing NFT licenses have not taken this possibility seriously, even though it is arguably the defining characteristic of NFTs and the one that makes them most appealing to users. - -The first major issue is that NFTs can be, and frequently are, stolen. A hacker or phisher gains access to an NFT owner's private key and uses it to transfer the NFT to themselves, frequently turning around and immediately reselling it. Under these circumstances, should the \emph{copyright} license go to the new NFT owner or stay with the previous NFT owner? -\begin{itemize} -\item Many blockchain technologists believe that the first answer is obviously correct. The license follows whoever is the owner of the NFT according to the blockchain. The previous NFT owner's license terminates, and the new NFT owner receives a license. This approach makes the blockchain reliable, but it also makes it difficult for NFT owners to commercialize derivative works of their NFTs unless they take extreme security measures. -\item Many lawyers believe that the second answer is obviously correct. The license stays with the true owner of the NFT according to property law. The previous NFT owner retains their copyright license, and the thief takes nothing. This approach protects NFT owners against theft, fraud, and duress, and makes the NFT copyright licensing more consistent with the rest of the legal system, but it makes the blockchain non-authoritative and can require additional investigation on the part of buyers and licensees. -\end{itemize} - -This is an irreconcilable difference of views about how ledgers ought to operate, and a copyright license cannot resolve this deeper split of opinion within the NFT community. Instead, the \iccclicense provides both options, and the licensor can choose which one to use by choosing an appropriate variant of the license. The first option is called \ledger because it makes the ledger authoritative as to ownership. The second option is called called \legal because it makes the legal system authoritative as to ownership. - -To keep the distinction clear, the license uses the term ``Controller'' for the person who has control of an NFT via a private key, and ``Owner'' for the person who is legally entitled to the license. The \ledger option is implemented by defining the Owner to be the Controller (so the two concepts are the same), and the \legal option by defining the Owner to be the ``person or entity who is legally entitled to be its Controller.'' (In theory, it would be possible for a license to fine-tune the circumstances under which it does and does not transfer by stating its own rules, but at the cost of decreased compatibility with both the blockchain and with the legal system. The \iccclicense does not attempt this task.) - -The licensor may optionally provide a ``Grace Period'' as part of the smart contract for the NFT. If present, it allows a NFT owner to continue using the work (but not to create new derivatives of it) for a period of time following the transfer of the NFT. The Grace Period is defined in terms of a ``Grace Period Process,'' e.g. a smart-contract method that indicates whether the grace period has expired or not. In our view, this is the most general solution to the question of how long a grace period should be, if any. The licensor is not locked in to any particular choice, and putting this function in the smart-contract logic avoids any difficulties translating human-readable terms like ``two days'' into computation-friendly form. - -\section{Revocation} - -The \iccclicense can be revoked, but it does not define the conditions of revocation. This may seem paradoxical, but it reflects the design goal of making the license itself simple and modular. Instead, the license \emph{defers} to the smart contract that invoked it in the first place. If that contract says that the license has been revoked, it has been. - -The reasoning for this design choice can be illustrated by considering the \eiplicense interface, which defines a generic \code{revokeLicense()} method that can be called by a \code{\_revoker} address designated by the creator of the license. Thus, the following are all possible: -\begin{itemize} -\item The \code{\_revoker} is the licensor. The license can only be revoked with the consent of the licensor. -\item The \code{\_revoker} is the licensee. The license can only be revoked with the consent of the licensee. -\item The \code{\_revoker} is the zero address. The license is irrevocable. -\item The \code{\_revoker} is a smart contract which can be invoked by anyone to call the \eiplicense \code{revokeLicense()} method after the passage of 30 days from the time the license is issued. The license is for a limited time. -\item The \code{\_revoker} is a smart contract which can be invoked to call the \eiplicense \code{revokeLicense()} method upon the payment of a specified amount to the licensee. The licensor has an option to buy out the licensee. -\end{itemize} -In other words, the \eiplicense is completely generic in supporting arbitrary licensing logic, and because the \iccclicense defers to the Licensing Process (e.g., smart contract), it is also completely generic. Once again, the license is designed to work with the \eiplicense interface, but it is drafted so that any technical process serving the same function can be used instead. - -\section{Sublicensing} - -The \iccclicense takes a similarly broad and deferential attitude toward sublicensing. In practice, sublicensing is likely to be particularly useful in two scenarios. First, the owner may wish to contract with others to produce goods embodying the work, like T-shirts or song downloads. Here, a sublicense is practically necessary in a world where people don't personally print T-shirts and host downloads, but instead hire professionals to do it for them. Second, sublicensing is necessary for many derivative works: producing an animated video series, for example, will require a sublicense to a production company. Thus, the license generally allows for the free sublicensing of any of the rights held by the NFT owner. - -The \iccclicense does not attempt to enforce the requirement that a sublicense be compatible with the license it is issued under. The sublicense need not be the \iccclicense; indeed, it usually will not be. It is up to the sublicensor to choose license terms that are appropriate and are within the scope of their own license. What the \eiplicense standard can do is ensure that a party checking the licensing status of an NFT can see all of the licenses and sublicenses in force. But in general, the sublicenses may be arbitrary human-readable documents, rather than standardized machine-readable ones; it is up to the human reading them to understand their terms. (Future licenses and technical standards may fill in more sublicensing options and provide technical mechanisms to describe them; we have not attempted to solve these problems here and now.) - -As with revocation, the \iccclicense allows for a sublicense to be recorded on the blockchain, and as with revocation, these sublicenses are specifically supported by the \eiplicense interface. Again, the support is generic; the sublicense must include a link to its terms, and the \eiplicense interface does not attempt to verify that the sublicense's terms are compatible with the license's terms. Indeed, the license does not require that all sublicenses be explicitly recorded in the smart contract; for a T-shirt vendor or a web host, this is overkill. - -Instead, the advantage of recording a sublicense on the blockchain is that it makes the sublicense transfer with the NFT. The \iccclicense provides that all sublicenses that have been recorded this way carry over and remain as sublicenses from the new owner of the NFT. Someone who wants to make a substantial investment in an NFT -- e.g., a filmmaker creating a movie based on an NFT-licensed work -- can record this license using an \eiplicense smart contract. This puts everyone in the world (including potential buyers of the NFT) on notice of their sublicense and that it will carry over. (This is similar to how recording systems for copyrights and real property work, and the \eiplicense interface has been designed to make this notice straightforward.) Sublicenses do not themselves need to be NFTs and typically will not be, although it is possible to tokenize one if desired. - - - -\section{Boilerplate} - -The \iccclicense includes a section advising courts on how to interpret it in the event of a dispute. Although the license itself does not always force the copyright license to keep in sync with ownership on the blockchain, it advises courts that maintaining the reliability of blockchain records is an important goal, encouraging them to pick interpretations that make the blockchain a useful and authoritative source for understanding the status of a copyright license. In addition, the interpretation clause encourages courts to promote other common legal policies, such as fairness, uniformity, and predictability. - -The severability clause and the disclaimer of warranties and limitation of liability are based on the text of the Creative Commons license suite. These licenses are also intended to create licenses in members of the public (including people who may be unknown to the licensor), so their use cases are broadly similar. Several other clauses and definitions, including the disclaimer at the start of the license text, are also adapted from language used in the Creative Commons licenses. - -\appendix - -\section{Human-Readable Summary} -\label{sec:human} - -This is a human-readable summary of the Token-Bound NFT [\commercial{} | \noncommercial{}] [\derivative{} | \derivativetracking{} | \sharealike{} | \noderivative{}] [\publiclicense{} | \nopubliclicense{}] [\ledger{} | \legal{}] License. It is not a substitute for the license, and you should carefully review the license to understand its terms and how they may apply to you. - -This license gives you the following rights to use a creative work associated with an NFT on a ledger (such as a blockchain): - -\begin{itemize} -\item \textbf{Use and Share}: You can make copies of the work, use them personally, and share them with others \iflicenseoption{\noncommercial}{, but not for commercial purposes}. -\item \ifnotlicenseoption{\noderivative}{\textbf{Derivatives}: you can make new works that include and build on the work, use them, and share them with others \iflicenseoption{\noncommercial}{, but not for commercial purposes} \iflicenseoption{\derivativetracking}{, provided that you also release them as NFTs using the same technical standard and on the same ledger as the original NFT \iflicenseoption{\sharealike}{under the same license}}.} -\item \textbf{Ownership}: You can use the work to show that you own the NFT. -\item \textbf{Sale}: If you sell the NFT, you can use the work to show people what the NFT is. -\end{itemize} - -You have these rights as long as you own the NFT, but when you sell or transfer the NFT, your rights will end and the new owner will have these rights instead. \iflicenseoption{\ledger}{For purposes of this license, the owner of the NFT is whoever the ledger says owns it. This means that if the NFT is stolen, your rights under this license will pass to the new owner.} \iflicenseoption{\legal}{For purposes of this license, the owner of the NFT is whoever the legal system says owns it. This means that if the NFT is stolen, you will retain your rights under this license.} - -Your rights to the work are not guaranteed to be exclusive. The copyright owner who made an NFT of the work may have the right to allow others to use the work, as well. - -\iflicenseoption{\publiclicense}{In addition, the copyright owner has given everyone the right to use and share the work, regardless of whether they own the NFT.} - -This license terminates if the technical process that created it says that it has been terminated. You should check that process to see whether the license is still in effect and how it can be terminated. If this license does terminate, your rights under it will end, and you must stop using the work. - - -\section{License Text} -\label{sec:text} - -\begin{sffamily} - -\emph{The Initiative for Cryptocurrencies and Contracts (IC3) and Coalition of Automated Legal Applications (COALA) are not law firms and do not provide legal services or legal advice. Publication of the text of the \iccclicense does not create a lawyer-client or other relationship. IC3 and COALA make this text and related information available on an ``as-is'' basis. IC3 and COALA give no warranties regarding this license, any material licensed under its terms and conditions, or any related information. IC3 and COALA disclaim all liability for damages resulting from their use to the fullest extent possible.}\\\\ -The \iccclicense (the ``License'') grants you certain intellectual property rights with respect to Licensed Material associated with an NFT. When you acquire the NFT, you own the personal property rights to the token underlying the NFT (e.g., the right to freely sell, transfer, or otherwise dispose of that NFT), but you do not own the associated Licensed Material. Instead, the Licensor grants you a specified limited license to use the Licensed Material, as set forth below: - -\sect{Definitions} - -\subsect{Ledgers and NFTs} - - - \begin{itemize} - \item A ``Unique Identifier'' is information that is sufficient to distinguish one digital record from other digital records. - - \item A ``Ledger'' is a blockchain, database, or other digital system that records information about Unique Identifiers. - - \item An ``NFT'' is a Unique Identifier recorded in a Ledger. - - \item A ``Private Key'' is a cryptographic key, the use of which is necessary to modify the information about a Unique Identifier recorded in a Ledger. - - \item An NFT is ``Associated'' with a person or entity when that person or entity has substantially exclusive control over the Private Key necessary to modify the information about that Unique Identifier in that Ledger. - - \item The ``Controller'' of an NFT is the person or entity with whom the NFT is Associated. - - \end{itemize} - - \subsect{Licensing} - - \begin{itemize} - \item ``Copyright and Similar Rights'' means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and sui generis database rights, without regard to how the rights are labeled or categorized. - - \item ``License'' means the intellectual property license granted under the terms of this document. - - \item ``Licensed Rights'' means the rights granted to You subject to the terms and conditions of this License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. - - \item A ``Licensing Process'' is a technical process (including a smart contract that implements \eiplicense or any update, revision, new version, or successor thereof) designed to allow authorized parties to specify the current status of intellectual property license terms associated with an NFT and any related material, including the text of a license. A Licensing Process can (but need not) specify whether the license has been sublicensed, transferred, and/or revoked. - - \item An NFT ``Invokes'' this License when the NFT refers to the verbatim text of this License by means of a Licensing Process. - - \item The ``Licensor'' means the individual or entity that causes an NFT to Invoke this License. - - \item The ``License NFT'' is the NFT that Invokes this License. If more than one NFT Invokes this License, each such NFT is a separate License NFT resulting in a separate and distinct license grant with respect to the Licensed Material respectively associated with each such NFT. - - \end{itemize} - - \subsect{Licensed and Adapted Material} - - \begin{itemize} - - \item An NFT is ``Linked'' to material when the NFT contains a description of or hyperlink to that material, and that description or hyperlink is substantially immutable in the ordinary course of operation of the Ledger, - - \item ``Licensed Material'' means the creative work or other material to which the License NFT is Linked. - - \item ``Adapted Material'' means material subject to copyright and similar rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the copyright and similar rights held by the Licensor. For purposes of this License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. For purposes of this License, the exact reproduction of the Licensed Material in a different medium in a manner not requiring original creative effort (such as printing a photographic or pictorial work on paper) does not produce Adapted Material. - - \item ``Commercial'' means primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this License, activity is Commercial if direct or indirect payment is required as a condition of access to the Licensed Material or Adapted Material. For purposes of this License, the sale and advertising for sale of the License NFT (including the accompanying rights granted under this License) is not Commercial. - - \item ``Non-Commercial'' means not Commercial. - - \item To ``Use'' material is to copy, display, distribute, make available to the public, or perform it. - - \end{itemize} - - \subsect{Ownership and Transfers} - - \begin{itemize} - - \item The ``Owner'' of an NFT is \iflicenseoption{\legal}{the person or entity who is legally entitled to be its Controller} \iflicenseoption{\ledger}{its Controller}. - - \item ``You'' and ``Your'' refer to the person receiving rights under this License as the Owner of the License NFT. - - \item An NFT ``Revokes'' this License when the NFT indicates that it has been revoked by means of a Licensing Process. - - \item An NFT is ``Sublicensed'' when the NFT indicates that it has been sublicensed by means of a Licensing Process. - - \item \iflicenseoption{\derivativetracking}{Adapted Material is ``Derivative Tracked'' from an NFT (the ``Parent NFT'') when the Parent NFT is Sublicensed by means of a Licensing Process that (1) creates another NFT (the ``Child NFT'') on the same Ledger as the Parent NFT, (2) causes the Child NFT to be Linked to Adapted Material, and (3) causes the Child NFT and Parent NFT to have materially the same properties and functionality, except for the identity of the parties they are Associated with and the identity of the material they are Linked to.} - - \item \iflicenseoption{\sharealike}{Adapted Material is ``Share-Alike Sublicensed'' from an NFT (the ``Parent NFT'') when the Parent NFT is Sublicensed by means of a Licensing Process that (1) creates another NFT (the ``Child NFT'') on the same Ledger as the Parent NFT, (2) causes the Child NFT to be Linked to Adapted Material, (3) causes the Child NFT and Parent NFT to have materially the same properties and functionality, except for the identity of the parties they are Associated with and the identity of the material they are Linked to, and (4) causes the Child NFT to Invoke this license.} - - \item To ``Transfer'' an NFT is to change, modify, or update the Ledger such that the identity of the Owner of that NFT changes, by any legally sufficient means, including sale, barter, gift, bequest, or operation of law. - - \item ``Grace Period Process'' means an interface, function, method, or similar technical process that is part of a Licensing Process and which, when invoked, indicates whether a specified limited duration following the Transfer of an NFT has elapsed. - - \item ``Grace Period'' means the time following the Transfer of an NFT during which the Grace Period Process indicates that the specified limited duration has not yet elapsed. - - \end{itemize} - - -\sect{Public License Grant} - -\iflicenseoption{\publiclicense}{The Licensed Material is made available to the public under the terms of the Creative Commons Attribution-NoDerivatives 4.0 International Public License at \href{https://creativecommons.org/licenses/by-nd/4.0/legalcode}{https://creativecommons.org/licenses/by-nd/4.0/legalcode}.} - -\sect{NFT License Grant} - -Subject to the terms and conditions of this License, and on the condition that You are the Owner of the License NFT, the Licensor hereby grants to You a worldwide, royalty-free, sublicensable, non-exclusive, license to exercise the Licensed Rights to: -\begin{itemize} -\item Use the Licensed Material \iflicenseoption{\noncommercial}{for Non-Commercial purposes only}, -\item \ifnotlicenseoption{\noderivative}{Create and Use Adapted Material \iflicenseoption{\noncommercial}{for Non-Commercial purposes only} \iflicenseoption{\derivativetracking}{provided that the Adapted Material is Derivative Tracked from the License NFT} \iflicenseoption{\sharealike}{provided that the Adapted Material is Share-Alike Sublicensed from the License NFT}}, -\item Identify You as the Owner of the License NFT, -\item Use the Licensed Material in connection with the sale and advertising for sale of the License NFT, -\end{itemize} -This License becomes effective when the Licensor causes the License NFT to Invoke it. Because this is a unilateral license grant and not a contract, Your assent is not required for it to become effective. \iflicenseoption{\publiclicense}{This license grant is in addition to any rights granted to You as a member of the public under the terms of the Public License Grant above.} - -All rights granted under this License last for the full duration of the Licensor's Licensed Rights, except that if the License NFT Revokes this License, Your rights under this license grant will terminate, as will all sublicenses granted hereunder. - -This license grant is non-transferable. If the License NFT is Transferred such that You are no longer the Owner, Your rights under this license grant will terminate. Notwithstanding the previous sentence, if the Licensing Process contains a Grace Period Process, You may continue to Use the Licensed Material and any already-existing Adapted Material under the terms of the license grant above during the Grace Period, but You may not Create or Use new Adapted Material, grant new sublicenses, or Use the Licensed Material to identify yourself as the owner of the License NFT. - -If the License NFT is Transferred, any sublicenses granted hereunder that are Sublicensed will continue in force as sublicenses from the new Owner. If You have become the Owner of the License NFT as a result of a Transfer, you automatically grant any such sublicenses that are Sublicensed. This License does not by itself require you to continue or grant any sublicenses given by a previous Owner that were not Sublicensed, but it does not prevent other law from doing so. - - -\sect{Interpretation} - -By adopting this License, the Licensor intends to enhance the clarity and predictability of intellectual-property licensing via digital transactions. In cases of doubt or ambiguity, the terms of this license should be interpreted and applied to promote the goals of (1) making the information in the Digital System accurately reflect the licensing relationships it describes, and vice versa, (2) providing clear, simple, and unambiguous mechanisms for parties to express their licensing intentions, (3) protecting parties from duress, fraud, forgery, and mistake, and (4) achieving uniformity and consistency in the application of this License to different transactions, at different times, and in different jurisdictions. - -To the extent possible, if any provision of this License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this License without affecting the enforceability of the remaining terms. - -\sect{Disclaimer of Warranties and Limitation of Liability} - -Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You. - -To the extent possible, unless otherwise separately undertaken by the Licensor, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You. - -The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. - -\end{sffamily} - -\end{document} diff --git a/assets/eip-5218/license-tree.png b/assets/eip-5218/license-tree.png deleted file mode 100644 index 088cae0b1051f7..00000000000000 Binary files a/assets/eip-5218/license-tree.png and /dev/null differ diff --git a/assets/eip-5219/IDecentralizedApp.sol b/assets/eip-5219/IDecentralizedApp.sol deleted file mode 100644 index 80a21fcd7dcc6a..00000000000000 --- a/assets/eip-5219/IDecentralizedApp.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -struct KeyValue { - string key; - string value; -} - -interface IDecentralizedApp { - /// @notice Send an HTTP GET-like request to this contract - /// @param resource The resource to request (e.g. "/asdf/1234" turns in to `["asdf", "1234"]`) - /// @param params The query parameters. (e.g. "?asdf=1234&foo=bar" turns in to `[{ key: "asdf", value: "1234" }, { key: "foo", value: "bar" }]`) - /// @return statusCode The HTTP status code (e.g. 200) - /// @return body The body of the response - /// @return headers A list of header names (e.g. [{ key: "Content-Type", value: "application/json" }]) - function request(string[] memory resource, KeyValue[] memory params) external view returns (uint16 statusCode, string memory body, KeyValue[] headers); -} diff --git a/assets/eip-5247/ProposalRegistry.sol b/assets/eip-5247/ProposalRegistry.sol deleted file mode 100644 index 76f56f2c99be98..00000000000000 --- a/assets/eip-5247/ProposalRegistry.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT -// A fully runnalbe version can be found in https://github.com/ercref/ercref-contracts/tree/869843f23dc4da793f0d9d018ed92e3950da8f75 -pragma solidity ^0.8.17; - -import "./IERC5247.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -struct Proposal { - address by; - uint256 proposalId; - address[] targets; - uint256[] values; - uint256[] gasLimits; - bytes[] calldatas; -} - -contract ProposalRegistry is IERC5247 { - using Address for address; - mapping(uint256 => Proposal) public proposals; - uint256 private proposalCount; - function createProposal( - uint256 proposalId, - address[] calldata targets, - uint256[] calldata values, - uint256[] calldata gasLimits, - bytes[] calldata calldatas, - bytes calldata extraParams - ) external returns (uint256 registeredProposalId) { - require(targets.length == values.length, "GeneralForwarder: targets and values length mismatch"); - require(targets.length == gasLimits.length, "GeneralForwarder: targets and gasLimits length mismatch"); - require(targets.length == calldatas.length, "GeneralForwarder: targets and calldatas length mismatch"); - registeredProposalId = proposalCount; - proposalCount++; - - proposals[registeredProposalId] = Proposal({ - by: msg.sender, - proposalId: proposalId, - targets: targets, - values: values, - calldatas: calldatas, - gasLimits: gasLimits - }); - emit ProposalCreated(msg.sender, proposalId, targets, values, gasLimits, calldatas, extraParams); - return registeredProposalId; - } - function executeProposal(uint256 proposalId, bytes calldata extraParams) external { - Proposal storage proposal = proposals[proposalId]; - address[] memory targets = proposal.targets; - string memory errorMessage = "Governor: call reverted without message"; - for (uint256 i = 0; i < targets.length; ++i) { - (bool success, bytes memory returndata) = proposal.targets[i].call{value: proposal.values[i]}(proposal.calldatas[i]); - Address.verifyCallResult(success, returndata, errorMessage); - } - emit ProposalExecuted(msg.sender, proposalId, extraParams); - } - - function getProposal(uint256 proposalId) external view returns (Proposal memory) { - return proposals[proposalId]; - } - - function getProposalCount() external view returns (uint256) { - return proposalCount; - } - -} diff --git a/assets/eip-5247/testProposalRegistry.ts b/assets/eip-5247/testProposalRegistry.ts deleted file mode 100644 index 947c40bebc327b..00000000000000 --- a/assets/eip-5247/testProposalRegistry.ts +++ /dev/null @@ -1,162 +0,0 @@ -// A fully runnalbe version can be found in https://github.com/ercref/ercref-contracts/tree/869843f23dc4da793f0d9d018ed92e3950da8f75 -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { hexlify } from "ethers/lib/utils"; -import { ethers } from "hardhat"; - -describe("ProposalRegistry", function () { - async function deployFixture() { - // Contracts are deployed using the first signer/account by default - const [owner, otherAccount] = await ethers.getSigners(); - - const ProposalRegistry = await ethers.getContractFactory("ProposalRegistry"); - const contract = await ProposalRegistry.deploy(); - - const ERC721ForTesting = await ethers.getContractFactory("ERC721ForTesting"); - const erc721 = await ERC721ForTesting.deploy(); - - const SimpleForwarder = await ethers.getContractFactory("SimpleForwarder"); - const forwarder = await SimpleForwarder.deploy(); - return { contract, erc721, forwarder, owner, otherAccount }; - } - - describe("Deployment", function () { - it("Should work for a simple case", async function () { - const { contract, erc721, owner } = await loadFixture(deployFixture); - const callData1 = erc721.interface.encodeFunctionData("mint", [owner.address, 1]); - const callData2 = erc721.interface.encodeFunctionData("mint", [owner.address, 2]); - await contract.connect(owner) - .createProposal( - 0, - [erc721.address, erc721.address], - [0,0], - [0,0], - [callData1, callData2], - []); - expect(await erc721.balanceOf(owner.address)).to.equal(0); - await contract.connect(owner).executeProposal(0, []); - expect(await erc721.balanceOf(owner.address)).to.equal(2); - }); - const Ns = [0, 50, 100, 150, 200]; - for (let n of Ns) { - - it(`Should work for a proposal case of ${n}`, async function () { - const { contract, erc721, owner } = await loadFixture(deployFixture); - const numOfMint = n; - const calldatas = []; - for (let i = 0 ; i < numOfMint; i++) { - const callData = erc721.interface.encodeFunctionData("mint", [owner.address, i]); - calldatas.push(callData); - } - let txCreate = await contract.connect(owner) - .createProposal( - 0, - Array(numOfMint).fill(erc721.address), - Array(numOfMint).fill(0), - Array(numOfMint).fill(0), - calldatas, - []); - let txCreateWaited = await txCreate.wait(); - console.log(`Creation TX gas`, txCreateWaited.cumulativeGasUsed.toString()); - console.log(`Gas per mint`, parseInt(txCreateWaited.cumulativeGasUsed.toString()) / numOfMint); - expect(await erc721.balanceOf(owner.address)).to.equal(0); - let txExecute = await contract.connect(owner).executeProposal(0, []); - let txExecuteWaited = await txExecute.wait(); - console.log(`Execution TX gas`, txExecuteWaited.cumulativeGasUsed.toString()); - console.log(`Gas per mint`, parseInt(txExecuteWaited.cumulativeGasUsed.toString()) / numOfMint); - expect(await erc721.balanceOf(owner.address)).to.equal(numOfMint); - }); - } - }); - describe("Benchmark", function () { - it(`Should work for a forwarding case`, async function () { - const { forwarder, erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 200; - const calldatas = []; - for (let i = 0 ; i < numOfMint; i++) { - const callData = erc721.interface.encodeFunctionData("mint", [owner.address, i]); - calldatas.push(callData); - } - expect(await erc721.balanceOf(owner.address)).to.equal(0); - let txForward = await forwarder.connect(owner) - .forward( - Array(numOfMint).fill(erc721.address), - Array(numOfMint).fill(0), - Array(numOfMint).fill(0), - calldatas); - let txForwardWaited = await txForward.wait(); - - console.log(`txForwardWaited TX gas`, txForwardWaited.cumulativeGasUsed.toString()); - - console.log(`Gas per mint`, parseInt(txForwardWaited.cumulativeGasUsed.toString()) / numOfMint); - expect(await erc721.balanceOf(owner.address)).to.equal(numOfMint); - - }); - - - it(`Should work for erc721 batchMint with same addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 200; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(owner.address);// addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }) - - it(`Should work for erc721 batchMint with different addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 200; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }); - - - it(`Should work for erc721 batchSafeMint with same addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 400; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(owner.address);// addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchSafeMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchSafeMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }); - - it(`Should work for erc721 batchSafeMint with different addresses`, async function () { - const { erc721, owner } = await loadFixture(deployFixture); - const numOfMint = 400; - const tokenIds = []; - const addresses = []; - - for (let i = 0 ; i < numOfMint; i++) { - addresses.push(hexlify(ethers.utils.randomBytes(20))); - tokenIds.push(i); - } - const tx = await erc721.connect(owner).batchSafeMint(addresses, tokenIds); - const txWaited = await tx.wait(); - console.log(`batchSafeMint TX gas`, txWaited.cumulativeGasUsed.toString()); - console.log(`At ${numOfMint} the Gas per mint`, parseInt(txWaited.cumulativeGasUsed.toString()) / numOfMint); - }); - }); -}); diff --git a/assets/eip-5252/.gitignore b/assets/eip-5252/.gitignore deleted file mode 100644 index b41feef092ab74..00000000000000 --- a/assets/eip-5252/.gitignore +++ /dev/null @@ -1,14 +0,0 @@ -node_modules -.env -coverage -coverage.json -typechain -typechain-types -yarn.lock -package-lock.json -yarn-error.log -.DS_Store - -#Hardhat files -cache -artifacts diff --git a/assets/eip-5252/README.md b/assets/eip-5252/README.md deleted file mode 100644 index 5136b9b0f38542..00000000000000 --- a/assets/eip-5252/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# EIP 5252 implementation - -This project is a reference implementation of EIP-5252. - -Try running some of the following tasks: - -```shell -npx hardhat help -npx hardhat test -GAS_REPORT=true npx hardhat test -npx hardhat node -npx hardhat run scripts/deploy.ts -``` diff --git a/assets/eip-5252/contracts/ABT.sol b/assets/eip-5252/contracts/ABT.sol deleted file mode 100644 index a9896e07fc70f6..00000000000000 --- a/assets/eip-5252/contracts/ABT.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC721A.sol"; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "./interfaces/IFactory.sol"; -import "./interfaces/IFinance.sol"; -import "./interfaces/IDescriptor.sol"; - -contract ABT is ERC721A, AccessControl { - // Create a new role identifier for the minter role - bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); - // factory address - address public factory; - // SVG for ABT - address public descriptor; - - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721A, AccessControl) returns (bool) { - return super.supportsInterface(interfaceId); - } - - function setDescriptor(address descriptor_) public { - require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "ABT: Caller is not a default admin"); - descriptor = descriptor_; - } - - function tokenURI(uint256 tokenId) public view virtual override returns (string memory tokenURI) { - require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - tokenURI = IDescriptor(descriptor).tokenURI(tokenId); - } - - constructor(address factory_) - ERC721A("Account-Bound Token", "ABT") { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - - _setupRole(MINTER_ROLE, _msgSender()); - _setupRole(BURNER_ROLE, _msgSender()); - factory = factory_; - } - - function setFactory(address factory_) public { - require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "ABT: Caller is not a default admin"); - factory = factory_; - } - - function mint(address to) external { - // Check that the calling account has the minter role - require(_msgSender() == factory, "ABT: Caller is not factory"); - _safeMint(to, 1); - } - - function burn(uint256 tokenId_) external { - require(hasRole(BURNER_ROLE, _msgSender()), "ABT: must have burner role to burn"); - _burn(tokenId_); - } - - function exists(uint256 tokenId_) external view returns (bool) { - return _exists(tokenId_); - } - - function transfer( - address to, - uint256 tokenId - ) public virtual { - transferFrom(msg.sender, to, tokenId); - } -} diff --git a/assets/eip-5252/contracts/ERC721A.sol b/assets/eip-5252/contracts/ERC721A.sol deleted file mode 100644 index 92ebd127e2d6e2..00000000000000 --- a/assets/eip-5252/contracts/ERC721A.sol +++ /dev/null @@ -1,616 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -// Creator: Chiru Labs - -pragma solidity ^0.8.4; - -import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; -import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; -import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol'; -import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol'; -import '@openzeppelin/contracts/utils/Address.sol'; -import '@openzeppelin/contracts/utils/Context.sol'; -import '@openzeppelin/contracts/utils/Strings.sol'; -import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; - -error ApprovalCallerNotOwnerNorApproved(); -error ApprovalQueryForNonexistentToken(); -error ApproveToCaller(); -error ApprovalToCurrentOwner(); -error BalanceQueryForZeroAddress(); -error MintedQueryForZeroAddress(); -error BurnedQueryForZeroAddress(); -error AuxQueryForZeroAddress(); -error MintToZeroAddress(); -error MintZeroQuantity(); -error OwnerIndexOutOfBounds(); -error OwnerQueryForNonexistentToken(); -error TokenIndexOutOfBounds(); -error TransferCallerNotOwnerNorApproved(); -error TransferFromIncorrectOwner(); -error TransferToNonERC721ReceiverImplementer(); -error TransferToZeroAddress(); -error URIQueryForNonexistentToken(); - -/** - * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including - * the Metadata extension. Built to optimize for lower gas during batch mints. - * - * Assumes serials are sequentially minted starting at _startTokenId() (defaults to 0, e.g. 0, 1, 2, 3..). - * - * Assumes that an owner cannot have more than 2**64 - 1 (max value of uint64) of supply. - * - * Assumes that the maximum token id cannot exceed 2**256 - 1 (max value of uint256). - */ -contract ERC721A is Context, ERC165, IERC721, IERC721Metadata { - using Address for address; - using Strings for uint256; - - // Compiler will pack this into a single 256bit word. - struct TokenOwnership { - // The address of the owner. - address addr; - // Keeps track of the start time of ownership with minimal overhead for tokenomics. - uint64 startTimestamp; - // Whether the token has been burned. - bool burned; - } - - // Compiler will pack this into a single 256bit word. - struct AddressData { - // Realistically, 2**64-1 is more than enough. - uint64 balance; - // Keeps track of mint count with minimal overhead for tokenomics. - uint64 numberMinted; - // Keeps track of burn count with minimal overhead for tokenomics. - uint64 numberBurned; - // For miscellaneous variable(s) pertaining to the address - // (e.g. number of whitelist mint slots used). - // If there are multiple variables, please pack them into a uint64. - uint64 aux; - } - - // The tokenId of the next token to be minted. - uint256 internal _currentIndex; - - // The number of tokens burned. - uint256 internal _burnCounter; - - // Token name - string private _name; - - // Token symbol - string private _symbol; - - // Mapping from token ID to ownership details - // An empty struct value does not necessarily mean the token is unowned. See ownershipOf implementation for details. - mapping(uint256 => TokenOwnership) internal _ownerships; - - // Mapping owner address to address data - mapping(address => AddressData) private _addressData; - - // Mapping from token ID to approved address - mapping(uint256 => address) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - _currentIndex = _startTokenId(); - } - - /** - * To change the starting tokenId, please override this function. - */ - function _startTokenId() internal view virtual returns (uint256) { - return 0; - } - - /** - * @dev See {IERC721Enumerable-totalSupply}. - * @dev Burned tokens are calculated here, use _totalMinted() if you want to count just minted tokens. - */ - function totalSupply() public view returns (uint256) { - // Counter underflow is impossible as _burnCounter cannot be incremented - // more than _currentIndex - _startTokenId() times - unchecked { - return _currentIndex - _burnCounter - _startTokenId(); - } - } - - /** - * Returns the total amount of tokens minted in the contract. - */ - function _totalMinted() internal view returns (uint256) { - // Counter underflow is impossible as _currentIndex does not decrement, - // and it is initialized to _startTokenId() - unchecked { - return _currentIndex - _startTokenId(); - } - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC721Metadata).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev See {IERC721-balanceOf}. - */ - function balanceOf(address owner) public view override returns (uint256) { - if (owner == address(0)) revert BalanceQueryForZeroAddress(); - return uint256(_addressData[owner].balance); - } - - /** - * Returns the number of tokens minted by `owner`. - */ - function _numberMinted(address owner) internal view returns (uint256) { - if (owner == address(0)) revert MintedQueryForZeroAddress(); - return uint256(_addressData[owner].numberMinted); - } - - /** - * Returns the number of tokens burned by or on behalf of `owner`. - */ - function _numberBurned(address owner) internal view returns (uint256) { - if (owner == address(0)) revert BurnedQueryForZeroAddress(); - return uint256(_addressData[owner].numberBurned); - } - - /** - * Returns the auxiliary data for `owner`. (e.g. number of whitelist mint slots used). - */ - function _getAux(address owner) internal view returns (uint64) { - if (owner == address(0)) revert AuxQueryForZeroAddress(); - return _addressData[owner].aux; - } - - /** - * Sets the auxiliary data for `owner`. (e.g. number of whitelist mint slots used). - * If there are multiple variables, please pack them into a uint64. - */ - function _setAux(address owner, uint64 aux) internal { - if (owner == address(0)) revert AuxQueryForZeroAddress(); - _addressData[owner].aux = aux; - } - - /** - * Gas spent here starts off proportional to the maximum mint batch size. - * It gradually moves to O(1) as tokens get transferred around in the collection over time. - */ - function ownershipOf(uint256 tokenId) internal view returns (TokenOwnership memory) { - uint256 curr = tokenId; - - unchecked { - if (_startTokenId() <= curr && curr < _currentIndex) { - TokenOwnership memory ownership = _ownerships[curr]; - if (!ownership.burned) { - if (ownership.addr != address(0)) { - return ownership; - } - // Invariant: - // There will always be an ownership that has an address and is not burned - // before an ownership that does not have an address and is not burned. - // Hence, curr will not underflow. - while (true) { - curr--; - ownership = _ownerships[curr]; - if (ownership.addr != address(0)) { - return ownership; - } - } - } - } - } - revert OwnerQueryForNonexistentToken(); - } - - /** - * @dev See {IERC721-ownerOf}. - */ - function ownerOf(uint256 tokenId) public view override returns (address) { - return ownershipOf(tokenId).addr; - } - - /** - * @dev See {IERC721Metadata-name}. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev See {IERC721Metadata-symbol}. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev See {IERC721Metadata-tokenURI}. - */ - function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { - if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); - - string memory baseURI = _baseURI(); - return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ''; - } - - /** - * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each - * token will be the concatenation of the `baseURI` and the `tokenId`. Empty - * by default, can be overridden in child contracts. - */ - function _baseURI() internal view virtual returns (string memory) { - return ''; - } - - /** - * @dev See {IERC721-approve}. - */ - function approve(address to, uint256 tokenId) public override { - address owner = ERC721A.ownerOf(tokenId); - if (to == owner) revert ApprovalToCurrentOwner(); - - if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) { - revert ApprovalCallerNotOwnerNorApproved(); - } - - _approve(to, tokenId, owner); - } - - /** - * @dev See {IERC721-getApproved}. - */ - function getApproved(uint256 tokenId) public view override returns (address) { - if (!_exists(tokenId)) revert ApprovalQueryForNonexistentToken(); - - return _tokenApprovals[tokenId]; - } - - /** - * @dev See {IERC721-setApprovalForAll}. - */ - function setApprovalForAll(address operator, bool approved) public override { - if (operator == _msgSender()) revert ApproveToCaller(); - - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @dev See {IERC721-isApprovedForAll}. - */ - function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { - return _operatorApprovals[owner][operator]; - } - - /** - * @dev See {IERC721-transferFrom}. - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - _transfer(from, to, tokenId); - } - - /** - * @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - safeTransferFrom(from, to, tokenId, ''); - } - - /** - * @dev See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) public virtual override { - _transfer(from, to, tokenId); - if (to.isContract() && !_checkContractOnERC721Received(from, to, tokenId, _data)) { - revert TransferToNonERC721ReceiverImplementer(); - } - } - - /** - * @dev Returns whether `tokenId` exists. - * - * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. - * - * Tokens start existing when they are minted (`_mint`), - */ - function _exists(uint256 tokenId) internal view returns (bool) { - return _startTokenId() <= tokenId && tokenId < _currentIndex && - !_ownerships[tokenId].burned; - } - - function _safeMint(address to, uint256 quantity) internal { - _safeMint(to, quantity, ''); - } - - /** - * @dev Safely mints `quantity` tokens and transfers them to `to`. - * - * Requirements: - * - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called for each safe transfer. - * - `quantity` must be greater than 0. - * - * Emits a {Transfer} event. - */ - function _safeMint( - address to, - uint256 quantity, - bytes memory _data - ) internal { - _mint(to, quantity, _data, true); - } - - /** - * @dev Mints `quantity` tokens and transfers them to `to`. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - `quantity` must be greater than 0. - * - * Emits a {Transfer} event. - */ - function _mint( - address to, - uint256 quantity, - bytes memory _data, - bool safe - ) internal { - uint256 startTokenId = _currentIndex; - if (to == address(0)) revert MintToZeroAddress(); - if (quantity == 0) revert MintZeroQuantity(); - - _beforeTokenTransfers(address(0), to, startTokenId, quantity); - - // Overflows are incredibly unrealistic. - // balance or numberMinted overflow if current value of either + quantity > 1.8e19 (2**64) - 1 - // updatedIndex overflows if _currentIndex + quantity > 1.2e77 (2**256) - 1 - unchecked { - _addressData[to].balance += uint64(quantity); - _addressData[to].numberMinted += uint64(quantity); - - _ownerships[startTokenId].addr = to; - _ownerships[startTokenId].startTimestamp = uint64(block.timestamp); - - uint256 updatedIndex = startTokenId; - uint256 end = updatedIndex + quantity; - - if (safe && to.isContract()) { - do { - emit Transfer(address(0), to, updatedIndex); - if (!_checkContractOnERC721Received(address(0), to, updatedIndex++, _data)) { - revert TransferToNonERC721ReceiverImplementer(); - } - } while (updatedIndex != end); - // Reentrancy protection - if (_currentIndex != startTokenId) revert(); - } else { - do { - emit Transfer(address(0), to, updatedIndex++); - } while (updatedIndex != end); - } - _currentIndex = updatedIndex; - } - _afterTokenTransfers(address(0), to, startTokenId, quantity); - } - - /** - * @dev Transfers `tokenId` from `from` to `to`. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * - * Emits a {Transfer} event. - */ - function _transfer( - address from, - address to, - uint256 tokenId - ) private { - TokenOwnership memory prevOwnership = ownershipOf(tokenId); - - bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr || - isApprovedForAll(prevOwnership.addr, _msgSender()) || - getApproved(tokenId) == _msgSender()); - - if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved(); - if (prevOwnership.addr != from) revert TransferFromIncorrectOwner(); - if (to == address(0)) revert TransferToZeroAddress(); - - _beforeTokenTransfers(from, to, tokenId, 1); - - // Clear approvals from the previous owner - _approve(address(0), tokenId, prevOwnership.addr); - - // Underflow of the sender's balance is impossible because we check for - // ownership above and the recipient's balance can't realistically overflow. - // Counter overflow is incredibly unrealistic as tokenId would have to be 2**256. - unchecked { - _addressData[from].balance -= 1; - _addressData[to].balance += 1; - - _ownerships[tokenId].addr = to; - _ownerships[tokenId].startTimestamp = uint64(block.timestamp); - - // If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it. - // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. - uint256 nextTokenId = tokenId + 1; - if (_ownerships[nextTokenId].addr == address(0)) { - // This will suffice for checking _exists(nextTokenId), - // as a burned slot cannot contain the zero address. - if (nextTokenId < _currentIndex) { - _ownerships[nextTokenId].addr = prevOwnership.addr; - _ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp; - } - } - } - - emit Transfer(from, to, tokenId); - _afterTokenTransfers(from, to, tokenId, 1); - } - - /** - * @dev Destroys `tokenId`. - * The approval is cleared when the token is burned. - * - * Requirements: - * - * - `tokenId` must exist. - * - * Emits a {Transfer} event. - */ - function _burn(uint256 tokenId) internal virtual { - TokenOwnership memory prevOwnership = ownershipOf(tokenId); - - _beforeTokenTransfers(prevOwnership.addr, address(0), tokenId, 1); - - // Clear approvals from the previous owner - _approve(address(0), tokenId, prevOwnership.addr); - - // Underflow of the sender's balance is impossible because we check for - // ownership above and the recipient's balance can't realistically overflow. - // Counter overflow is incredibly unrealistic as tokenId would have to be 2**256. - unchecked { - _addressData[prevOwnership.addr].balance -= 1; - _addressData[prevOwnership.addr].numberBurned += 1; - - // Keep track of who burned the token, and the timestamp of burning. - _ownerships[tokenId].addr = prevOwnership.addr; - _ownerships[tokenId].startTimestamp = uint64(block.timestamp); - _ownerships[tokenId].burned = true; - - // If the ownership slot of tokenId+1 is not explicitly set, that means the burn initiator owns it. - // Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls. - uint256 nextTokenId = tokenId + 1; - if (_ownerships[nextTokenId].addr == address(0)) { - // This will suffice for checking _exists(nextTokenId), - // as a burned slot cannot contain the zero address. - if (nextTokenId < _currentIndex) { - _ownerships[nextTokenId].addr = prevOwnership.addr; - _ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp; - } - } - } - - emit Transfer(prevOwnership.addr, address(0), tokenId); - _afterTokenTransfers(prevOwnership.addr, address(0), tokenId, 1); - - // Overflow not possible, as _burnCounter cannot be exceed _currentIndex times. - unchecked { - _burnCounter++; - } - } - - /** - * @dev Approve `to` to operate on `tokenId` - * - * Emits a {Approval} event. - */ - function _approve( - address to, - uint256 tokenId, - address owner - ) private { - _tokenApprovals[tokenId] = to; - emit Approval(owner, to, tokenId); - } - - /** - * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target contract. - * - * @param from address representing the previous owner of the given token ID - * @param to target address that will receive the tokens - * @param tokenId uint256 ID of the token to be transferred - * @param _data bytes optional data to send along with the call - * @return bool whether the call correctly returned the expected magic value - */ - function _checkContractOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory _data - ) private returns (bool) { - try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { - return retval == IERC721Receiver(to).onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert TransferToNonERC721ReceiverImplementer(); - } else { - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } - - /** - * @dev Hook that is called before a set of serially-ordered token ids are about to be transferred. This includes minting. - * And also called before burning one token. - * - * startTokenId - the first token id to be transferred - * quantity - the amount to be transferred - * - * Calling conditions: - * - * - When `from` and `to` are both non-zero, `from`'s `tokenId` will be - * transferred to `to`. - * - When `from` is zero, `tokenId` will be minted for `to`. - * - When `to` is zero, `tokenId` will be burned by `from`. - * - `from` and `to` are never both zero. - */ - function _beforeTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual {} - - /** - * @dev Hook that is called after a set of serially-ordered token ids have been transferred. This includes - * minting. - * And also called after one token has been burned. - * - * startTokenId - the first token id to be transferred - * quantity - the amount to be transferred - * - * Calling conditions: - * - * - When `from` and `to` are both non-zero, `from`'s `tokenId` has been - * transferred to `to`. - * - When `from` is zero, `tokenId` has been minted for `to`. - * - When `to` is zero, `tokenId` has been burned by `from`. - * - `from` and `to` are never both zero. - */ - function _afterTokenTransfers( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual {} -} \ No newline at end of file diff --git a/assets/eip-5252/contracts/Factory.sol b/assets/eip-5252/contracts/Factory.sol deleted file mode 100644 index 335e01a27550cb..00000000000000 --- a/assets/eip-5252/contracts/Factory.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "./Finance.sol"; -import "./libraries/CloneFactory.sol"; -import "./interfaces/IFactory.sol"; - -contract Factory is AccessControl, IFactory { - // Vaults - address[] public allFinances; - /// Address of cdp nft registry - address public override abt; - /// Address of Wrapped Ether - address public override WETH; - /// Address of manager - address public override manager; - /// version number of impl - uint256 version; - /// address of vault impl - address public impl; - - constructor() { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - _createImpl(); - } - - /// Vault can issue stablecoin, it just manages the position - function createFinance( - address weth_, - uint256 amount_, - address recipient - ) external override returns (address vault, uint256 id) { - require(msg.sender == manager, "Factory: IA"); - uint256 gIndex = allFinancesLength(); - address proxy = CloneFactory._createClone(impl); - IFinance(proxy).initialize(manager, gIndex, abt, amount_, weth_); - allFinances.push(proxy); - IABT(abt).mint(recipient); - return (proxy, gIndex); - } - - // Set immutable, consistent, one rule for vault implementation - function _createImpl() internal { - address addr; - bytes memory bytecode = type(Finance).creationCode; - bytes32 salt = keccak256(abi.encodePacked("finance", version)); - assembly { - addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) - if iszero(extcodesize(addr)) { - revert(0, 0) - } - } - impl = addr; - } - - function isClone(address vault) external view returns (bool cloned) { - cloned = CloneFactory._isClone(impl, vault); - } - - function initialize( - address abt_, - address weth_, - address manager_, - uint256 version_ - ) public { - require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "IA"); // Invalid Access - abt = abt_; - WETH = weth_; - manager = manager_; - version = version_; - } - - function getFinance(uint256 financeId_) - external - view - override - returns (address) - { - return allFinances[financeId_]; - } - - function financeCodeHash() - external - pure - override - returns (bytes32 vaultCode) - { - return keccak256(hex"3d602d80600a3d3981f3"); - } - - function allFinancesLength() public view returns (uint256) { - return allFinances.length; - } -} diff --git a/assets/eip-5252/contracts/Finance.sol b/assets/eip-5252/contracts/Finance.sol deleted file mode 100644 index 69d4be55607172..00000000000000 --- a/assets/eip-5252/contracts/Finance.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./interfaces/IERC20Minimal.sol"; -import "./libraries/TransferHelper.sol"; -import "./interfaces/IFinance.sol"; -import "./interfaces/IABT.sol"; -import "./interfaces/IWETH.sol"; -import "./libraries/Initializable.sol"; -import "./interfaces/IManager.sol"; -import "./interfaces/IInfluencer.sol"; - -contract Finance is IFinance, Initializable { - /// Address of a manager - address public override manager; - /// Address of a factory - address public override factory; - /// Address of a factory - address public override influencer; - /// Address of account bound token - address public override abt; - /// Finance global identifier - uint256 public override financeId; - /// Address of wrapped eth - address public override WETH; - /// Finance Creation Date - uint256 public override createdAt; - /// Finance Last Updated Date - uint256 public override lastUpdated; - /// deposited amount to the account - uint256 public override deposit; - - modifier onlyFinanceOwner() { - require( - IABT(abt).ownerOf(financeId) == msg.sender, - "Finance: Finance is not owned by you" - ); - _; - } - - // called once by the factory at time of deployment - function initialize( - address manager_, - uint256 financeId_, - address abt_, - uint256 amount_, - address weth_ - ) external override initializer { - financeId = financeId_; - abt = abt_; - WETH = weth_; - manager = manager_; - factory = msg.sender; - deposit = amount_; - lastUpdated = block.timestamp; - createdAt = block.timestamp; - influencer = IManager(manager_).influencer(); - } - - function depositNative() external payable onlyFinanceOwner { - // wrap deposit - deposit += msg.value; - IInfluencer(influencer).deposit(msg.value); - IWETH(WETH).deposit{value: msg.value}(); - emit DepositFundNative(financeId, msg.value); - } - - /// Withdraw collateral as native currency - function withdrawNative(uint256 amount_) external virtual onlyFinanceOwner { - deposit -= amount_; - IInfluencer(influencer).withdraw(amount_); - // unwrap collateral - IWETH(WETH).withdraw(amount_); - // send withdrawn native currency - TransferHelper.safeTransferETH(msg.sender, address(this).balance); - emit WithdrawFundNative(financeId, amount_); - } - - receive() external payable { - assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract - } -} diff --git a/assets/eip-5252/contracts/Manager.sol b/assets/eip-5252/contracts/Manager.sol deleted file mode 100644 index 3b1142c497ef9d..00000000000000 --- a/assets/eip-5252/contracts/Manager.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "./interfaces/IWETH.sol"; -import "./interfaces/IManager.sol"; -import "./interfaces/IFactory.sol"; -import "./interfaces/IERC20Minimal.sol"; - -contract Manager is AccessControl, IManager { - - // Configs - /// key: Collateral address, value: Liquidation Fee Ratio (LFR) in percent(%) with 5 decimal precision(100.00000%) - mapping (address => uint) internal ExampleConfig; - - address public override factory; - - constructor() { - _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); - } - - function initializeConfig(address something, uint example) public { - require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "IA"); // Invalid Access - ExampleConfig[something] = example; - emit ConfigInitialized(something, example); - } - - function initialize(address stablecoin_, address factory_, address liquidator_) public { - require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "IA"); // Invalid Access - factory = factory_; - } - - function createFinanceNative(uint amount_) payable public returns(bool success) { - address WETH = IFactory(factory).WETH(); - // check validity - - // create vault - (address vlt, uint256 id) = IFactory(factory).createFinance(WETH, amount_, _msgSender()); - require(vlt != address(0), "VAULTMANAGER: FE"); // Factory error - // wrap native currency - IWETH(WETH).deposit{value: address(this).balance}(); - uint256 weth = IERC20Minimal(WETH).balanceOf(address(this)); - // then transfer collateral native currency to the finance contract, manage collateral from there. - require(IWETH(WETH).transfer(vlt, weth)); - emit FinanceCreated(id, WETH, msg.sender, vlt, msg.value); - return true; - } - - - function getExampleConfig(address something) external view override returns (uint) { - return ExampleConfig[something]; - } -} - diff --git a/assets/eip-5252/contracts/governance/Governor.sol b/assets/eip-5252/contracts/governance/Governor.sol deleted file mode 100644 index 5d06e65b2863cc..00000000000000 --- a/assets/eip-5252/contracts/governance/Governor.sol +++ /dev/null @@ -1,109 +0,0 @@ -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/governance/Governor.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; -import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; -import "../interfaces/IInfluencer.sol"; -import "@openzeppelin/contracts/governance/utils/IVotes.sol"; - -contract MyGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorTimelockControl { - constructor(IVotes _token, TimelockController _timelock) - Governor("MyGovernor") - GovernorSettings(1 /* 1 block */, 45818 /* 1 week */, 0) - GovernorVotes(_token) - GovernorVotesQuorumFraction(4) - GovernorTimelockControl(_timelock) - {} - - // The following functions are overrides required by Solidity. - - function votingDelay() - public - view - override(IGovernor, GovernorSettings) - returns (uint256) - { - return super.votingDelay(); - } - - function votingPeriod() - public - view - override(IGovernor, GovernorSettings) - returns (uint256) - { - return super.votingPeriod(); - } - - function quorum(uint256 blockNumber) - public - view - override(IGovernor, GovernorVotesQuorumFraction) - returns (uint256) - { - return super.quorum(blockNumber); - } - - function state(uint256 proposalId) - public - view - override(Governor, GovernorTimelockControl) - returns (ProposalState) - { - return super.state(proposalId); - } - - function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description) - public - override(Governor, IGovernor) - returns (uint256) - { - // check if sender is enforcer - return super.propose(targets, values, calldatas, description); - } - - function proposalThreshold() - public - view - override(Governor, GovernorSettings) - returns (uint256) - { - return super.proposalThreshold(); - } - - function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) - internal - override(Governor, GovernorTimelockControl) - { - super._execute(proposalId, targets, values, calldatas, descriptionHash); - } - - function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) - internal - override(Governor, GovernorTimelockControl) - returns (uint256) - { - return super._cancel(targets, values, calldatas, descriptionHash); - } - - function _executor() - internal - view - override(Governor, GovernorTimelockControl) - returns (address) - { - return super._executor(); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(Governor, GovernorTimelockControl) - returns (bool) - { - return super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5252/contracts/governance/Influencer.sol b/assets/eip-5252/contracts/governance/Influencer.sol deleted file mode 100644 index d34000f4c4d14b..00000000000000 --- a/assets/eip-5252/contracts/governance/Influencer.sol +++ /dev/null @@ -1,44 +0,0 @@ -pragma solidity ^0.8.0; - -import "../interfaces/IABT.sol"; -import "../interfaces/IFactory.sol"; -import "../interfaces/IFinance.sol"; -import "../interfaces/IERC20Minimal.sol"; - -contract Influencer { - - uint256 totalContributionValue; - - mapping(string => Weight) weights; - - struct Weight { - uint256 percentage; - uint256 decimal; - } - - function getInfluence(address abt_, uint256 id_) public returns (uint multiplier) { - return _getInfluence(abt_, id_); - } - - - function _getInfluence(address abt_, uint256 id_) internal returns (uint influence) { - // get Finance address - address factory = IABT(abt_).factory(); - address finance = IFactory(factory).getFinance(id_); - address WETH = IFinance(finance).WETH(); - // normalize finance value - uint256 norm_alpha = IERC20Minimal(WETH).balanceOf(finance) / totalContributionValue * 100; - uint256 norm_beta = block.timestamp - IFinance(finance).createdAt() / block.timestamp * 100; - - // Divide with each decimal - uint256 influence_dec = weights["alpha"].percentage * norm_alpha + weights["beta"].percentage * norm_beta; - return influence_dec / weights["alpha"].decimal / weights["beta"].decimal; - } - - function setWeight(string memory key, uint256 percentage, uint256 decimal) public { - weights[key] = Weight({ - percentage: percentage, - decimal: decimal - }); - } -} \ No newline at end of file diff --git a/assets/eip-5252/contracts/governance/Vote.sol b/assets/eip-5252/contracts/governance/Vote.sol deleted file mode 100644 index 003ebb1c6674a6..00000000000000 --- a/assets/eip-5252/contracts/governance/Vote.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.2; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; -import "@openzeppelin/contracts/governance/Governor.sol"; -import "../interfaces/IInfluencer.sol"; -import "../interfaces/IABT.sol"; - -contract MyToken is ERC20, ERC20Permit, ERC20Votes, Governor { - constructor() ERC20("GovToken", "GOV") ERC20Permit("Governance Token") {} - - mapping(address => uint256[]) private _multiplier; - address public influencer; - - // The functions below are overrides required by Solidity. - - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal override(ERC20, ERC20Votes) { - super._afterTokenTransfer(from, to, amount); - } - - function _mint(address to, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._mint(to, amount); - } - - function _burn(address account, uint256 amount) - internal - override(ERC20, ERC20Votes) - { - super._burn(account, amount); - } - - function _sqrt(uint256 x) internal returns (uint256 y) { - uint256 z = (x + 1) / 2; - y = x; - while (z < y) { - y = z; - z = (x / z + z) / 2; - } - } - - function getVotes(address account) - public - view - virtual - override - returns (uint256) - { - uint256 pos = _checkpoints[account].length; - uint256 vote = pos == 0 ? 0 : _checkpoints[account][pos - 1].votes; - // 0 as None, Multiplied with - uint256 multiplied = _multiplier[pos - 1] > 0 - ? _sqrt(vote) - : _sqrt(vote * _multiplier[pos - 1]); - return multiplied; - } - - function mulInfluence(address abt, uint256 id) public { - require(IABT(abt).ownerOf(id) == msg.sender, "Vote: not abt owner"); - uint256 pos = _checkpoints[msg.sender].length; - _multiplier[pos - 1] = IInfluencer.getInfluence(abt, id); - } - - function setInfluencer(address influencer_) public onlyGovernance { - influencer = influencer_; - } -} diff --git a/assets/eip-5252/contracts/interfaces/IABT.sol b/assets/eip-5252/contracts/interfaces/IABT.sol deleted file mode 100644 index e6b3d5fa69a0b5..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IABT.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IABT { - function mint(address to) external; - function burn(uint256 tokenId_) external; - function exists(uint256 tokenId_) external view returns (bool); - function ownerOf(uint256 tokenId) external view returns (address owner); - function factory() external view returns (address factory); -} diff --git a/assets/eip-5252/contracts/interfaces/IDescriptor.sol b/assets/eip-5252/contracts/interfaces/IDescriptor.sol deleted file mode 100644 index eecf26858e3b67..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IDescriptor.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity ^0.8.0; - -interface IDescriptor { - function tokenURI(uint256 tokenId) external view returns (string memory); -} diff --git a/assets/eip-5252/contracts/interfaces/IERC20Minimal.sol b/assets/eip-5252/contracts/interfaces/IERC20Minimal.sol deleted file mode 100644 index 5e58531d096013..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IERC20Minimal.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.5.0; - -interface IERC20Minimal { - function totalSupply() external view returns (uint); - function balanceOf(address owner) external view returns (uint); - function decimals() external view returns (uint8); -} diff --git a/assets/eip-5252/contracts/interfaces/IFactory.sol b/assets/eip-5252/contracts/interfaces/IFactory.sol deleted file mode 100644 index e764bc1b06cd05..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IFactory.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IFactory { - - /// View funcs - /// NFT token address - function abt() external view returns (address); - /// Address of wrapped eth - function WETH() external view returns (address); - /// Address of a manager - function manager() external view returns (address); - - /// Getters - /// Get Config of CDP - function financeCodeHash() external pure returns (bytes32); - function createFinance(address weth, uint256 amount_, address recipient) external returns (address vault, uint256 id); - function getFinance(uint financeId_) external view returns (address); - - /// Event - event FinanceCreated(uint256 vaultId, address collateral, address debt, address creator, address vault, uint256 cAmount, uint256 dAmount); -} diff --git a/assets/eip-5252/contracts/interfaces/IFinance.sol b/assets/eip-5252/contracts/interfaces/IFinance.sol deleted file mode 100644 index 5f9749c926b4d9..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IFinance.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IFinance { - event DepositFundNative(uint256 vaultID, uint256 amount); - event WithdrawFundNative(uint256 vaultID, uint256 amount); - /// Getters - /// Address of a factory - function factory() external view returns (address); - /// Address of a manager - function manager() external view returns (address); - function influencer() external view returns (address); - /// Address of account bound token - function abt() external view returns (address); - /// Finance global identifier - function financeId() external view returns (uint256); - /// Finance Last Updated Date - function lastUpdated() external view returns (uint256); - /// Finance creation date - function createdAt() external view returns (uint256); - /// address of wrapped eth - function WETH() external view returns (address); - /// deposit amount of finance account - function deposit() external view returns (uint256); - - /// Functions - function initialize( - address manager_, - uint256 financeId_, - address abt_, - uint256 amount_, - address weth_ - ) external; - -} diff --git a/assets/eip-5252/contracts/interfaces/IInfluencer.sol b/assets/eip-5252/contracts/interfaces/IInfluencer.sol deleted file mode 100644 index c732c48164c7c6..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IInfluencer.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.5.0; - -interface IInfluencer { - function isEnforcer(address sender) external; -} diff --git a/assets/eip-5252/contracts/interfaces/IManager.sol b/assets/eip-5252/contracts/interfaces/IManager.sol deleted file mode 100644 index 3f7bfa14812d10..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IManager.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity ^0.8.0; - -interface IManager { - function factory() external view returns (address); - function influencer() external view returns (address); - function getExampleConfig(address something) external view returns (uint); - event ConfigInitialized(address something, uint example); - event FinanceCreated(uint id, address weth, address sender, address finance, uint input); -} diff --git a/assets/eip-5252/contracts/interfaces/IWETH.sol b/assets/eip-5252/contracts/interfaces/IWETH.sol deleted file mode 100644 index 6cf78eaabdbe41..00000000000000 --- a/assets/eip-5252/contracts/interfaces/IWETH.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity >=0.5.0; - -interface IWETH { - function deposit() external payable; - function transfer(address to, uint value) external returns (bool); - function withdraw(uint) external; -} diff --git a/assets/eip-5252/contracts/libraries/CloneFactory.sol b/assets/eip-5252/contracts/libraries/CloneFactory.sol deleted file mode 100644 index 911e6b5b75c6cb..00000000000000 --- a/assets/eip-5252/contracts/libraries/CloneFactory.sol +++ /dev/null @@ -1,82 +0,0 @@ -library CloneFactory { - function _createClone(address target) internal returns (address result) { - // convert address to 20 bytes - bytes20 targetBytes = bytes20(target); - - // actual code // - // 3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3 - - // creation code // - // copy runtime code into memory and return it - // 3d602d80600a3d3981f3 - - // runtime code // - // code to delegatecall to address - // 363d3d373d3d3d363d73 address 5af43d82803e903d91602b57fd5bf3 - - assembly { - /* - reads the 32 bytes of memory starting at pointer stored in 0x40 - - In solidity, the 0x40 slot in memory is special: it contains the "free memory pointer" - which points to the end of the currently allocated memory. - */ - let clone := mload(0x40) - // store 32 bytes to memory starting at "clone" - mstore( - clone, - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 - ) - - /* - | 20 bytes | - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000 - ^ - pointer - */ - // store 32 bytes to memory starting at "clone" + 20 bytes - // 0x14 = 20 - mstore(add(clone, 0x14), targetBytes) - - /* - | 20 bytes | 20 bytes | - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe - ^ - pointer - */ - // store 32 bytes to memory starting at "clone" + 40 bytes - // 0x28 = 40 - mstore( - add(clone, 0x28), - 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000 - ) - - /* - | 20 bytes | 20 bytes | 15 bytes | - 0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3 - */ - // create new contract - // send 0 Ether - // code starts at pointer stored in "clone" - // code size 0x37 (55 bytes) - result := create(0, clone, 0x37) - } - } - - function _isClone(address target, address query) internal view returns (bool result) { - bytes20 targetBytes = bytes20(target); - assembly { - let clone := mload(0x40) - mstore(clone, 0x363d3d373d3d3d363d7300000000000000000000000000000000000000000000) - mstore(add(clone, 0xa), targetBytes) - mstore(add(clone, 0x1e), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) - - let other := add(clone, 0x40) - extcodecopy(query, other, 0, 0x2d) - result := and( - eq(mload(clone), mload(other)), - eq(mload(add(clone, 0xd)), mload(add(other, 0xd))) - ) - } - } -} diff --git a/assets/eip-5252/contracts/libraries/Initializable.sol b/assets/eip-5252/contracts/libraries/Initializable.sol deleted file mode 100644 index 55a39786399780..00000000000000 --- a/assets/eip-5252/contracts/libraries/Initializable.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -contract Initializable { - bool private _initialized = false; - - modifier initializer() { - // solhint-disable-next-line reason-string - require(!_initialized); - _; - _initialized = true; - } - - function initialized() external view returns (bool) { - return _initialized; - } -} diff --git a/assets/eip-5252/contracts/libraries/SVG.sol b/assets/eip-5252/contracts/libraries/SVG.sol deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/assets/eip-5252/contracts/libraries/TransferHelper.sol b/assets/eip-5252/contracts/libraries/TransferHelper.sol deleted file mode 100644 index 550db796334d85..00000000000000 --- a/assets/eip-5252/contracts/libraries/TransferHelper.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false -library TransferHelper { - function safeApprove(address token, address to, uint value) internal { - // bytes4(keccak256(bytes("approve(address,uint256)"))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "AF"); - } - - 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))), "TF"); - } - - function safeTransferFrom(address token, address from, address to, uint value) internal { - // bytes4(keccak256(bytes("transferFrom(address,address,uint256)"))); - (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value)); - require(success && (data.length == 0 || abi.decode(data, (bool))), "TFF"); - } - - function safeTransferETH(address to, uint value) internal { - (bool success,) = to.call{value:value}(new bytes(0)); - require(success, "ETF"); - } -} diff --git a/assets/eip-5252/hardhat.config.ts b/assets/eip-5252/hardhat.config.ts deleted file mode 100644 index 414e974b9574b9..00000000000000 --- a/assets/eip-5252/hardhat.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; - -const config: HardhatUserConfig = { - solidity: "0.8.9", -}; - -export default config; diff --git a/assets/eip-5252/media/media.svg b/assets/eip-5252/media/media.svg deleted file mode 100644 index c74c943c9f941d..00000000000000 --- a/assets/eip-5252/media/media.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/eip-5252/package.json b/assets/eip-5252/package.json deleted file mode 100644 index af7ea0c2593e39..00000000000000 --- a/assets/eip-5252/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "hardhat-project", - "devDependencies": { - "@nomicfoundation/hardhat-toolbox": "^1.0.2", - "hardhat": "^2.10.1", - "ts-node": "^10.9.1", - "typescript": "^4.7.4" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.7.1" - } -} diff --git a/assets/eip-5252/scripts/deploy.ts b/assets/eip-5252/scripts/deploy.ts deleted file mode 100644 index 90e8908a29a0d6..00000000000000 --- a/assets/eip-5252/scripts/deploy.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ethers } from "hardhat"; - -async function main() { - const currentTimestampInSeconds = Math.round(Date.now() / 1000); - const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; - const unlockTime = currentTimestampInSeconds + ONE_YEAR_IN_SECS; - - const lockedAmount = ethers.utils.parseEther("1"); - - const Lock = await ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); - - await lock.deployed(); - - console.log("Lock with 1 ETH deployed to:", lock.address); -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/assets/eip-5252/test/Lock.ts b/assets/eip-5252/test/Lock.ts deleted file mode 100644 index 3127221fc4217b..00000000000000 --- a/assets/eip-5252/test/Lock.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("Lock", function () { - // We define a fixture to reuse the same setup in every test. - // We use loadFixture to run this setup once, snapshot that state, - // and reset Hardhat Network to that snapshopt in every test. - async function deployOneYearLockFixture() { - const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; - const ONE_GWEI = 1_000_000_000; - - const lockedAmount = ONE_GWEI; - const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; - - // Contracts are deployed using the first signer/account by default - const [owner, otherAccount] = await ethers.getSigners(); - - const Lock = await ethers.getContractFactory("Lock"); - const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); - - return { lock, unlockTime, lockedAmount, owner, otherAccount }; - } - - describe("Deployment", function () { - it("Should set the right unlockTime", async function () { - const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); - - expect(await lock.unlockTime()).to.equal(unlockTime); - }); - - it("Should set the right owner", async function () { - const { lock, owner } = await loadFixture(deployOneYearLockFixture); - - expect(await lock.owner()).to.equal(owner.address); - }); - - it("Should receive and store the funds to lock", async function () { - const { lock, lockedAmount } = await loadFixture( - deployOneYearLockFixture - ); - - expect(await ethers.provider.getBalance(lock.address)).to.equal( - lockedAmount - ); - }); - - it("Should fail if the unlockTime is not in the future", async function () { - // We don't use the fixture here because we want a different deployment - const latestTime = await time.latest(); - const Lock = await ethers.getContractFactory("Lock"); - await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( - "Unlock time should be in the future" - ); - }); - }); - - describe("Withdrawals", function () { - describe("Validations", function () { - it("Should revert with the right error if called too soon", async function () { - const { lock } = await loadFixture(deployOneYearLockFixture); - - await expect(lock.withdraw()).to.be.revertedWith( - "You can't withdraw yet" - ); - }); - - it("Should revert with the right error if called from another account", async function () { - const { lock, unlockTime, otherAccount } = await loadFixture( - deployOneYearLockFixture - ); - - // We can increase the time in Hardhat Network - await time.increaseTo(unlockTime); - - // We use lock.connect() to send a transaction from another account - await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( - "You aren't the owner" - ); - }); - - it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { - const { lock, unlockTime } = await loadFixture( - deployOneYearLockFixture - ); - - // Transactions are sent using the first signer by default - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()).not.to.be.reverted; - }); - }); - - describe("Events", function () { - it("Should emit an event on withdrawals", async function () { - const { lock, unlockTime, lockedAmount } = await loadFixture( - deployOneYearLockFixture - ); - - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()) - .to.emit(lock, "Withdrawal") - .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg - }); - }); - - describe("Transfers", function () { - it("Should transfer the funds to the owner", async function () { - const { lock, unlockTime, lockedAmount, owner } = await loadFixture( - deployOneYearLockFixture - ); - - await time.increaseTo(unlockTime); - - await expect(lock.withdraw()).to.changeEtherBalances( - [owner, lock], - [lockedAmount, -lockedAmount] - ); - }); - }); - }); -}); diff --git a/assets/eip-5252/tsconfig.json b/assets/eip-5252/tsconfig.json deleted file mode 100644 index e5f1a64007abf2..00000000000000 --- a/assets/eip-5252/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true - } -} diff --git a/assets/eip-5269/contracts/ERC5269.sol b/assets/eip-5269/contracts/ERC5269.sol deleted file mode 100644 index 4d89b471c9767e..00000000000000 --- a/assets/eip-5269/contracts/ERC5269.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code - -pragma solidity ^0.8.9; - -import "./IERC5269.sol"; - -contract ERC5269 is IERC5269 { - bytes32 constant public EIP_STATUS = keccak256("DRAFTv1"); - constructor () { - emit OnSupportEIP(address(0x0), 5269, bytes32(0), EIP_STATUS, ""); - } - - function _supportEIP( - address /*caller*/, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata /*extraData*/) - internal virtual view returns (bytes32 eipStatus) { - if (majorEIPIdentifier == 5269) { - if (minorEIPIdentifier == bytes32(0)) { - return EIP_STATUS; - } - } - return bytes32(0); - } - - function supportEIP( - address caller, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata extraData) - external virtual view returns (bytes32 eipStatus) { - return _supportEIP(caller, majorEIPIdentifier, minorEIPIdentifier, extraData); - } -} diff --git a/assets/eip-5269/contracts/IERC5269.sol b/assets/eip-5269/contracts/IERC5269.sol deleted file mode 100644 index 8b444549578895..00000000000000 --- a/assets/eip-5269/contracts/IERC5269.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code -pragma solidity ^0.8.9; - -interface IERC5269 { - event OnSupportEIP( - address indexed caller, // when emitted with `address(0x0)` means all callers. - uint256 indexed majorEIPIdentifier, - bytes32 indexed minorEIPIdentifier, // 0 means the entire EIP - bytes32 eipStatus, - bytes extraData - ); - - /// @dev The core method of EIP/ERC Interface Detection - /// @param caller, a `address` value of the address of a caller being queried whether the given EIP is supported. - /// @param majorEIPIdentifier, a `uint256` value and SHOULD BE the EIP number being queried. Unless superseded by future EIP, such EIP number SHOULD BE less or equal to (0, 2^32-1]. For a function call to `supportEIP`, any value outside of this range is deemed unspecified and open to implementation's choice or for future EIPs to specify. - /// @param minorEIPIdentifier, a `bytes32` value reserved for authors of individual EIP to specify. For example the author of [EIP-721](/EIPS/eip-721) MAY specify `keccak256("ERC721Metadata")` or `keccak256("ERC721Metadata.tokenURI")` as `minorEIPIdentifier` to be quired for support. Author could also use this minorEIPIdentifier to specify different versions, such as EIP-712 has its V1-V4 with different behavior. - /// @param extraData, a `bytes` for [EIP-5750](/EIPS/eip-5750) for future extensions. - /// @return eipStatus a bytes32 indicating the status of EIP the contract supports. - /// - For FINAL EIPs, it MUST return `keccak256("FINAL")`. - /// - For non-FINAL EIPs, it SHOULD return `keccak256("DRAFT")`. - /// During EIP procedure, EIP authors are allowed to specify their own - /// eipStatus other than `FINAL` or `DRAFT` at their discretion such as `keccak256("DRAFTv1")` - /// or `keccak256("DRAFT-option1")`and such value of eipStatus MUST be documented in the EIP body - function supportEIP( - address caller, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata extraData) - external view returns (bytes32 eipStatus); -} diff --git a/assets/eip-5269/contracts/testing/ERC721ForTesting.sol b/assets/eip-5269/contracts/testing/ERC721ForTesting.sol deleted file mode 100644 index 22d21c469b9c4f..00000000000000 --- a/assets/eip-5269/contracts/testing/ERC721ForTesting.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code -pragma solidity ^0.8.9; -// import 721 -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -// impport 5269 -import "../ERC5269.sol"; - -contract ERC721ForTesting is ERC721, ERC5269 { - - bytes32 constant public EIP_FINAL = keccak256("FINAL"); - constructor() ERC721("ERC721ForTesting", "E721FT") ERC5269() { - _mint(msg.sender, 0); - emit OnSupportEIP(address(0x0), 721, bytes32(0), EIP_FINAL, ""); - emit OnSupportEIP(address(0x0), 721, keccak256("ERC721Metadata"), EIP_FINAL, ""); - emit OnSupportEIP(address(0x0), 721, keccak256("ERC721Enumerable"), EIP_FINAL, ""); - } - - function supportEIP( - address caller, - uint256 majorEIPIdentifier, - bytes32 minorEIPIdentifier, - bytes calldata extraData) - external - override - view - returns (bytes32 eipStatus) { - if (majorEIPIdentifier == 721) { - if (minorEIPIdentifier == 0) { - return keccak256("FINAL"); - } else if (minorEIPIdentifier == keccak256("ERC721Metadata")) { - return keccak256("FINAL"); - } else if (minorEIPIdentifier == keccak256("ERC721Enumerable")) { - return keccak256("FINAL"); - } - } - return super._supportEIP(caller, majorEIPIdentifier, minorEIPIdentifier, extraData); - } -} diff --git a/assets/eip-5269/test/TestERC5269.ts b/assets/eip-5269/test/TestERC5269.ts deleted file mode 100644 index b71635df22b2af..00000000000000 --- a/assets/eip-5269/test/TestERC5269.ts +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -// Author: Zainan Victor Zhou -// DRAFTv1 -// Source https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5269 -// Deployment https://goerli.etherscan.io/address/0x33F735852619E3f99E1AF069cCf3b9232b2806bE#code - -import { loadFixture, mine } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { BigNumber, ContractReceipt, Wallet } from "ethers"; -import { ethers } from "hardhat"; - -describe("ERC5269", function () { - async function deployFixture() { - // Contracts are deployed using the first signer/account by default - const [owner, mintSender, recipient] = await ethers.getSigners(); - const testWallet: Wallet = new ethers.Wallet("0x0000000000000000000000000000000000000000000000000000000000000001"); - - const factory = await ethers.getContractFactory("ERC5269"); - const contract = await factory.deploy(); - let tx1 = await contract.deployed(); - let txDeployErc5269: ContractReceipt = await tx1.deployTransaction.wait(); - - const ERC721ForTesting = await ethers.getContractFactory("ERC721ForTesting"); - const erc721ForTesting = await ERC721ForTesting.deploy(); - let tx2 = await erc721ForTesting.deployed(); - const txDeployErc721: ContractReceipt = await tx2.deployTransaction.wait(); - const provider = ethers.provider; - return { - provider, - contract, - erc721ForTesting, - tx1, txDeployErc5269, - tx2, txDeployErc721, - owner, mintSender, recipient, testWallet - }; - } - - describe("Deployment", function () { - it("Should be deployable", async function () { - await loadFixture(deployFixture); - }); - - it("Should emit proper OnSupportEIP events", async function () { - let { txDeployErc721 } = await loadFixture(deployFixture); - let events = txDeployErc721.events?.filter(event => event.event === 'OnSupportEIP'); - expect(events).to.have.lengthOf(4); - - let ev5269 = events!.filter( - (event) => event.args!.majorEIPIdentifier.eq(5269)); - expect(ev5269).to.have.lengthOf(1); - expect(ev5269[0].args!.caller).to.equal(BigNumber.from(0)); - expect(ev5269[0].args!.minorEIPIdentifier).to.equal(BigNumber.from(0)); - expect(ev5269[0].args!.eipStatus).to.equal(ethers.utils.id("DRAFTv1")); - - let ev721 = events!.filter( - (event) => event.args!.majorEIPIdentifier.eq(721)); - expect(ev721).to.have.lengthOf(3); - expect(ev721[0].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[0].args!.minorEIPIdentifier).to.equal(BigNumber.from(0)); - expect(ev721[0].args!.eipStatus).to.equal(ethers.utils.id("FINAL")); - - expect(ev721[1].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[1].args!.minorEIPIdentifier).to.equal(ethers.utils.id("ERC721Metadata")); - expect(ev721[1].args!.eipStatus).to.equal(ethers.utils.id("FINAL")); - - expect(ev721[2].args!.caller).to.equal(BigNumber.from(0)); - expect(ev721[2].args!.minorEIPIdentifier).to.equal(ethers.utils.id("ERC721Enumerable")); - expect(ev721[2].args!.eipStatus).to.equal(ethers.utils.id("FINAL")); - }); - - it("Should return proper eipStatus value when called supportEIP() for declared supported EIP/features", async function () { - let { erc721ForTesting, owner } = await loadFixture(deployFixture); - expect(await erc721ForTesting.supportEIP(owner.address, 5269, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("DRAFTv1")); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("FINAL")); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("ERC721Metadata"), [])).to.equal(ethers.utils.id("FINAL")); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("ERC721Enumerable"), [])).to.equal(ethers.utils.id("FINAL")); - - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0)); - expect(await erc721ForTesting.supportEIP(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0)); - }); - - it("Should return zero as eipStatus value when called supportEIP() for non declared EIP/features", async function () { - let { erc721ForTesting, owner } = await loadFixture(deployFixture); - expect(await erc721ForTesting.supportEIP(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0)); - expect(await erc721ForTesting.supportEIP(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0)); - }); - }); -}); diff --git a/assets/eip-5289/ERC5289Library.sol b/assets/eip-5289/ERC5289Library.sol deleted file mode 100644 index 04bb6c72d8ffbb..00000000000000 --- a/assets/eip-5289/ERC5289Library.sol +++ /dev/null @@ -1,42 +0,0 @@ -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interfaces/IERC165.sol"; -import "./interfaces/IERC5289Library.sol"; - -contract ERC5289Library is IERC165, IERC5289Library { - uint16 private counter = 0; - mapping(uint16 => string) private uris; - mapping(uint16 => mapping(address => uint64)) signedAt; - - constructor() { } - - function registerDocument(string memory uri) public returns (uint16) { - uris[counter] = uri; - return counter++; - } - - function legalDocument(uint16 documentId) public view returns (string uri) { - return uris[documentId]; - } - - function documentSigned(address user, uint16 documentId) public view returns (bool isSigned) { - return signedAt[documentId][user] != 0; - } - - function documentSignedAt(address user, uint16 documentId) public view returns (uint64 timestamp) { - return signedAt[documentId][user]; - } - - function signDocument(address signer, uint16 documentId) public { - require(signer == msg.sender, "invalid user"); - - signedAt[documentId][msg.sender] = uint64(block.timestamp); - - emit DocumentSigned(msg.sender, documentId); - } - - function supportsInterface(bytes4 _interfaceId) public view returns (bool) { - return _interfaceId == type(IERC5289Library).interfaceId; - } -} diff --git a/assets/eip-5289/example-popup.png b/assets/eip-5289/example-popup.png deleted file mode 100644 index 957bb6ba315232..00000000000000 Binary files a/assets/eip-5289/example-popup.png and /dev/null differ diff --git a/assets/eip-5289/interfaces/IERC165.sol b/assets/eip-5289/interfaces/IERC165.sol deleted file mode 100644 index 3e6ec8ea3e222e..00000000000000 --- a/assets/eip-5289/interfaces/IERC165.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} diff --git a/assets/eip-5289/interfaces/IERC5289Library.sol b/assets/eip-5289/interfaces/IERC5289Library.sol deleted file mode 100644 index 6eb68b152beb9a..00000000000000 --- a/assets/eip-5289/interfaces/IERC5289Library.sol +++ /dev/null @@ -1,23 +0,0 @@ -/// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC165.sol"; - -interface IERC5289Library is IERC165 { - /// @notice Emitted when signDocument is called - event DocumentSigned(address indexed signer, uint16 indexed documentId); - - /// @notice An immutable link to the legal document (RECOMMENDED to be hosted on IPFS). This MUST use a common file format, such as PDF, HTML, TeX, or Markdown. - function legalDocument(uint16 documentId) external view returns (string memory); - - /// @notice Returns whether or not the given user signed the document. - function documentSigned(address user, uint16 documentId) external view returns (bool signed); - - /// @notice Returns when the the given user signed the document. - /// @dev If the user has not signed the document, the timestamp may be anything. - function documentSignedAt(address user, uint16 documentId) external view returns (uint64 timestamp); - - /// @notice Sign a document - /// @dev This MUST be validated by the smart contract. This MUST emit DocumentSigned or throw. - function signDocument(address signer, uint16 documentId) external; -} diff --git a/assets/eip-5334/ERC5334.sol b/assets/eip-5334/ERC5334.sol deleted file mode 100644 index b6a79acaf3a4fe..00000000000000 --- a/assets/eip-5334/ERC5334.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5334.sol"; - -contract ERC5334 is ERC721, IERC5334 { - struct UserInfo - { - address user; // address of user role - uint64 expires; // unix timestamp, user expires - uint8 level; // user level - } - - mapping (uint256 => UserInfo) internal _users; - - constructor(string memory name_, string memory symbol_) - ERC721(name_,symbol_) - { - } - - /// @notice set the user and expires and level of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - /// @param level user level - function setUser(uint256 tokenId, address user, uint64 expires, uint8 level) public virtual{ - require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: transfer caller is not owner nor approved"); - UserInfo storage info = _users[tokenId]; - info.user = user; - info.expires = expires; - info.level = level - emit UpdateUser(tokenId,user,expires,level); - } - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId)public view virtual returns(address){ - if( uint256(_users[tokenId].expires) >= block.timestamp){ - return _users[tokenId].user; - } - else{ - return address(0); - } - } - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) public view virtual returns(uint256){ - return _users[tokenId].expires; - } - - /// @notice Get the user level of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user level for - /// @return The user level for this NFT - function userLevel(uint256 tokenId) public view virtual returns(uint256){ - return _users[tokenId].level; - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC5334).interfaceId || super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override{ - super._beforeTokenTransfer(from, to, tokenId); - - if (from != to && _users[tokenId].user != address(0)) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0, 0); - } - } -} - diff --git a/assets/eip-5334/IERC5334.sol b/assets/eip-5334/IERC5334.sol deleted file mode 100644 index 0adc386f3f241d..00000000000000 --- a/assets/eip-5334/IERC5334.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5334 { - // Logged when the user of a token assigns a new user or updates expires - /// @notice Emitted when the `user` of an NFT or the `expires` of the `user` is changed or the `level` of the `user` is changed - /// The zero address for user indicates that there is no user address - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires, uint8 level); - - /// @notice set the user and expires of a NFT - /// @dev The zero address indicates there is no user - /// Throws if `tokenId` is not valid NFT - /// @param user The new user of the NFT - /// @param expires UNIX timestamp, The new user could use the NFT before expires - /// @param level user level - function setUser(uint256 tokenId, address user, uint64 expires, uint8 level) external ; - - /// @notice Get the user address of an NFT - /// @dev The zero address indicates that there is no user or the user is expired - /// @param tokenId The NFT to get the user address for - /// @return The user address for this NFT - function userOf(uint256 tokenId) external view returns(address); - - /// @notice Get the user expires of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId) external view returns(uint256); - - /// @notice Get the user level of an NFT - /// @dev The zero value indicates that there is no user - /// @param tokenId The NFT to get the user level for - /// @return The user level for this NFT - function userLevel(uint256 tokenId) external view returns(uint256); -} diff --git a/assets/eip-5453/AERC5453.sol b/assets/eip-5453/AERC5453.sol deleted file mode 100644 index f3b4d762a68669..00000000000000 --- a/assets/eip-5453/AERC5453.sol +++ /dev/null @@ -1,271 +0,0 @@ -// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; - -import "./IERC5453.sol"; - -abstract contract AERC5453Endorsible is EIP712, - IERC5453EndorsementCore, IERC5453EndorsementDigest, IERC5453EndorsementDataTypeA, IERC5453EndorsementDataTypeB { - uint256 private threshold; - uint256 private currentNonce = 0; - bytes32 constant MAGIC_WORLD = keccak256("ERC5453-ENDORSEMENT"); // ASCII of "ENDORSED" - uint256 constant ERC5453_TYPE_A = 1; - uint256 constant ERC5453_TYPE_B = 2; - - constructor( - string memory _name, - string memory _erc721Version - ) EIP712(_name, _erc721Version) {} - - function _validate( - bytes32 msgDigest, - SingleEndorsementData memory endersement - ) internal virtual { - require( - endersement.sig.length == 65, - "AERC5453Endorsible: wrong signature length" - ); - require( - SignatureChecker.isValidSignatureNow( - endersement.endorserAddress, - msgDigest, - endersement.sig - ), - "AERC5453Endorsible: invalid signature" - ); - } - - function _extractEndorsers( - bytes32 digest, - GeneralExtensionDataStruct memory data - ) internal virtual returns (address[] memory endorsers) { - require( - data.erc5453MagicWord == MAGIC_WORLD, - "AERC5453Endorsible: MagicWord not matched" - ); - require( - data.validSince <= block.number, - "AERC5453Endorsible: Not valid yet" - ); // TODO consider per-Endorser validSince - require(data.validBy >= block.number, "AERC5453Endorsible: Expired"); // TODO consider per-Endorser validBy - require( - currentNonce == data.nonce, - "AERC5453Endorsible: Nonce not matched" - ); // TODO consider per-Endorser nonce or range of nonce - currentNonce += 1; - - if (data.erc5453Type == ERC5453_TYPE_A) { - SingleEndorsementData memory endersement = abi.decode( - data.endorsementPayload, - (SingleEndorsementData) - ); - endorsers = new address[](1); - endorsers[0] = endersement.endorserAddress; - _validate(digest, endersement); - } else if (data.erc5453Type == ERC5453_TYPE_B) { - SingleEndorsementData[] memory endorsements = abi.decode( - data.endorsementPayload, - (SingleEndorsementData[]) - ); - endorsers = new address[](endorsements.length); - for (uint256 i = 0; i < endorsements.length; ++i) { - endorsers[i] = endorsements[i].endorserAddress; - _validate(digest, endorsements[i]); - } - return endorsers; - } - } - - function _decodeExtensionData( - bytes memory extensionData - ) internal pure virtual returns (GeneralExtensionDataStruct memory) { - return abi.decode(extensionData, (GeneralExtensionDataStruct)); - } - - // Well, I know this is epensive. Let's improve it later. - function _noRepeat(address[] memory _owners) internal pure returns (bool) { - for (uint256 i = 0; i < _owners.length; i++) { - for (uint256 j = i + 1; j < _owners.length; j++) { - if (_owners[i] == _owners[j]) { - return false; - } - } - } - return true; - } - - function _isEndorsed( - bytes32 _functionParamStructHash, - bytes calldata _extraData - ) internal returns (bool) { - GeneralExtensionDataStruct memory _data = _decodeExtensionData( - _extraData - ); - bytes32 finalDigest = _computeValidityDigest( - _functionParamStructHash, - _data.validSince, - _data.validBy, - _data.nonce - ); - - address[] memory endorsers = _extractEndorsers(finalDigest, _data); - require( - endorsers.length >= threshold, - "AERC5453Endorsable: not enough endorsers" - ); - require(_noRepeat(endorsers)); - for (uint256 i = 0; i < endorsers.length; i++) { - require( - _isEligibleEndorser(endorsers[i]), - "AERC5453Endorsable: not eligible endorsers" - ); // everyone must be a legit endorser - } - return true; - } - - function _isEligibleEndorser( - address /*_endorser*/ - ) internal view virtual returns (bool); - - modifier onlyEndorsed( - bytes32 _functionParamStructHash, - bytes calldata _extensionData - ) { - require(_isEndorsed(_functionParamStructHash, _extensionData)); - _; - } - - function _computeValidityDigest( - bytes32 _functionParamStructHash, - uint256 _validSince, - uint256 _validBy, - uint256 _nonce - ) internal view returns (bytes32) { - return - super._hashTypedDataV4( - keccak256( - abi.encode( - keccak256( - "ValidityBound(bytes32 functionParamStructHash,uint256 validSince,uint256 validBy,uint256 nonce)" - ), - _functionParamStructHash, - _validSince, - _validBy, - _nonce - ) - ) - ); - } - - function _computeFunctionParamHash( - string memory _functionStructure, - bytes memory _functionParamPacked - ) internal pure returns (bytes32) { - bytes32 functionParamStructHash = keccak256( - abi.encodePacked( - keccak256(bytes(_functionStructure)), - _functionParamPacked - ) - ); - return functionParamStructHash; - } - - function _setThreshold(uint256 _threshold) internal virtual { - threshold = _threshold; - } - - function computeValidityDigest( - bytes32 _functionParamStructHash, - uint256 _validSince, - uint256 _validBy, - uint256 _nonce - ) external view override returns (bytes32) { - return - _computeValidityDigest( - _functionParamStructHash, - _validSince, - _validBy, - _nonce - ); - } - - function computeFunctionParamHash( - string memory _functionName, - bytes memory _functionParamPacked - ) external pure override returns (bytes32) { - return - _computeFunctionParamHash( - _functionName, - _functionParamPacked - ); - } - - function eip5453Nonce(address addr) external view override returns (uint256) { - require(address(this) == addr, "AERC5453Endorsable: not self"); - return currentNonce; - } - - function isEligibleEndorser(address _endorser) - external - view - override - returns (bool) - { - return _isEligibleEndorser(_endorser); - } - - function computeExtensionDataTypeA( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address endorserAddress, - bytes calldata sig - ) external pure override returns (bytes memory) { - return - abi.encode( - GeneralExtensionDataStruct( - MAGIC_WORLD, - ERC5453_TYPE_A, - nonce, - validSince, - validBy, - abi.encode(SingleEndorsementData(endorserAddress, sig)) - ) - ); - } - - function computeExtensionDataTypeB( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address[] calldata endorserAddress, - bytes[] calldata sigs - ) external pure override returns (bytes memory) { - require(endorserAddress.length == sigs.length); - SingleEndorsementData[] - memory endorsements = new SingleEndorsementData[]( - endorserAddress.length - ); - for (uint256 i = 0; i < endorserAddress.length; ++i) { - endorsements[i] = SingleEndorsementData( - endorserAddress[i], - sigs[i] - ); - } - return - abi.encode( - GeneralExtensionDataStruct( - MAGIC_WORLD, - ERC5453_TYPE_B, - nonce, - validSince, - validBy, - abi.encode(endorsements) - ) - ); - } -} diff --git a/assets/eip-5453/EndorsableERC721.sol b/assets/eip-5453/EndorsableERC721.sol deleted file mode 100644 index 3299ef8fac527d..00000000000000 --- a/assets/eip-5453/EndorsableERC721.sol +++ /dev/null @@ -1,47 +0,0 @@ -/// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -import "./AERC5453.sol"; - -contract EndorsableERC721 is ERC721, AERC5453Endorsible { - mapping(address => bool) private owners; - - constructor() - ERC721("ERC721ForTesting", "ERC721ForTesting") - AERC5453Endorsible("EndorsableERC721", "v1") - { - owners[msg.sender] = true; - } - - function addOwner(address _owner) external { - require(owners[msg.sender], "EndorsableERC721: not owner"); - owners[_owner] = true; - } - - function mint( - address _to, - uint256 _tokenId, - bytes calldata _extraData - ) - external - onlyEndorsed( - _computeFunctionParamHash( - "function mint(address _to,uint256 _tokenId)", - abi.encode(_to, _tokenId) - ), - _extraData - ) - { - _mint(_to, _tokenId); - } - - function _isEligibleEndorser( - address _endorser - ) internal view override returns (bool) { - return owners[_endorser]; - } -} diff --git a/assets/eip-5453/IERC5453.sol b/assets/eip-5453/IERC5453.sol deleted file mode 100644 index d51df706cb0783..00000000000000 --- a/assets/eip-5453/IERC5453.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -struct ValidityBound { - bytes32 functionParamStructHash; - uint256 validSince; - uint256 validBy; - uint256 nonce; -} - -struct SingleEndorsementData { - address endorserAddress; // 32 - bytes sig; // dynamic = 65 -} - -struct GeneralExtensionDataStruct { - bytes32 erc5453MagicWord; - uint256 erc5453Type; - uint256 nonce; - uint256 validSince; - uint256 validBy; - bytes endorsementPayload; -} - -interface IERC5453EndorsementCore { - function eip5453Nonce(address endorser) external view returns (uint256); - function isEligibleEndorser(address endorser) external view returns (bool); -} - -interface IERC5453EndorsementDigest { - function computeValidityDigest( - bytes32 _functionParamStructHash, - uint256 _validSince, - uint256 _validBy, - uint256 _nonce - ) external view returns (bytes32); - - function computeFunctionParamHash( - string memory _functionName, - bytes memory _functionParamPacked - ) external view returns (bytes32); -} - -interface IERC5453EndorsementDataTypeA { - function computeExtensionDataTypeA( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address endorserAddress, - bytes calldata sig - ) external view returns (bytes memory); -} - - -interface IERC5453EndorsementDataTypeB { - function computeExtensionDataTypeB( - uint256 nonce, - uint256 validSince, - uint256 validBy, - address[] calldata endorserAddress, - bytes[] calldata sigs - ) external view returns (bytes memory); -} diff --git a/assets/eip-5453/ThresholdMultiSigForwarder.sol b/assets/eip-5453/ThresholdMultiSigForwarder.sol deleted file mode 100644 index c366e6dcd47d5e..00000000000000 --- a/assets/eip-5453/ThresholdMultiSigForwarder.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC0.0 OR Apache-2.0 -// Author: Zainan Victor Zhou -// See a full runnable hardhat project in https://github.com/ercref/ercref-contracts/tree/main/ERCs/eip-5453 -pragma solidity ^0.8.9; - -import "./AERC5453.sol"; - -contract ThresholdMultiSigForwarder is AERC5453Endorsible { - mapping(address => bool) private owners; - uint256 private ownerCount; - - constructor() AERC5453Endorsible("ThresholdMultiSigForwarder", "v1") {} - - function initialize( - address[] calldata _owners, - uint256 _threshold - ) external { - require(_threshold >= 1, "Threshold must be positive"); - require(_owners.length >= _threshold); - require(_noRepeat(_owners)); - _setThreshold(_threshold); - for (uint256 i = 0; i < _owners.length; i++) { - owners[_owners[i]] = true; - } - ownerCount = _owners.length; - } - - function forward( - address _dest, - uint256 _value, - uint256 _gasLimit, - bytes calldata _calldata, - bytes calldata _extraData - ) - external - onlyEndorsed( - _computeFunctionParamHash( - "function forward(address _dest,uint256 _value,uint256 _gasLimit,bytes calldata _calldata)", - abi.encode(_dest, _value, _gasLimit, keccak256(_calldata)) - ), - _extraData - ) - { - string memory errorMessage = "Fail to call remote contract"; - (bool success, bytes memory returndata) = _dest.call{value: _value}( - _calldata - ); - Address.verifyCallResult(success, returndata, errorMessage); - } - - function _isEligibleEndorser( - address _endorser - ) internal view override returns (bool) { - return owners[_endorser] == true; - } -} diff --git a/assets/eip-5489/contracts/ERC5489.sol b/assets/eip-5489/contracts/ERC5489.sol deleted file mode 100644 index 8625d77e944dfc..00000000000000 --- a/assets/eip-5489/contracts/ERC5489.sol +++ /dev/null @@ -1,127 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC5489.sol"; - -contract ERC5489 is IERC5489, ERC721Enumerable, Ownable { - using EnumerableSet for EnumerableSet.AddressSet; - - mapping(uint256 => EnumerableSet.AddressSet) tokenId2AuthroizedAddresses; - mapping(uint256 => mapping(address=> string)) tokenId2Address2Value; - mapping(uint256 => string) tokenId2ImageUri; - - string private _imageURI; - string private _name; - - constructor() ERC721("Hyperlink NFT Collection", "HNFT") {} - - modifier onlyTokenOwner(uint256 tokenId) { - require(_msgSender() == ownerOf(tokenId), "should be the token owner"); - _; - } - - modifier onlySlotManager(uint256 tokenId) { - require(_msgSender() == ownerOf(tokenId) || tokenId2AuthroizedAddresses[tokenId].contains(_msgSender()), "address should be authorized"); - _; - } - - function setSlotUri(uint256 tokenId, string calldata value) override external onlySlotManager(tokenId) { - tokenId2Address2Value[tokenId][_msgSender()] = value; - - emit SlotUriUpdated(tokenId, _msgSender(), value); - } - - function getSlotUri(uint256 tokenId, address slotManagerAddr) override external view returns (string memory) { - return tokenId2Address2Value[tokenId][slotManagerAddr]; - } - - function authorizeSlotTo(uint256 tokenId, address slotManagerAddr) override external onlyTokenOwner(tokenId) { - require(!tokenId2AuthroizedAddresses[tokenId].contains(slotManagerAddr), "address already authorized"); - - _authorizeSlotTo(tokenId, slotManagerAddr); - } - - function _authorizeSlotTo(uint256 tokenId, address slotManagerAddr) private { - tokenId2AuthroizedAddresses[tokenId].add(slotManagerAddr); - emit SlotAuthorizationCreated(tokenId, slotManagerAddr); - } - - function revokeAuthorization(uint256 tokenId, address slotManagerAddr) override external onlyTokenOwner(tokenId) { - tokenId2AuthroizedAddresses[tokenId].remove(slotManagerAddr); - delete tokenId2Address2Value[tokenId][slotManagerAddr]; - - emit SlotAuthorizationRevoked(tokenId, slotManagerAddr); - } - - function revokeAllAuthorizations(uint256 tokenId) override external onlyTokenOwner(tokenId) { - for (uint256 i = tokenId2AuthroizedAddresses[tokenId].length() - 1;i > 0; i--) { - address addr = tokenId2AuthroizedAddresses[tokenId].at(i); - tokenId2AuthroizedAddresses[tokenId].remove(addr); - delete tokenId2Address2Value[tokenId][addr]; - - emit SlotAuthorizationRevoked(tokenId, addr); - } - - if (tokenId2AuthroizedAddresses[tokenId].length() > 0) { - address addr = tokenId2AuthroizedAddresses[tokenId].at(0); - tokenId2AuthroizedAddresses[tokenId].remove(addr); - delete tokenId2Address2Value[tokenId][addr]; - - emit SlotAuthorizationRevoked(tokenId, addr); - } - } - - function isSlotManager(uint256 tokenId, address addr) public view returns (bool) { - return tokenId2AuthroizedAddresses[tokenId].contains(addr); - } - - // !!expensive, should call only when no gas is needed; - function getSlotManagers(uint256 tokenId) external view returns (address[] memory) { - return tokenId2AuthroizedAddresses[tokenId].values(); - } - - function _mintToken(uint256 tokenId, string calldata imageUri) private { - _safeMint(msg.sender, tokenId); - tokenId2ImageUri[tokenId] = imageUri; - } - - function mint(string calldata imageUri) external { - uint256 tokenId = totalSupply() + 1; - _mintToken(tokenId, imageUri); - } - - function mintAndAuthorizeTo(string calldata imageUri, address slotManagerAddr) external { - uint256 tokenId = totalSupply() + 1; - _mintToken(tokenId, imageUri); - _authorizeSlotTo(tokenId, slotManagerAddr); - } - - function tokenURI(uint256 _tokenId) public view override returns (string memory) { - require( - _exists(_tokenId), - "URI query for nonexistent token" - ); - - return - string( - abi.encodePacked( - "data:application/json;base64,", - Base64.encode( - bytes( - abi.encodePacked( - '{"name":"', - abi.encodePacked( - _name, - " # ", - Strings.toString(_tokenId) - ), - '",', - '"description":"Hyperlink NFT collection created with Parami Foundation"', - '}' - ) - ) - ) - ) - ); - } -} diff --git a/assets/eip-5489/contracts/IERC5489.sol b/assets/eip-5489/contracts/IERC5489.sol deleted file mode 100644 index ba6ba4ff8ef3cf..00000000000000 --- a/assets/eip-5489/contracts/IERC5489.sol +++ /dev/null @@ -1,63 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5489 { - /** - * @dev this event emits when the slot on `tokenId` is authorzized to `slotManagerAddr` - */ - event SlotAuthorizationCreated(uint256 indexed tokenId, address indexed slotManagerAddr); - - /** - * @dev this event emits when the authorization on slot `slotManagerAddr` of token `tokenId` is revoked. - * So, the corresponding DApp can handle this to stop on-going incentives or rights - */ - event SlotAuthorizationRevoked(uint256 indexed tokenId, address indexed slotManagerAddr); - - /** - * @dev this event emits when the uri on slot `slotManagerAddr` of token `tokenId` has been updated to `uri`. - */ - event SlotUriUpdated(uint256 indexed tokenId, address indexed slotManagerAddr, string uri); - - /** - * @dev - * Authorize a hyperlink slot on `tokenId` to address `slotManagerAddr`. - * Indeed slot is an entry in a map whose key is address `slotManagerAddr`. - * Only the address `slotManagerAddr` can manage the specific slot. - * This method will emit SlotAuthorizationCreated event - */ - function authorizeSlotTo(uint256 tokenId, address slotManagerAddr) external; - - /** - * @dev - * Revoke the authorization of the slot indicated by `slotManagerAddr` on token `tokenId` - * This method will emit SlotAuthorizationRevoked event - */ - function revokeAuthorization(uint256 tokenId, address slotManagerAddr) external; - - /** - * @dev - * Revoke all authorizations of slot on token `tokenId` - * This method will emit SlotAuthorizationRevoked event for each slot - */ - function revokeAllAuthorizations(uint256 tokenId) external; - - /** - * @dev - * Set uri for a slot on a token, which is indicated by `tokenId` and `slotManagerAddr` - * Only the address with authorization through {authorizeSlotTo} can manipulate this slot. - * This method will emit SlotUriUpdated event - */ - function setSlotUri( - uint256 tokenId, - string calldata newUri - ) external; - - /** - * @dev - * returns the latest uri of an slot on a token, which is indicated by `tokenId`, `slotManagerAddr` - */ - function getSlotUri(uint256 tokenId, address slotManagerAddr) - external - view - returns (string memory); -} \ No newline at end of file diff --git a/assets/eip-5496/contracts/ERC5496.sol b/assets/eip-5496/contracts/ERC5496.sol deleted file mode 100644 index fe5c46762f6e82..00000000000000 --- a/assets/eip-5496/contracts/ERC5496.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "./IERC5496.sol"; - -contract ERC5496 is ERC721, IERC5496 { - struct PrivilegeRecord { - address user; - uint256 expiresAt; - } - struct PrivilegeStorage { - uint lastExpiresAt; - // privId => PrivilegeRecord - mapping(uint => PrivilegeRecord) privilegeEntry; - } - - uint public privilegeTotal; - // tokenId => PrivilegeStorage - mapping(uint => PrivilegeStorage) public privilegeBook; - mapping(address => mapping(address => bool)) private privilegeDelegator; - - constructor(string memory name_, string memory symbol_) - ERC721(name_,symbol_) - { - - } - - function setPrivilege( - uint tokenId, - uint privId, - address user, - uint64 expires - ) external virtual { - require((hasPrivilege(tokenId, privId, ownerOf(tokenId)) && _isApprovedOrOwner(msg.sender, tokenId)) || _isDelegatorOrHolder(msg.sender, tokenId, privId), "ERC721: transfer caller is not owner nor approved"); - require(expires < block.timestamp + 30 days, "expire time invalid"); - require(privId < privilegeTotal, "invalid privilege id"); - privilegeBook[tokenId].privilegeEntry[privId].user = user; - if (_isApprovedOrOwner(msg.sender, tokenId)) { - privilegeBook[tokenId].privilegeEntry[privId].expiresAt = expires; - if (privilegeBook[tokenId].lastExpiresAt < expires) { - privilegeBook[tokenId].lastExpiresAt = expires; - } - } - emit PrivilegeAssigned(tokenId, privId, user, uint64(privilegeBook[tokenId].privilegeEntry[privId].expiresAt)); - } - - function hasPrivilege( - uint256 tokenId, - uint256 privId, - address user - ) public virtual view returns(bool) { - if ( privilegeBook[tokenId].privilegeEntry[privId].expiresAt >= block.timestamp ){ - return privilegeBook[tokenId].privilegeEntry[privId].user == user; - } - return ownerOf(tokenId) == user; - } - - function privilegeExpires( - uint256 tokenId, - uint256 privId - ) public virtual view returns(uint256){ - return privilegeBook[tokenId].privilegeEntry[privId].expiresAt; - } - - function _setPrivilegeTotal( - uint total - ) internal { - emit PrivilegeTotalChanged(total, privilegeTotal); - privilegeTotal = total; - } - - function getPrivilegeInfo(uint tokenId, uint privId) external view returns(address user, uint256 expiresAt) { - return (privilegeBook[tokenId].privilegeEntry[privId].user, privilegeBook[tokenId].privilegeEntry[privId].expiresAt); - } - - function setDelegator(address delegator, bool enabled) external { - privilegeDelegator[msg.sender][delegator] = enabled; - } - - function _isDelegatorOrHolder(address delegator, uint256 tokenId, uint privId) internal virtual view returns (bool) { - address holder = privilegeBook[tokenId].privilegeEntry[privId].user; - return (delegator == holder || privilegeDelegator[holder][delegator]); - } - - function supportsInterface(bytes4 interfaceId) public override virtual view returns (bool) { - return interfaceId == type(IERC5496).interfaceId || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5496/contracts/ERC5496CloneableDemo.sol b/assets/eip-5496/contracts/ERC5496CloneableDemo.sol deleted file mode 100644 index 49f7fb107430e5..00000000000000 --- a/assets/eip-5496/contracts/ERC5496CloneableDemo.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./extensions/ERC5496Cloneable.sol"; - -contract ERC5496CloneableDemo is ERC5496Cloneable { - - constructor(string memory name_, string memory symbol_) - ERC5496(name_,symbol_) - { - - } - - function mint(uint256 tokenId, address to) public { - _mint(to, tokenId); - } - - function setPrivilegeTotal(uint total) external { - _setPrivilegeTotal(total); - } - - function increasePrivileges(bool _cloneable) external { - uint privId = privilegeTotal; - _setPrivilegeTotal(privilegeTotal + 1); - cloneable[privId] = _cloneable; - } -} diff --git a/assets/eip-5496/contracts/ERC5496Demo.sol b/assets/eip-5496/contracts/ERC5496Demo.sol deleted file mode 100644 index deb7c07453a910..00000000000000 --- a/assets/eip-5496/contracts/ERC5496Demo.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC5496.sol"; - -contract ERC5496Demo is ERC5496 { - - constructor(string memory name_, string memory symbol_) - ERC5496(name_,symbol_) - { - - } - - function mint(uint256 tokenId, address to) public { - _mint(to, tokenId); - } - - function setPrivilegeTotal(uint total) external { - _setPrivilegeTotal(total); - } - - function increasePrivileges(bool ) external { - _setPrivilegeTotal(privilegeTotal + 1); - } -} diff --git a/assets/eip-5496/contracts/IERC5496.sol b/assets/eip-5496/contracts/IERC5496.sol deleted file mode 100644 index 178241a9a0a418..00000000000000 --- a/assets/eip-5496/contracts/IERC5496.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5496 { - event PrivilegeAssigned(uint tokenId, uint privId, address user, uint64 expires); - event PrivilegeTotalChanged(uint newTotal, uint oldTotal); - function setPrivilege(uint256 tokenId, uint privId, address user, uint64 expires) external; - function privilegeExpires(uint256 tokenId, uint256 privId) external view returns(uint256); - function hasPrivilege(uint256 tokenId, uint256 privId, address user) external view returns(bool); -} diff --git a/assets/eip-5496/contracts/extensions/ERC5496Cloneable.sol b/assets/eip-5496/contracts/extensions/ERC5496Cloneable.sol deleted file mode 100644 index c08d829941320b..00000000000000 --- a/assets/eip-5496/contracts/extensions/ERC5496Cloneable.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/ERC721Enumerable.sol) - -pragma solidity ^0.8.0; - -import "../ERC5496.sol"; -import "./IERC5496Cloneable.sol"; - -/** - * @dev This implements an optional extension of {ERC721} defined in the EIP that adds - * enumerability of all the token ids in the contract as well as all token ids owned by each - * account. - */ -abstract contract ERC5496Cloneable is ERC5496, IERC5496Cloneable { - struct CloneableRecord { - // account => shared - mapping(address => bool) shared; - // account => refer - mapping(address => address) referrer; - } - - // privId => isCloneable - mapping(uint => bool) public cloneable; - // tokenId => privId => CloneableRecord - mapping(uint => mapping(uint => CloneableRecord)) cloneableSetting; - - function supportsInterface(bytes4 interfaceId) public override virtual view returns (bool) { - return interfaceId == type(IERC5496Cloneable).interfaceId || super.supportsInterface(interfaceId); - } - - function hasPrivilege( - uint256 tokenId, - uint256 privId, - address user - ) public override virtual view returns(bool) { - if ( privilegeBook[tokenId].privilegeEntry[privId].expiresAt >= block.timestamp ){ - return cloneableSetting[tokenId][privId].shared[user] || super.hasPrivilege(tokenId, privId, user); - } - return ownerOf(tokenId) == user; - } - - function clonePrivilege(uint tokenId, uint privId, address referrer) external returns (bool) { - require(cloneable[privId], "privilege not cloneable"); - return _clonePrivilege(tokenId, privId, referrer); - } - - function _clonePrivilege(uint tokenId, uint privId, address referrer) internal returns (bool) { - require(privilegeBook[tokenId].privilegeEntry[privId].user == referrer || cloneableSetting[tokenId][privId].shared[referrer], "referrer not exists"); - if (cloneableSetting[tokenId][privId].referrer[msg.sender] == address(0)) { - cloneableSetting[tokenId][privId].shared[msg.sender] = true; - cloneableSetting[tokenId][privId].referrer[msg.sender] = referrer; - emit PrivilegeCloned(tokenId, privId, referrer, msg.sender); - return true; - } - return false; - } -} diff --git a/assets/eip-5496/contracts/extensions/IERC5496Cloneable.sol b/assets/eip-5496/contracts/extensions/IERC5496Cloneable.sol deleted file mode 100644 index 9228e7ceee0ce2..00000000000000 --- a/assets/eip-5496/contracts/extensions/IERC5496Cloneable.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5496Cloneable { - event PrivilegeCloned(uint tokenId, uint privId, address from, address to); - function clonePrivilege(uint tokenId, uint privId, address referrer) external returns (bool); -} diff --git a/assets/eip-5496/test/test.js b/assets/eip-5496/test/test.js deleted file mode 100644 index a89379e530e065..00000000000000 --- a/assets/eip-5496/test/test.js +++ /dev/null @@ -1,122 +0,0 @@ -const { assert } = require("chai"); -const { expectRevert } = require('@openzeppelin/test-helpers'); - -const ERC5496Demo = artifacts.require("ERC5496Demo"); - -contract("ERC5496", async accounts => { - const Alice = accounts[0]; - const Bob = accounts[1]; - const Tom = accounts[2]; - let demoContract; - - before(async function() { - const instance = await ERC5496Demo.deployed("ERC5496Demo", "EPD"); - demoContract = instance; - await demoContract.mint(1, Alice); - await demoContract.mint(2, Alice); - await demoContract.mint(3, Alice); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(false); - }) - - it("Should set privilege 0 to Bob", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(1, 0, Bob, BigInt(expires)); - - let user_hasP0 = await demoContract.hasPrivilege(1, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 1 should be Bob" - ); - }); - - it("Privilege should belong to the owner by default", async () => { - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Alice , - "Owner of NFT 1 should be Alice" - ); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Alice); - assert.equal( - user_hasP1, - true, - "Privilege 1 of NFT 1 should be Alice" - ); - }); - - it("The privilege holder is allowed to transfer the privilege to others", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(2, 0, Bob, BigInt(expires)); - let user_hasP0 = await demoContract.hasPrivilege(2, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Bob" - ); - await demoContract.setPrivilege(2, 0, Tom, BigInt(expires + 100), { from: Bob }) - user_hasP0 = await demoContract.hasPrivilege(2, 0, Tom); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Tom" - ); - let privilege_info = await demoContract.getPrivilegeInfo(2, 0); - assert.equal( - privilege_info.expiresAt, - expires, - "Only owner can set the expiresAt" - ) - }); - - it("User is allowed to transfer NFT while privileges on renting", async () => { - await demoContract.transferFrom(Alice, Bob, 1); - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Bob, - "Owner of NFT 1 should be Bob" - ); - let expires = Math.floor(new Date().getTime()/1000) + 1000; - await demoContract.setPrivilege(1, 1, Tom, BigInt(expires), { from: Bob }); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Tom); - assert.equal( - user_hasP1, - true, - "Bob should be allowed to set the unassigned privilege to Tom" - ); - }); - - it("NFT owner may change the privileges total for each tokenId", async () => { - await demoContract.increasePrivileges(false); - let owner_1 = await demoContract.ownerOf(1); - let user_hasP2 = await demoContract.hasPrivilege(1, 2, owner_1); - assert.equal( - user_hasP2, - true, - "privilege 2 available after NFT owner update the privilege total" - ); - }); - - it("NFT owner should not change the privilege if it has been assigned", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(3, 0, Bob, BigInt(expires)); - await expectRevert( - demoContract.setPrivilege(3, 0, Tom, BigInt(expires)), - "ERC721: transfer caller is not owner nor approved", - ); - }); - - it("NFT should support interface IERC5496", async () => { - const interfaceIds = { - IERC165: "0x01ffc9a7", - IERC721: "0x80ac58cd", - IERC5496: "0x076e1bbb", - } - for(let interfaceName in interfaceIds) { - let isSupport = await demoContract.supportsInterface(interfaceIds[interfaceName]); - assert.equal(isSupport, true, "NFT should support interface "+interfaceName); - } - }) -}); diff --git a/assets/eip-5496/test/testCloneable.js b/assets/eip-5496/test/testCloneable.js deleted file mode 100644 index 21346386d60eac..00000000000000 --- a/assets/eip-5496/test/testCloneable.js +++ /dev/null @@ -1,158 +0,0 @@ -const { assert } = require("chai"); -const { expectRevert } = require('@openzeppelin/test-helpers'); - -const ERC5496Demo = artifacts.require("ERC5496CloneableDemo"); - -contract("ERC5496Cloneable", async accounts => { - const Alice = accounts[0]; - const Bob = accounts[1]; - const Tom = accounts[2]; - let demoContract; - - before(async function() { - const instance = await ERC5496Demo.deployed("ERC5496CDemo", "EPCD"); - demoContract = instance; - await demoContract.mint(1, Alice); - await demoContract.mint(2, Alice); - await demoContract.mint(3, Alice); - await demoContract.mint(4, Alice); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(false); - await demoContract.increasePrivileges(true); - }) - - it("Should set privilege 0 to Bob", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(1, 0, Bob, BigInt(expires)); - - let user_hasP0 = await demoContract.hasPrivilege(1, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 1 should be Bob" - ); - }); - - it("Privilege should belong to the owner by default", async () => { - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Alice , - "Owner of NFT 1 should be Alice" - ); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Alice); - assert.equal( - user_hasP1, - true, - "Privilege 1 of NFT 1 should be Alice" - ); - }); - - it("The privilege holder is allowed to transfer the privilege to others", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(2, 0, Bob, BigInt(expires)); - let user_hasP0 = await demoContract.hasPrivilege(2, 0, Bob); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Bob" - ); - await demoContract.setPrivilege(2, 0, Tom, BigInt(expires + 100), { from: Bob }) - user_hasP0 = await demoContract.hasPrivilege(2, 0, Tom); - assert.equal( - user_hasP0, - true, - "Privilege 0 of NFT 2 should be Tom" - ); - let privilege_info = await demoContract.getPrivilegeInfo(2, 0); - assert.equal( - privilege_info.expiresAt, - expires, - "Only owner can set the expiresAt" - ) - }); - - it("User is allowed to transfer NFT while privileges on renting", async () => { - await demoContract.transferFrom(Alice, Bob, 1); - let owner_1 = await demoContract.ownerOf(1); - assert.equal( - owner_1, - Bob, - "Owner of NFT 1 should be Bob" - ); - let expires = Math.floor(new Date().getTime()/1000) + 1000; - await demoContract.setPrivilege(1, 1, Tom, BigInt(expires), { from: Bob }); - let user_hasP1 = await demoContract.hasPrivilege(1, 1, Tom); - assert.equal( - user_hasP1, - true, - "Bob should be allowed to set unassigned privilege to Tom" - ); - }); - - it("NFT owner may change the privileges total for each tokenId", async () => { - let owner_1 = await demoContract.ownerOf(1); - let user_hasP2 = await demoContract.hasPrivilege(1, 2, owner_1); - assert.equal( - user_hasP2, - true, - "privilege 2 available after NFT owner update the privilege total" - ); - }); - - it("NFT owner should not change the privilege if it has been assigned", async () => { - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(3, 0, Bob, BigInt(expires)); - await expectRevert( - demoContract.setPrivilege(3, 0, Tom, BigInt(expires)), - "ERC721: transfer caller is not owner nor approved", - ); - }); - - it("ERC5496 cloneable", async () => { - let owner_1 = await demoContract.ownerOf(4); - let cloneable_P2 = await demoContract.cloneable(2); - assert.equal( - cloneable_P2, - false, - "privilege 2 should not be cloneable" - ); - let cloneable_P3 = await demoContract.cloneable(3); - assert.equal( - cloneable_P3, - true, - "privilege 3 should be cloneable" - ); - let expires = Math.floor(new Date().getTime()/1000) + 5000; - await demoContract.setPrivilege(4, 3, Bob, BigInt(expires), { from: Alice }); - - await expectRevert( - demoContract.clonePrivilege(4, 2, owner_1, {from: Bob}), - "privilege not cloneable", - ); - await expectRevert( - demoContract.clonePrivilege(4, 3, Tom, { from: Bob }), - "referrer not exists", - ); - await demoContract.clonePrivilege(4, 3, Bob, { from: Tom }); - let user_hasP3 = await demoContract.hasPrivilege(4, 3, Tom); - assert.equal( - user_hasP3, - true, - "privilege 3 available after Bob cloned" - ); - }); - - it("NFT should support interface IERC5496", async () => { - const interfaceIds = { - IERC165: "0x01ffc9a7", - IERC721: "0x80ac58cd", - IERC5496: "0x076e1bbb", - } - for(let interfaceName in interfaceIds) { - let isSupport = await demoContract.supportsInterface(interfaceIds[interfaceName]); - assert.equal(isSupport, true, "NFT should support interface "+interfaceName); - } - }) -}); diff --git a/assets/eip-5501/contracts/ERC5501.sol b/assets/eip-5501/contracts/ERC5501.sol deleted file mode 100644 index 6dd2f6501bdafc..00000000000000 --- a/assets/eip-5501/contracts/ERC5501.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5501.sol"; - -/** - * @dev Implementation of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501 is IERC5501, ERC721 { - /** - * @dev Structure to hold user information. - * @notice If isBorrowed is true, UserInfo cannot be modified before it expires. - */ - struct UserInfo { - address user; // Address of user role - uint64 expires; // Unix timestamp, user expires on - bool isBorrowed; // Borrowed flag - } - - // Mapping from token ID to UserInfo - mapping(uint256 => UserInfo) internal _users; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC721(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - require( - _isApprovedOrOwner(msg.sender, tokenId), - "ERC5501: set user caller is not token owner or approved" - ); - require(user != address(0), "ERC5501: set user to zero address"); - - UserInfo storage info = _users[tokenId]; - require( - !info.isBorrowed || info.expires < block.timestamp, - "ERC5501: token is borrowed" - ); - info.user = user; - info.expires = expires; - info.isBorrowed = isBorrowed; - emit UpdateUser(tokenId, user, expires, isBorrowed); - } - - /** - * @dev See {IERC5501-userOf}. - */ - function userOf(uint256 tokenId) - public - view - virtual - override - returns (address) - { - require( - uint256(_users[tokenId].expires) >= block.timestamp, - "ERC5501: user does not exist for this token" - ); - return _users[tokenId].user; - } - - /** - * @dev See {IERC5501-userExpires}. - */ - function userExpires(uint256 tokenId) - public - view - virtual - override - returns (uint64) - { - return _users[tokenId].expires; - } - - /** - * @dev See {IERC5501-isBorrowed}. - */ - function userIsBorrowed(uint256 tokenId) - public - view - virtual - override - returns (bool) - { - return _users[tokenId].isBorrowed; - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev Hook that is called after any token transfer. - * If user is set and token is not borrowed, reset user. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._afterTokenTransfer(from, to, tokenId); - if ( - from != to && - !_users[tokenId].isBorrowed && - _users[tokenId].user != address(0) - ) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0, false); - } - } -} diff --git a/assets/eip-5501/contracts/ERC5501Balance.sol b/assets/eip-5501/contracts/ERC5501Balance.sol deleted file mode 100644 index a967ccb9fade83..00000000000000 --- a/assets/eip-5501/contracts/ERC5501Balance.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5501.sol"; -import "./IERC5501Balance.sol"; - -/** - * @dev Implementation of Balance extension of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Balance is IERC5501Balance, ERC5501 { - // Mapping from address to userOf tokens - mapping(address => uint256[]) internal _userBalances; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC5501(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - flushExpired(user); - super.setUser(tokenId, user, expires, isBorrowed); - _userBalances[user].push(tokenId); - } - - /** - * @dev See {IERC5501-userBalanceOf}. - */ - function userBalanceOf(address user) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Balance: address zero is not a valid owner" - ); - uint256 balance; - uint256[] memory candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].expires >= block.timestamp && - _users[candidates[i]].user == user - ) { - ++balance; - } - } - } - return balance; - } - - /** - * @notice On setUser flush all expired userOf statuses. - * @dev This function may revert out of gas if user borrows too many tokens at once. - * There must be a way to prevent such behaviour (such as flushing by parts only). - * @param user an address to flush - */ - function flushExpired(address user) internal { - uint256[] storage candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].user != user || - _users[candidates[i]].expires < block.timestamp - ) { - candidates[i] = candidates[candidates.length - 1]; - candidates.pop(); - --i; // test moved element - } - } - } - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501Balance).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5501/contracts/ERC5501Combined.sol b/assets/eip-5501/contracts/ERC5501Combined.sol deleted file mode 100644 index 95f8b27b9220b1..00000000000000 --- a/assets/eip-5501/contracts/ERC5501Combined.sol +++ /dev/null @@ -1,325 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC5501.sol"; -import "./IERC5501Balance.sol"; -import "./IERC5501Enumerable.sol"; -import "./IERC5501Terminable.sol"; - -/** - * @dev Implementation of ERC5501 contract with all extensions https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Combined is - IERC5501, - IERC5501Balance, - IERC5501Terminable, - IERC5501Enumerable, - ERC721 -{ - /** - * @dev Structure to hold user information. - * @notice If isBorrowed is true, UserInfo cannot be modified before it expires. - */ - struct UserInfo { - address user; // Address of user role - uint64 expires; // Unix timestamp, user expires on - bool isBorrowed; // Borrowed flag - } - - /** - * @dev Structure to hold agreements from both parties to terminate a borrow. - * @notice If both parties agree, it is possible to modify UserInfo even before it expires. - * In such case, isBorrowed status is reverted to false. - */ - struct BorrowTerminationInfo { - bool lenderAgreement; - bool borrowerAgreement; - } - - // Mapping from token ID to UserInfo - mapping(uint256 => UserInfo) internal _users; - - // Mapping from address to userOf tokens - mapping(address => uint256[]) internal _userBalances; - - // Mapping from token ID to BorrowTerminationInfo - mapping(uint256 => BorrowTerminationInfo) internal _borrowTerminations; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC721(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - // Balance extension - flushExpired(user); - - require( - _isApprovedOrOwner(msg.sender, tokenId), - "ERC5501: set user caller is not token owner or approved" - ); - require(user != address(0), "ERC5501: set user to zero address"); - - UserInfo storage info = _users[tokenId]; - require( - !info.isBorrowed || info.expires < block.timestamp, - "ERC5501: token is borrowed" - ); - info.user = user; - info.expires = expires; - info.isBorrowed = isBorrowed; - emit UpdateUser(tokenId, user, expires, isBorrowed); - - // Balance extension - _userBalances[user].push(tokenId); - // Terminable extension - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - - /** - * @dev See {IERC5501-userOf}. - */ - function userOf(uint256 tokenId) - public - view - virtual - override - returns (address) - { - require( - uint256(_users[tokenId].expires) >= block.timestamp, - "ERC5501: user does not exist for this token" - ); - return _users[tokenId].user; - } - - /** - * @dev See {IERC5501-userBalanceOf}. - */ - function userBalanceOf(address user) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Balance: address zero is not a valid owner" - ); - uint256 balance; - uint256[] memory candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].expires >= block.timestamp && - _users[candidates[i]].user == user - ) { - ++balance; - } - } - } - return balance; - } - - /** - * @dev See {IERC5501-tokenOfUserByIndex}. - */ - function tokenOfUserByIndex(address user, uint256 index) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Enumerable: address zero is not a valid owner" - ); - uint256[] memory balance = _userBalances[user]; - require( - balance.length > 0 && index < balance.length, - "ERC5501Enumerable: owner index out of bounds" - ); - uint256 counter; - unchecked { - for (uint256 i; i < balance.length; ++i) { - if ( - _users[balance[i]].expires >= block.timestamp && - _users[balance[i]].user == user - ) { - if (counter == index) { - return balance[i]; - } - ++counter; - } - } - } - revert("ERC5501Enumerable: owner index out of bounds"); - } - - /** - * @dev See {IERC5501-userExpires}. - */ - function userExpires(uint256 tokenId) - public - view - virtual - override - returns (uint64) - { - return _users[tokenId].expires; - } - - /** - * @dev See {IERC5501-isBorrowed}. - */ - function userIsBorrowed(uint256 tokenId) - public - view - virtual - override - returns (bool) - { - return _users[tokenId].isBorrowed; - } - - /** - * @dev See {IERC5501Terminable-getBorrowTermination}. - */ - function getBorrowTermination(uint256 tokenId) - public - view - virtual - override - returns (bool, bool) - { - return ( - _borrowTerminations[tokenId].lenderAgreement, - _borrowTerminations[tokenId].borrowerAgreement - ); - } - - /** - * @dev See {IERC5501Terminable-setBorrowTermination}. - */ - function setBorrowTermination(uint256 tokenId) public virtual override { - UserInfo storage userInfo = _users[tokenId]; - require( - userInfo.expires >= block.timestamp && userInfo.isBorrowed, - "ERC5501Terminable: borrow not active" - ); - - BorrowTerminationInfo storage terminationInfo = _borrowTerminations[ - tokenId - ]; - if (ownerOf(tokenId) == msg.sender) { - terminationInfo.lenderAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, true); - } - if (userInfo.user == msg.sender) { - terminationInfo.borrowerAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, false); - } - } - - /** - * @dev See {IERC5501Terminable-terminateBorrow}. - */ - function terminateBorrow(uint256 tokenId) public virtual override { - BorrowTerminationInfo storage info = _borrowTerminations[tokenId]; - require( - info.lenderAgreement && info.borrowerAgreement, - "ERC5501Terminable: not agreed" - ); - _users[tokenId].isBorrowed = false; - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - emit TerminateBorrow( - tokenId, - ownerOf(tokenId), - _users[tokenId].user, - msg.sender - ); - } - - /** - * @notice On setUser flush all expired userOf statuses. - * @dev This function may revert out of gas if user borrows too many tokens at once. - * There must be a way to prevent such behaviour (such as flushing by parts only). - * @param user an address to flush - */ - function flushExpired(address user) internal { - uint256[] storage candidates = _userBalances[user]; - unchecked { - for (uint256 i; i < candidates.length; ++i) { - if ( - _users[candidates[i]].user != user || - _users[candidates[i]].expires < block.timestamp - ) { - candidates[i] = candidates[candidates.length - 1]; - candidates.pop(); - --i; // test moved element - } - } - } - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501).interfaceId || - interfaceId == type(IERC5501Balance).interfaceId || - interfaceId == type(IERC5501Enumerable).interfaceId || - interfaceId == type(IERC5501Terminable).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev Hook that is called after any token transfer. - * If user is set and token is not borrowed, reset user. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._afterTokenTransfer(from, to, tokenId); - if ( - from != to && - !_users[tokenId].isBorrowed && - _users[tokenId].user != address(0) - ) { - delete _users[tokenId]; - emit UpdateUser(tokenId, address(0), 0, false); - } else if ( - // Terminable extension - from != to && _users[tokenId].isBorrowed - ) { - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - } -} diff --git a/assets/eip-5501/contracts/ERC5501Enumerable.sol b/assets/eip-5501/contracts/ERC5501Enumerable.sol deleted file mode 100644 index 1068cad12c23e5..00000000000000 --- a/assets/eip-5501/contracts/ERC5501Enumerable.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5501Balance.sol"; -import "./IERC5501Enumerable.sol"; - -/** - * @dev Implementation of Enumerable extension of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Enumerable is IERC5501Enumerable, ERC5501Balance { - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC5501Balance(name_, symbol_) - {} - - /** - * @dev See {IERC5501-tokenOfUserByIndex}. - */ - function tokenOfUserByIndex(address user, uint256 index) - public - view - virtual - override - returns (uint256) - { - require( - user != address(0), - "ERC5501Enumerable: address zero is not a valid owner" - ); - uint256[] memory balance = _userBalances[user]; - require( - balance.length > 0 && index < balance.length, - "ERC5501Enumerable: owner index out of bounds" - ); - uint256 counter; - unchecked { - for (uint256 i; i < balance.length; ++i) { - if ( - _users[balance[i]].expires >= block.timestamp && - _users[balance[i]].user == user - ) { - if (counter == index) { - return balance[i]; - } - ++counter; - } - } - } - revert("ERC5501Enumerable: owner index out of bounds"); - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501Enumerable).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5501/contracts/ERC5501Terminable.sol b/assets/eip-5501/contracts/ERC5501Terminable.sol deleted file mode 100644 index 938ef78ecf5266..00000000000000 --- a/assets/eip-5501/contracts/ERC5501Terminable.sol +++ /dev/null @@ -1,136 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./ERC5501.sol"; -import "./IERC5501Terminable.sol"; - -/** - * @dev Implementation of Terminable extension of https://eips.ethereum.org/EIPS/eip-5501 with OpenZeppelin ERC721 version. - */ -contract ERC5501Terminable is IERC5501Terminable, ERC5501 { - /** - * @dev Structure to hold agreements from both parties to terminate a borrow. - * @notice If both parties agree, it is possible to modify UserInfo even before it expires. - * In such case, isBorrowed status is reverted to false. - */ - struct BorrowTerminationInfo { - bool lenderAgreement; - bool borrowerAgreement; - } - - // Mapping from token ID to BorrowTerminationInfo - mapping(uint256 => BorrowTerminationInfo) internal _borrowTerminations; - - /** - * @dev Initializes the contract by setting a name and a symbol to the token collection. - */ - constructor(string memory name_, string memory symbol_) - ERC5501(name_, symbol_) - {} - - /** - * @dev See {IERC5501-setUser}. - */ - function setUser( - uint256 tokenId, - address user, - uint64 expires, - bool isBorrowed - ) public virtual override { - super.setUser(tokenId, user, expires, isBorrowed); - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - - /** - * @dev See {IERC5501Terminable-setBorrowTermination}. - */ - function setBorrowTermination(uint256 tokenId) public virtual override { - UserInfo storage userInfo = _users[tokenId]; - require( - userInfo.expires >= block.timestamp && userInfo.isBorrowed, - "ERC5501Terminable: borrow not active" - ); - - BorrowTerminationInfo storage terminationInfo = _borrowTerminations[ - tokenId - ]; - if (ownerOf(tokenId) == msg.sender) { - terminationInfo.lenderAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, true); - } - if (userInfo.user == msg.sender) { - terminationInfo.borrowerAgreement = true; - emit AgreeToTerminateBorrow(tokenId, msg.sender, false); - } - } - - /** - * @dev See {IERC5501Terminable-getBorrowTermination}. - */ - function getBorrowTermination(uint256 tokenId) - public - view - virtual - override - returns (bool, bool) - { - return ( - _borrowTerminations[tokenId].lenderAgreement, - _borrowTerminations[tokenId].borrowerAgreement - ); - } - - /** - * @dev See {IERC5501Terminable-terminateBorrow}. - */ - function terminateBorrow(uint256 tokenId) public virtual override { - BorrowTerminationInfo storage info = _borrowTerminations[tokenId]; - require( - info.lenderAgreement && info.borrowerAgreement, - "ERC5501Terminable: not agreed" - ); - _users[tokenId].isBorrowed = false; - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - emit TerminateBorrow( - tokenId, - ownerOf(tokenId), - _users[tokenId].user, - msg.sender - ); - } - - /** - * @dev See {EIP-165: Standard Interface Detection}. - * https://eips.ethereum.org/EIPS/eip-165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return - interfaceId == type(IERC5501Terminable).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev Hook that is called after any token transfer. - * If user is set and token is borrowed, reset termination agreements. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override { - super._afterTokenTransfer(from, to, tokenId); - if (from != to && _users[tokenId].isBorrowed) { - delete _borrowTerminations[tokenId]; - emit ResetTerminationAgreements(tokenId); - } - } -} diff --git a/assets/eip-5501/contracts/IERC5501.sol b/assets/eip-5501/contracts/IERC5501.sol deleted file mode 100644 index 37626bd2f23d08..00000000000000 --- a/assets/eip-5501/contracts/IERC5501.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501: Rental & Delegation NFT - EIP-721 Extension - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * @notice the EIP-165 identifier for this interface is 0xf808ec37. - */ -interface IERC5501 /* is IERC721 */ { - /** - * @dev Emitted when the user of an NFT is modified. - */ - event UpdateUser(uint256 indexed _tokenId, address indexed _user, uint64 _expires, bool _isBorrowed); - - /** - * @notice Set the user info of an NFT. - * @dev User address cannot be zero address. - * Only approved operator or NFT owner can set the user. - * If NFT is borrowed, the user info cannot be changed until user status expires. - * @param _tokenId uint256 ID of the token to set user info for - * @param _user address of the new user - * @param _expires Unix timestamp when user info expires - * @param _isBorrowed flag whether or not the NFT is borrowed - */ - function setUser(uint256 _tokenId, address _user, uint64 _expires, bool _isBorrowed) external; - - /** - * @notice Get the user address of an NFT. - * @dev Reverts if user is not set. - * @param _tokenId uint256 ID of the token to get the user address for - * @return address user address for this NFT - */ - function userOf(uint256 _tokenId) external view returns (address); - - /** - * @notice Get the user expires of an NFT. - * @param _tokenId uint256 ID of the token to get the user expires for - * @return uint64 user expires for this NFT - */ - function userExpires(uint256 _tokenId) external view returns (uint64); - - /** - * @notice Get the user isBorrowed of an NFT. - * @param _tokenId uint256 ID of the token to get the user isBorrowed for - * @return bool user isBorrowed for this NFT - */ - function userIsBorrowed(uint256 _tokenId) external view returns (bool); -} diff --git a/assets/eip-5501/contracts/IERC5501Balance.sol b/assets/eip-5501/contracts/IERC5501Balance.sol deleted file mode 100644 index fa32ec82f96f53..00000000000000 --- a/assets/eip-5501/contracts/IERC5501Balance.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501Balance - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * Extension for ERC5501 which adds userBalanceOf to query how many tokens address is userOf. - * @notice the EIP-165 identifier for this interface is 0x0cb22289. - */ -interface IERC5501Balance /* is IERC5501 */{ - /** - * @notice Count of all NFTs assigned to a user. - * @dev Reverts if user is zero address. - * @param _user an address for which to query the balance - * @return uint256 the number of NFTs the user has - */ - function userBalanceOf(address _user) external view returns (uint256); -} diff --git a/assets/eip-5501/contracts/IERC5501Enumerable.sol b/assets/eip-5501/contracts/IERC5501Enumerable.sol deleted file mode 100644 index 87800077745882..00000000000000 --- a/assets/eip-5501/contracts/IERC5501Enumerable.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501Enumerable - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * This extension for ERC5501 adds the option to iterate over user tokens. - * @notice the EIP-165 identifier for this interface is 0x1d350ef8. - */ -interface IERC5501Enumerable /* is IERC5501Balance, IERC5501 */ { - /** - * @notice Enumerate NFTs assigned to a user. - * @dev Reverts if user is zero address or _index >= userBalanceOf(_owner). - * @param _user an address to iterate over its tokens - * @return uint256 the token ID for given index assigned to _user - */ - function tokenOfUserByIndex(address _user, uint256 _index) external view returns (uint256); -} diff --git a/assets/eip-5501/contracts/IERC5501Terminable.sol b/assets/eip-5501/contracts/IERC5501Terminable.sol deleted file mode 100644 index 6f74bb3c30a1e2..00000000000000 --- a/assets/eip-5501/contracts/IERC5501Terminable.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/** - * @title IERC5501Terminable - * @dev See https://eips.ethereum.org/EIPS/eip-5501 - * This extension for ERC5501 adds the option to terminate borrowing if both parties agree. - * @notice the EIP-165 identifier for this interface is 0x6a26417e. - */ -interface IERC5501Terminable /* is IERC5501 */ { - /** - * @dev Emitted when one party from borrowing contract approves termination of agreement. - * @param _isLender true for lender, false for borrower - */ - event AgreeToTerminateBorrow(uint256 indexed _tokenId, address indexed _party, bool _isLender); - - /** - * @dev Emitted when agreements to terminate borrow are reset. - */ - event ResetTerminationAgreements(uint256 indexed _tokenId); - - /** - * @dev Emitted when borrow of token ID is terminated. - */ - event TerminateBorrow(uint256 indexed _tokenId, address indexed _lender, address indexed _borrower, address _caller); - - /** - * @notice Agree to terminate a borrowing. - * @dev Lender must be ownerOf token ID. Borrower must be userOf token ID. - * If lender and borrower are the same, set termination agreement for both at once. - * @param _tokenId uint256 ID of the token to set termination info for - */ - function setBorrowTermination(uint256 _tokenId) external; - - /** - * @notice Get if it is possible to terminate a borrow agreement. - * @param _tokenId uint256 ID of the token to get termination info for - * @return bool, bool first indicates lender agrees, second indicates borrower agrees - */ - function getBorrowTermination(uint256 _tokenId) external view returns (bool, bool); - - /** - * @notice Terminate a borrow if both parties agreed. - * @dev Both parties must have agreed, otherwise revert. - * @param _tokenId uint256 ID of the token to terminate borrow of - */ - function terminateBorrow(uint256 _tokenId) external; -} diff --git a/assets/eip-5501/contracts/test/ERC5501BalanceTestCollection.sol b/assets/eip-5501/contracts/test/ERC5501BalanceTestCollection.sol deleted file mode 100644 index 38057df7daeb87..00000000000000 --- a/assets/eip-5501/contracts/test/ERC5501BalanceTestCollection.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Balance.sol"; - -contract ERC5501BalanceTestCollection is ERC5501Balance { - - constructor(string memory name_, string memory symbol_) ERC5501Balance(name_,symbol_) {} - - function getUserBalances(address user) external view returns (uint256[] memory) { - return _userBalances[user]; - } - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501CombinedCollection.sol b/assets/eip-5501/contracts/test/ERC5501CombinedCollection.sol deleted file mode 100644 index 2a2c3468c8ef91..00000000000000 --- a/assets/eip-5501/contracts/test/ERC5501CombinedCollection.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Combined.sol"; - -contract ERC5501CombinedTestCollection is ERC5501Combined { - - constructor(string memory name_, string memory symbol_) ERC5501Combined(name_,symbol_) {} - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501EnumerableTestCollection.sol b/assets/eip-5501/contracts/test/ERC5501EnumerableTestCollection.sol deleted file mode 100644 index eed0b4bbc41cab..00000000000000 --- a/assets/eip-5501/contracts/test/ERC5501EnumerableTestCollection.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Enumerable.sol"; - -contract ERC5501EnumerableTestCollection is ERC5501Enumerable { - - constructor(string memory name_, string memory symbol_) ERC5501Enumerable(name_,symbol_) {} - - function getUserBalances(address user) external view returns (uint256[] memory) { - return _userBalances[user]; - } - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501TerminableTestCollection.sol b/assets/eip-5501/contracts/test/ERC5501TerminableTestCollection.sol deleted file mode 100644 index 1ba6f8eab50f7c..00000000000000 --- a/assets/eip-5501/contracts/test/ERC5501TerminableTestCollection.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501Terminable.sol"; - -contract ERC5501TerminableTestCollection is ERC5501Terminable { - - constructor(string memory name_, string memory symbol_) ERC5501Terminable(name_,symbol_) {} - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/contracts/test/ERC5501TestCollection.sol b/assets/eip-5501/contracts/test/ERC5501TestCollection.sol deleted file mode 100644 index 67429ad631b3a5..00000000000000 --- a/assets/eip-5501/contracts/test/ERC5501TestCollection.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "../ERC5501.sol"; - -contract ERC5501TestCollection is ERC5501 { - - constructor(string memory name_, string memory symbol_) ERC5501(name_,symbol_) {} - - function mint(address to, uint256 tokenId) public { - _mint(to, tokenId); - } -} diff --git a/assets/eip-5501/test/ERC5501BalanceTest.ts b/assets/eip-5501/test/ERC5501BalanceTest.ts deleted file mode 100644 index a936ca00b1bf67..00000000000000 --- a/assets/eip-5501/test/ERC5501BalanceTest.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; - -describe("ERC5501BalanceTest", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - // Fri Jan 01 2021 00:00:00 GMT+0000 - const expired = 1609459200; - - const expires = (await time.latest()) + fastForwardYear - 1; - - const [owner, delegatee] = await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501BalanceTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - await contract.mint(owner.address, 2); - await contract.mint(owner.address, 3); - await contract.mint(owner.address, 4); - await contract.mint(owner.address, 5); - await contract.mint(owner.address, 6); - await contract.mint(owner.address, 7); - - return { contract, owner, delegatee, expires, expired, fastForwardYear }; - } - - it("Returns correct balance of user", async function () { - const { contract, owner, delegatee, expires, expired, fastForwardYear } = - await loadFixture(initialize); - - await contract.setUser(1, delegatee.address, expires, false); - await contract.setUser(2, delegatee.address, expires, false); - await contract.setUser(3, delegatee.address, expires, false); - await contract.setUser(4, delegatee.address, expired, false); - await contract.setUser(5, delegatee.address, expired, false); - await contract.setUser(6, delegatee.address, expired, false); - await contract.setUser(7, delegatee.address, expired, false); - - // flush function is called for user parameter - meaning flush does not happen for delegatee if user parameter is different address - await contract.setUser(2, owner.address, expires, false); - // delegatee is user of 1, 3 - // delegatee balances array is 1, 2, 3, 7 - - expect(await contract.userBalanceOf(delegatee.address)).to.equal(2); - expect(await contract.getUserBalances(delegatee.address)).to.deep.equal([ - BigNumber.from("1"), - BigNumber.from("2"), - BigNumber.from("3"), - BigNumber.from("7"), - ]); - - await time.increaseTo((await time.latest()) + fastForwardYear); - await contract.setUser( - 1, - delegatee.address, - expires + fastForwardYear, - false - ); - - expect(await contract.userBalanceOf(delegatee.address)).to.equal(1); - expect(await contract.getUserBalances(delegatee.address)).to.deep.equal([ - BigNumber.from("1"), - ]); - - await time.increaseTo((await time.latest()) + fastForwardYear); - expect(await contract.userBalanceOf(delegatee.address)).to.equal(0); - }); - - it("Revert user balance query for zero address", async function () { - const { contract } = await loadFixture(initialize); - - await expect( - contract.userBalanceOf(ethers.constants.AddressZero) - ).to.be.revertedWith("ERC5501Balance: address zero is not a valid owner"); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0x0cb22289")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501CombinedTest.ts b/assets/eip-5501/test/ERC5501CombinedTest.ts deleted file mode 100644 index da8bb8d3923d4a..00000000000000 --- a/assets/eip-5501/test/ERC5501CombinedTest.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; - -describe("ERC5501CombinedTest", function () { - async function initialize() { - // 7 * 24 * 60 * 60 - const week = 604800; - - const uint64MaxValue = BigNumber.from("18446744073709551615"); - - const [owner, delegatee, borrower, rentalContractMock] = - await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501CombinedTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - - return { - contract, - owner, - delegatee, - borrower, - rentalContractMock, - week, - uint64MaxValue, - }; - } - - it("Scenario", async function () { - const { - contract, - owner, - delegatee, - borrower, - rentalContractMock, - week, - uint64MaxValue, - } = await loadFixture(initialize); - - // owner delegates NFT to hot wallet for security - await expect(contract.setUser(1, delegatee.address, uint64MaxValue, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, uint64MaxValue, false); - expect(await contract.userBalanceOf(delegatee.address)).to.equal(1); - expect(await contract.userOf(1)).to.equal(delegatee.address); - expect(await contract.tokenOfUserByIndex(delegatee.address, 0)).to.equal(1); - expect(await contract.userExpires(1)).to.equal(uint64MaxValue); - expect(await contract.userIsBorrowed(1)).to.equal(false); - - // owner then decides to lend the NFT for one week - await contract.setApprovalForAll(rentalContractMock.address, true); - const oneWeekLater = (await time.latest()) + week; - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, oneWeekLater, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, oneWeekLater, true); - expect(await contract.userBalanceOf(delegatee.address)).to.equal(0); - expect(await contract.userBalanceOf(borrower.address)).to.equal(1); - expect(await contract.tokenOfUserByIndex(borrower.address, 0)).to.equal(1); - expect(await contract.userOf(1)).to.equal(borrower.address); - expect(await contract.userExpires(1)).to.equal(oneWeekLater); - expect(await contract.userIsBorrowed(1)).to.equal(true); - - // borrow expires - await time.increaseTo((await time.latest()) + oneWeekLater + 1); - - // owner decides to lend the NFT again - // this time, they accidentally set wrong time - // the owner and borrower agree to terminate the loan under certain conditions - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, uint64MaxValue, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, uint64MaxValue, true); - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - await expect(contract.terminateBorrow(1)) - .to.emit(contract, "TerminateBorrow") - .withArgs(1, owner.address, borrower.address, owner.address) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0xf808ec37")).to.equal(true); - expect(await contract.supportsInterface("0x0cb22289")).to.equal(true); - expect(await contract.supportsInterface("0x1d350ef8")).to.equal(true); - expect(await contract.supportsInterface("0x6a26417e")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501EnumerableTest.ts b/assets/eip-5501/test/ERC5501EnumerableTest.ts deleted file mode 100644 index 9717910db41015..00000000000000 --- a/assets/eip-5501/test/ERC5501EnumerableTest.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("ERC5501EnumerableTest", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - // allows to set multiple tokens which will expire after fastForwardYear - const expired = (await time.latest()) + fastForwardYear - 1; - - const expires = (await time.latest()) + fastForwardYear + fastForwardYear; - - const [owner, delegatee] = await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501EnumerableTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - await contract.mint(owner.address, 2); - await contract.mint(owner.address, 3); - await contract.mint(owner.address, 4); - await contract.mint(owner.address, 5); - await contract.mint(owner.address, 6); - await contract.mint(owner.address, 7); - - return { contract, owner, delegatee, expires, expired, fastForwardYear }; - } - - it("Return correct user tokens by index", async function () { - const { contract, owner, delegatee, expires, expired, fastForwardYear } = - await loadFixture(initialize); - - await contract.setUser(1, delegatee.address, expires, false); - await contract.setUser(2, delegatee.address, expired, false); - await contract.setUser(3, delegatee.address, expires, false); - await contract.setUser(4, delegatee.address, expired, false); - await contract.setUser(5, delegatee.address, expires, false); - await contract.setUser(6, delegatee.address, expired, false); - await contract.setUser(7, delegatee.address, expires, false); - - expect(await contract.tokenOfUserByIndex(delegatee.address, 0)).to.equal(1); - expect(await contract.tokenOfUserByIndex(delegatee.address, 1)).to.equal(2); - expect(await contract.tokenOfUserByIndex(delegatee.address, 2)).to.equal(3); - expect(await contract.tokenOfUserByIndex(delegatee.address, 3)).to.equal(4); - expect(await contract.tokenOfUserByIndex(delegatee.address, 4)).to.equal(5); - expect(await contract.tokenOfUserByIndex(delegatee.address, 5)).to.equal(6); - expect(await contract.tokenOfUserByIndex(delegatee.address, 6)).to.equal(7); - - // fast forward one year, token 2, 4, 6 expired for user - // current balance: 1, 3, 5, 7 - await time.increaseTo((await time.latest()) + fastForwardYear); - - expect(await contract.tokenOfUserByIndex(delegatee.address, 0)).to.equal(1); - expect(await contract.tokenOfUserByIndex(delegatee.address, 1)).to.equal(3); - expect(await contract.tokenOfUserByIndex(delegatee.address, 2)).to.equal(5); - expect(await contract.tokenOfUserByIndex(delegatee.address, 3)).to.equal(7); - await expect( - contract.tokenOfUserByIndex(delegatee.address, 4) - ).to.be.revertedWith("ERC5501Enumerable: owner index out of bounds"); - }); - - it("Revert user token id by index query for zero address", async function () { - const { contract } = await loadFixture(initialize); - - await expect( - contract.tokenOfUserByIndex(ethers.constants.AddressZero, 0) - ).to.be.revertedWith("ERC5501Enumerable: address zero is not a valid owner"); - }); - - it("Revert user token id by index query for out of bounds index", async function () { - const { contract, delegatee } = await loadFixture(initialize); - - await expect( - contract.tokenOfUserByIndex(delegatee.address, 0) - ).to.be.revertedWith("ERC5501Enumerable: owner index out of bounds"); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0x1d350ef8")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501TerminableTest.ts b/assets/eip-5501/test/ERC5501TerminableTest.ts deleted file mode 100644 index c3da145a6f9704..00000000000000 --- a/assets/eip-5501/test/ERC5501TerminableTest.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; - -describe("ERC5501TerminableTest", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - - const expires = (await time.latest()) + fastForwardYear - 1; - - const uint64MaxValue = BigNumber.from("18446744073709551615"); - - const [owner, delegatee, borrower] = await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501TerminableTestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - - return { - contract, - owner, - delegatee, - borrower, - uint64MaxValue, - expires, - fastForwardYear, - }; - } - - it("Cannot terminate borrow without approval of both parties", async function () { - const { contract, borrower, uint64MaxValue } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, borrower.address, uint64MaxValue, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, uint64MaxValue, true); - await expect(contract.terminateBorrow(1)).to.be.revertedWith( - "ERC5501Terminable: not agreed" - ); - }); - - it("Cannot set borrow termination if borrow is not active", async function () { - const { contract, delegatee, uint64MaxValue } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, delegatee.address, uint64MaxValue, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, uint64MaxValue, false); - await expect(contract.setBorrowTermination(1)).to.be.revertedWith( - "ERC5501Terminable: borrow not active" - ); - }); - - it("Can reset borrow if owner mistakenly borrowed token to own wallet and set a long duration", async function () { - const { contract, owner, uint64MaxValue } = await loadFixture(initialize); - - await expect(contract.setUser(1, owner.address, uint64MaxValue, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, owner.address, uint64MaxValue, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await expect(contract.terminateBorrow(1)) - .to.emit(contract, "TerminateBorrow") - .withArgs(1, owner.address, owner.address, owner.address) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - expect(await contract.userIsBorrowed(1)).to.equal(false); - }); - - it("Can reset borrow and set a new user if both parties agree", async function () { - const { contract, owner, delegatee, borrower, uint64MaxValue } = - await loadFixture(initialize); - - await expect(contract.setUser(1, borrower.address, uint64MaxValue, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, uint64MaxValue, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - false, - ]); - - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await expect(contract.terminateBorrow(1)) - .to.emit(contract, "TerminateBorrow") - .withArgs(1, owner.address, borrower.address, owner.address) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - expect(await contract.userIsBorrowed(1)).to.equal(false); - - await expect(contract.setUser(1, delegatee.address, 0, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, 0, false); - }); - - it("Agreed borrow terminations must be reset if userOf is changed", async function () { - const { contract, owner, delegatee, borrower, expires, fastForwardYear } = - await loadFixture(initialize); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await time.increaseTo((await time.latest()) + fastForwardYear); - - await expect(contract.setUser(1, delegatee.address, 0, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, 0, false) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - }); - - it("Reset termination agreements if token is transferred", async function () { - const { contract, owner, delegatee, borrower, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - - await expect(contract.setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, owner.address, true); - await expect(contract.connect(borrower).setBorrowTermination(1)) - .to.emit(contract, "AgreeToTerminateBorrow") - .withArgs(1, borrower.address, false); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - true, - true, - ]); - - await expect( - contract["safeTransferFrom(address,address,uint256)"]( - owner.address, - delegatee.address, - 1 - ) - ) - .to.emit(contract, "ResetTerminationAgreements") - .withArgs(1); - - expect(await contract.getBorrowTermination(1)).to.have.ordered.members([ - false, - false, - ]); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0x6a26417e")).to.equal(true); - }); -}); diff --git a/assets/eip-5501/test/ERC5501Test.ts b/assets/eip-5501/test/ERC5501Test.ts deleted file mode 100644 index 0be415a2450e64..00000000000000 --- a/assets/eip-5501/test/ERC5501Test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -describe("ERC5501Test", function () { - async function initialize() { - // 365 * 24 * 60 * 60 - const fastForwardYear = 31536000; - - const expires = (await time.latest()) + fastForwardYear - 1; - - const [owner, delegatee, borrower, rentalContractMock] = - await ethers.getSigners(); - - const contractFactory = await ethers.getContractFactory( - "ERC5501TestCollection" - ); - const contract = await contractFactory.deploy("Test Collection", "TEST"); - - await contract.mint(owner.address, 1); - - return { - contract, - owner, - delegatee, - borrower, - rentalContractMock, - expires, - fastForwardYear, - }; - } - - it("Operator is not owner or approved", async function () { - const { contract, borrower } = await loadFixture(initialize); - - await expect( - contract.connect(borrower).setUser(1, borrower.address, 0, false) - ).to.be.revertedWith( - "ERC5501: set user caller is not token owner or approved" - ); - }); - - it("User cannot be zero address", async function () { - const { contract } = await loadFixture(initialize); - - await expect( - contract.setUser(1, ethers.constants.AddressZero, 0, false) - ).to.be.revertedWith("ERC5501: set user to zero address"); - }); - - it("Revert userOf if not set or expired", async function () { - const { contract } = await loadFixture(initialize); - - await expect(contract.userOf(1)).to.be.revertedWith( - "ERC5501: user does not exist for this token" - ); - }); - - it("Cannot set user if NFT is borrowed", async function () { - const { contract, delegatee, borrower, rentalContractMock, expires } = - await loadFixture(initialize); - - await contract.setApprovalForAll(rentalContractMock.address, true); - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, expires, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - await expect( - contract.setUser(1, delegatee.address, 0, false) - ).to.be.revertedWith("ERC5501: token is borrowed"); - }); - - it("Can delegate and redelegate user", async function () { - const { contract, owner, delegatee, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, owner.address, expires, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, owner.address, expires, false); - await expect(contract.setUser(1, delegatee.address, expires, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, expires, false); - }); - - it("Can set user after borrow expires", async function () { - const { contract, delegatee, borrower, expires, fastForwardYear } = - await loadFixture(initialize); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - await time.increaseTo((await time.latest()) + fastForwardYear); - await expect(contract.setUser(1, delegatee.address, 0, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, 0, false); - }); - - it("User is reset if NFT is not borrowed and transferred", async function () { - const { contract, owner, delegatee, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, delegatee.address, expires, false)) - .to.emit(contract, "UpdateUser") - .withArgs(1, delegatee.address, expires, false); - await expect( - contract["safeTransferFrom(address,address,uint256)"]( - owner.address, - delegatee.address, - 1 - ) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, ethers.constants.AddressZero, 0, false); - - await expect(contract.userOf(1)).to.be.revertedWith( - "ERC5501: user does not exist for this token" - ); - expect(await contract.userExpires(1)).to.equal(0); - expect(await contract.userIsBorrowed(1)).to.equal(false); - }); - - it("User is not reset if NFT is borrowed and transferred", async function () { - const { contract, owner, delegatee, borrower, expires } = await loadFixture( - initialize - ); - - await expect(contract.setUser(1, borrower.address, expires, true)) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - await contract["safeTransferFrom(address,address,uint256)"]( - owner.address, - delegatee.address, - 1 - ); - - expect(await contract.userOf(1)).to.equal(borrower.address); - expect(await contract.userExpires(1)).to.equal(expires); - expect(await contract.userIsBorrowed(1)).to.equal(true); - }); - - it("Rental contract can set user", async function () { - const { contract, borrower, rentalContractMock, expires } = - await loadFixture(initialize); - - await contract.setApprovalForAll(rentalContractMock.address, true); - await expect( - contract - .connect(rentalContractMock) - .setUser(1, borrower.address, expires, true) - ) - .to.emit(contract, "UpdateUser") - .withArgs(1, borrower.address, expires, true); - - expect(await contract.userOf(1)).to.equal(borrower.address); - expect(await contract.userExpires(1)).to.equal(expires); - expect(await contract.userIsBorrowed(1)).to.equal(true); - }); - - it("Supports interface", async function () { - const { contract } = await loadFixture(initialize); - - expect(await contract.supportsInterface("0xf808ec37")).to.equal(true); - }); -}); diff --git a/assets/eip-5516/ERC5516.sol b/assets/eip-5516/ERC5516.sol deleted file mode 100644 index 8e4a6e471c0a15..00000000000000 --- a/assets/eip-5516/ERC5516.sol +++ /dev/null @@ -1,819 +0,0 @@ -//SPDX-License-Identifier: CC0-1.0 - -/** - * @notice Reference implementation of the eip-5516 interface. - * Note: this implementation only allows for each user to own only 1 token type for each `id`. - * @author Matias Arazi , Lucas Martín Grasso Ramos - * See https://github.com/ethereum/EIPs/pull/5516 - * - */ - -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "./IERC5516.sol"; - -contract ERC5516 is Context, ERC165, IERC1155, IERC1155MetadataURI, IERC5516 { - using Address for address; - - // Used for making each token unique, Maintains ID registry and quantity of tokens minted. - uint256 private nonce; - - // Used as the URI for all token types by relying on ID substitution, e.g. https://ipfs.io/ipfs/token.data - string private _uri; - - // Mapping from token ID to account balances - mapping(address => mapping(uint256 => bool)) private _balances; - - // Mapping from address to mapping id bool that states if address has tokens(under id) awaiting to be claimed - mapping(address => mapping(uint256 => bool)) private _pendings; - - // Mapping from account to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // Mapping from ID to minter address. - mapping(uint256 => address) private _tokenMinters; - - // Mapping from ID to URI. - mapping(uint256 => string) private _tokenURIs; - - /** - * @dev Sets base uri for tokens. Preferably "https://ipfs.io/ipfs/" - */ - constructor(string memory uri_) { - _uri = uri_; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC165, IERC165) - returns (bool) - { - return - interfaceId == type(IERC1155).interfaceId || - interfaceId == type(IERC1155MetadataURI).interfaceId || - interfaceId == type(IERC5516).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev See {IERC1155MetadataURI-uri}. - */ - function uri(uint256 _id) - external - view - virtual - override - returns (string memory) - { - return string(abi.encodePacked(_uri, _tokenURIs[_id])); - } - - /** - * @dev See {IERC1155-balanceOf}. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - */ - function balanceOf(address account, uint256 id) - public - view - virtual - override - returns (uint256) - { - require(account != address(0), "EIP5516: Address zero error"); - if (_balances[account][id]) { - return 1; - } else { - return 0; - } - } - - /** - * @dev See {IERC1155-balanceOfBatch}. - * - * Requirements: - * - * - `accounts` and `ids` must have the same length. - * - */ - function balanceOfBatch(address[] memory accounts, uint256[] memory ids) - public - view - virtual - override - returns (uint256[] memory) - { - require( - accounts.length == ids.length, - "EIP5516: Array lengths mismatch" - ); - - uint256[] memory batchBalances = new uint256[](accounts.length); - - for (uint256 i = 0; i < accounts.length; ++i) { - batchBalances[i] = balanceOf(accounts[i], ids[i]); - } - - return batchBalances; - } - - /** - * @dev Get tokens owned by a given address - * - * Requirements: - * - * - `account` cannot be the zero address. - * - */ - function tokensFrom(address account) - public - view - virtual - override - returns (uint256[] memory) - { - require(account != address(0), "EIP5516: Address zero error"); - - uint256 _tokenCount = 0; - for (uint256 i = 1; i <= nonce; ) { - if (_balances[account][i]) { - unchecked { - ++_tokenCount; - } - } - unchecked { - ++i; - } - } - - uint256[] memory _ownedTokens = new uint256[](_tokenCount); - - for (uint256 i = 1; i <= nonce; ) { - if (_balances[account][i]) { - _ownedTokens[--_tokenCount] = i; - } - unchecked { - ++i; - } - } - - return _ownedTokens; - } - - /** - * @dev Get tokens marked as _pendings of a given address - * - * Requirements: - * - * - `account` cannot be the zero address. - * - */ - function pendingFrom(address account) - public - view - virtual - override - returns (uint256[] memory) - { - require(account != address(0), "EIP5516: Address zero error"); - - uint256 _tokenCount = 0; - - for (uint256 i = 1; i <= nonce; ) { - if (_pendings[account][i]) { - ++_tokenCount; - } - unchecked { - ++i; - } - } - - uint256[] memory _pendingTokens = new uint256[](_tokenCount); - - for (uint256 i = 1; i <= nonce; ) { - if (_pendings[account][i]) { - _pendingTokens[--_tokenCount] = i; - } - unchecked { - ++i; - } - } - - return _pendingTokens; - } - - /** - * @dev See {IERC1155-setApprovalForAll}. - */ - function setApprovalForAll(address operator, bool approved) - public - virtual - override - { - _setApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @dev See {IERC1155-isApprovedForAll}. - */ - function isApprovedForAll(address account, address operator) - public - view - virtual - override - returns (bool) - { - return _operatorApprovals[account][operator]; - } - - /** - * @dev mints(creates) a token - */ - function _mint(address account, string memory data) internal virtual { - unchecked { - ++nonce; - } - - address operator = _msgSender(); - uint256[] memory ids = _asSingletonArray(nonce); - uint256[] memory amounts = _asSingletonArray(1); - bytes memory _bData = bytes(data); - - _beforeTokenTransfer( - operator, - address(0), - operator, - ids, - amounts, - _bData - ); - _tokenURIs[nonce] = data; - _tokenMinters[nonce] = account; - emit TransferSingle(operator, address(0), operator, nonce, 1); - _afterTokenTransfer( - operator, - address(0), - operator, - ids, - amounts, - _bData - ); - } - - /** - * @dev See {IERC1155-safeTransferFrom}. - * - * Requirements: - * - * - `from` must be the creator(minter) of `id` or must have allowed _msgSender() as an operator. - * - */ - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public virtual override { - require(amount == 1, "EIP5516: Can only transfer one token"); - require( - _msgSender() == _tokenMinters[id] || - isApprovedForAll(_tokenMinters[id], _msgSender()), - "EIP5516: Unauthorized" - ); - - _safeTransferFrom(from, to, id, amount, data); - } - - /** - * @dev See {eip-5516-batchTransfer} - * - * Requirements: - * - * - 'from' must be the creator(minter) of `id` or must have allowed _msgSender() as an operator. - * - */ - function batchTransfer( - address from, - address[] memory to, - uint256 id, - uint256 amount, - bytes memory data - ) external virtual override { - require(amount == 1, "EIP5516: Can only transfer one token"); - require( - _msgSender() == _tokenMinters[id] || - isApprovedForAll(_tokenMinters[id], _msgSender()), - "EIP5516: Unauthorized" - ); - - _batchTransfer(from, to, id, amount, data); - } - - /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. - * - * Emits a {TransferSingle} event. - * - * Requirements: - * - * - `from` must be the creator(minter) of the token under `id`. - * - `to` must be non-zero. - * - `to` must have the token `id` marked as _pendings. - * - `to` must not own a token type under `id`. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the - * acceptance magic value. - * - */ - function _safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) internal virtual { - require(from != address(0), "EIP5516: Address zero error"); - require( - _pendings[to][id] == false && _balances[to][id] == false, - "EIP5516: Already Assignee" - ); - - address operator = _msgSender(); - - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(amount); - - _beforeTokenTransfer(operator, from, to, ids, amounts, data); - - _pendings[to][id] = true; - - emit TransferSingle(operator, from, to, id, amount); - _afterTokenTransfer(operator, from, to, ids, amounts, data); - - _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); - } - - /** - * Transfers `id` token from `from` to every address at `to[]`. - * - * Requirements: - * - See {eip-5516-safeMultiTransfer}. - * - */ - function _batchTransfer( - address from, - address[] memory to, - uint256 id, - uint256 amount, - bytes memory data - ) internal virtual { - address operator = _msgSender(); - - _beforeBatchedTokenTransfer(operator, from, to, id, data); - - for (uint256 i = 0; i < to.length; ) { - address _to = to[i]; - - require(_to != address(0), "EIP5516: Address zero error"); - require( - _pendings[_to][id] == false && _balances[_to][id] == false, - "EIP5516: Already Assignee" - ); - - _pendings[_to][id] = true; - - unchecked { - ++i; - } - } - - emit TransferMulti(operator, from, to, amount, id); - - _beforeBatchedTokenTransfer(operator, from, to, id, data); - } - - /** - * @dev See {eip-5516-claimOrReject} - * - * If action == true: Claims pending token under `id`. - * Else, rejects pending token under `id`. - * - */ - function claimOrReject( - address account, - uint256 id, - bool action - ) external virtual override { - require(_msgSender() == account, "EIP5516: Unauthorized"); - - _claimOrReject(account, id, action); - } - - /** - * @dev See {eip-5516-claimOrReject} - * - * For each `id` - `action` pair: - * - * If action == true: Claims pending token under `id`. - * Else, rejects pending token under `id`. - * - */ - function claimOrRejectBatch( - address account, - uint256[] memory ids, - bool[] memory actions - ) external virtual override { - require( - ids.length == actions.length, - "EIP5516: Array lengths mismatch" - ); - - require(_msgSender() == account, "EIP5516: Unauthorized"); - - _claimOrRejectBatch(account, ids, actions); - } - - /** - * @dev Claims or Reject pending token under `_id` from address `_account`. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have a _pendings token under `id` at the moment of call. - * - `account` mUST not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function _claimOrReject( - address account, - uint256 id, - bool action - ) internal virtual { - require( - _pendings[account][id] == true && _balances[account][id] == false, - "EIP5516: Not claimable" - ); - - address operator = _msgSender(); - - bool[] memory actions = new bool[](1); - actions[0] = action; - uint256[] memory ids = _asSingletonArray(id); - - _beforeTokenClaim(operator, account, actions, ids); - - _balances[account][id] = action; - _pendings[account][id] = false; - - delete _pendings[account][id]; - - emit TokenClaimed(operator, account, actions, ids); - - _afterTokenClaim(operator, account, actions, ids); - } - - /** - * @dev Claims or Reject _pendings `_id` from address `_account`. - * - * For each `id`-`action` pair: - * - * Requirements: - * - `account` cannot be the zero address. - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function _claimOrRejectBatch( - address account, - uint256[] memory ids, - bool[] memory actions - ) internal virtual { - uint256 totalIds = ids.length; - address operator = _msgSender(); - - _beforeTokenClaim(operator, account, actions, ids); - - for (uint256 i = 0; i < totalIds; ) { - uint256 id = ids[i]; - - require( - _pendings[account][id] == true && - _balances[account][id] == false, - "EIP5516: Not claimable" - ); - - _balances[account][id] = actions[i]; - _pendings[account][id] = false; - - delete _pendings[account][id]; - - unchecked { - ++i; - } - } - - emit TokenClaimed(operator, account, actions, ids); - - _afterTokenClaim(operator, account, actions, ids); - } - - /** - * @dev Destroys `id` token from `account` - * - * Emits a {TransferSingle} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` must own a token under `id`. - * - */ - function _burn(address account, uint256 id) internal virtual { - require(_balances[account][id] == true, "EIP5516: Unauthorized"); - - address operator = _msgSender(); - uint256[] memory ids = _asSingletonArray(id); - uint256[] memory amounts = _asSingletonArray(1); - - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - - delete _balances[account][id]; - - emit TransferSingle(operator, account, address(0), id, 1); - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - } - - /** - * @dev Destroys all tokens under `ids` from `account` - * - * Emits a {TransferBatch} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` must own all tokens under `ids`. - * - */ - function _burnBatch(address account, uint256[] memory ids) - internal - virtual - { - uint256 totalIds = ids.length; - address operator = _msgSender(); - uint256[] memory amounts = _asSingletonArray(totalIds); - uint256[] memory values = _asSingletonArray(0); - - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - - for (uint256 i = 0; i < totalIds; ) { - uint256 id = ids[i]; - - require(_balances[account][id] == true, "EIP5516: Unauthorized"); - - delete _balances[account][id]; - - unchecked { - ++i; - } - } - - emit TransferBatch(operator, account, address(0), ids, values); - - _afterTokenTransfer(operator, account, address(0), ids, amounts, ""); - } - - /** - * @dev Approve `operator` to operate on all of `owner` tokens - * - * Emits a {ApprovalForAll} event. - * - */ - function _setApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "ERC1155: setting approval status for self"); - _operatorApprovals[owner][operator] = approved; - emit ApprovalForAll(owner, operator, approved); - } - - /** - * @dev Hook that is called before any token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `ids` and `amounts` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called after any token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called before any batched token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeBatchedTokenTransfer( - address operator, - address from, - address[] memory to, - uint256 id, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called after any batched token transfer. This includes minting - * and burning, as well as batched variants. - * - * The same hook is called on both single and batched variants. For single - * transfers, the length of the `id` and `amount` arrays will be 1. - * - * Calling conditions (for each `id` and `amount` pair): - * - * - `amount` will always be and must be equal to 1. - * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * of token type `id` will be transferred to `to`. - * - When `from` is zero, `amount` tokens of token type `id` will be minted - * for `to`. - * - When `to` is zero, `amount` of ``from``'s tokens of token type `id` - * will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterBatchedTokenTransfer( - address operator, - address from, - address[] memory to, - uint256 id, - bytes memory data - ) internal virtual {} - - /** - * @dev Hook that is called before any token claim. - + - * Calling conditions (for each `action` and `id` pair): - * - * - A token under `id` must exist. - * - When `action` is non-zero, a token under `id` will now be claimed and owned by`operator`. - * - When `action` is false, a token under `id` will now be rejected. - * - */ - function _beforeTokenClaim( - address operator, - address account, - bool[] memory actions, - uint256[] memory ids - ) internal virtual {} - - /** - * @dev Hook that is called after any token claim. - + - * Calling conditions (for each `action` and `id` pair): - * - * - A token under `id` must exist. - * - When `action` is non-zero, a token under `id` is now owned by`operator`. - * - When `action` is false, a token under `id` was rejected. - * - */ - function _afterTokenClaim( - address operator, - address account, - bool[] memory actions, - uint256[] memory ids - ) internal virtual {} - - function _asSingletonArray(uint256 element) - private - pure - returns (uint256[] memory) - { - uint256[] memory array = new uint256[](1); - array[0] = element; - - return array; - } - - /** - * @dev see {ERC1155-_doSafeTransferAcceptanceCheck, IERC1155Receivable} - */ - function _doSafeTransferAcceptanceCheck( - address operator, - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) private { - if (to.isContract()) { - try - IERC1155Receiver(to).onERC1155Received( - operator, - from, - id, - amount, - data - ) - returns (bytes4 response) { - if (response != IERC1155Receiver.onERC1155Received.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); - } - } catch Error(string memory reason) { - revert(reason); - } catch { - revert("ERC1155: transfer to non-ERC1155Receiver implementer"); - } - } - } - - /** - * @dev Unused/Deprecated function - * @dev See {IERC1155-safeBatchTransferFrom}. - */ - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public virtual override {} -} diff --git a/assets/eip-5516/IERC5516.sol b/assets/eip-5516/IERC5516.sol deleted file mode 100644 index a827f9578aa982..00000000000000 --- a/assets/eip-5516/IERC5516.sol +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.4; - -/** - @title Soulbound, Multi-Token standard. - @notice Interface of the EIP-5516 - Note: The ERC-165 identifier for this interface is 0x8314f22b. - */ - -interface IERC5516 { - /** - * @dev Emitted when `account` claims or rejects pending tokens under `ids[]`. - */ - event TokenClaimed( - address indexed operator, - address indexed account, - bool[] actions, - uint256[] ids - ); - - /** - * @dev Emitted when `from` transfers token under `id` to every address at `to[]`. - */ - event TransferMulti( - address indexed operator, - address indexed from, - address[] to, - uint256 amount, - uint256 id - ); - - /** - * @dev Get tokens owned by a given address. - */ - function tokensFrom(address from) external view returns (uint256[] memory); - - /** - * @dev Get tokens awaiting to be claimed by a given address. - */ - function pendingFrom(address from) external view returns (uint256[] memory); - - /** - * @dev Claims or Reject pending `id`. - * - * Requirements: - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function claimOrReject( - address account, - uint256 id, - bool action - ) external; - - /** - * @dev Claims or Reject pending tokens under `ids[]`. - * - * Requirements for each `id` `action` pair: - * - `account` must have a pending token under `id` at the moment of call. - * - `account` must not own a token under `id` at the moment of call. - * - * Emits a {TokenClaimed} event. - * - */ - function claimOrRejectBatch( - address account, - uint256[] memory ids, - bool[] memory actions - ) external; - - /** - * @dev Transfers `id` token from `from` to every address at `to[]`. - * - * Requirements: - * - * - `from` MUST be the creator(minter) of `id`. - * - All addresses in `to[]` MUST be non-zero. - * - All addresses in `to[]` MUST have the token `id` under `_pendings`. - * - All addresses in `to[]` MUST not own a token type under `id`. - * - * Emits a {TransfersMulti} event. - * - */ - function batchTransfer( - address from, - address[] memory to, - uint256 id, - uint256 amount, - bytes memory data - ) external; - -} diff --git a/assets/eip-5521/ERC_5521.sol b/assets/eip-5521/ERC_5521.sol deleted file mode 100644 index aee6a3aee50849..00000000000000 --- a/assets/eip-5521/ERC_5521.sol +++ /dev/null @@ -1,170 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./IERC_5521.sol"; - -contract ERC_5521 is ERC721, IERC_5521, TargetContract { - - struct Relationship { - mapping (address => uint256[]) referring; - mapping (address => uint256[]) referred; - uint256 createdTimestamp; // unix timestamp when the rNFT is being created - } - - mapping (uint256 => Relationship) internal _relationship; - address contractOwner = address(0); - - mapping (uint256 => address[]) private referringKeys; - mapping (uint256 => address[]) private referredKeys; - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) { - contractOwner = msg.sender; - } - - function safeMint(uint256 tokenId, address[] memory addresses, uint256[][] memory _tokenIds) public { - // require(msg.sender == contractOwner, "ERC_rNFT: Only contract owner can mint"); - _safeMint(msg.sender, tokenId); - setNode(tokenId, addresses, _tokenIds); - } - - /// @notice set the referred list of an rNFT associated with different contract addresses and update the referring list of each one in the referred list - /// @param tokenIds array of rNFTs, recommended to check duplication at the caller's end - function setNode(uint256 tokenId, address[] memory addresses, uint256[][] memory tokenIds) public virtual override { - require( - addresses.length == tokenIds.length, - "Addresses and TokenID arrays must have the same length" - ); - for (uint i = 0; i < tokenIds.length; i++) { - if (tokenIds[i].length == 0) { revert("ERC_5521: the referring list cannot be empty"); } - } - setNodeReferring(addresses, tokenId, tokenIds); - setNodeReferred(addresses, tokenId, tokenIds); - } - - /// @notice set the referring list of an rNFT associated with different contract addresses - /// @param _tokenIds array of rNFTs associated with addresses, recommended to check duplication at the caller's end - function setNodeReferring(address[] memory addresses, uint256 tokenId, uint256[][] memory _tokenIds) private { - require(_isApprovedOrOwner(msg.sender, tokenId), "ERC_5521: transfer caller is not owner nor approved"); - - Relationship storage relationship = _relationship[tokenId]; - - for (uint i = 0; i < addresses.length; i++) { - if (relationship.referring[addresses[i]].length == 0) { referringKeys[tokenId].push(addresses[i]); } // Add the address if it's a new entry - relationship.referring[addresses[i]] = _tokenIds[i]; - } - - relationship.createdTimestamp = block.timestamp; - emitEvents(tokenId, msg.sender); - } - - /// @notice set the referred list of an rNFT associated with different contract addresses - /// @param _tokenIds array of rNFTs associated with addresses, recommended to check duplication at the caller's end - function setNodeReferred(address[] memory addresses, uint256 tokenId, uint256[][] memory _tokenIds) private { - for (uint i = 0; i < addresses.length; i++) { - if (addresses[i] == address(this)) { - for (uint j = 0; j < _tokenIds[i].length; j++) { - if (_relationship[_tokenIds[i][j]].referred[addresses[i]].length == 0) { referredKeys[_tokenIds[i][j]].push(addresses[i]); } // Add the address if it's a new entry - Relationship storage relationship = _relationship[_tokenIds[i][j]]; - - require(tokenId != _tokenIds[i][j], "ERC_5521: self-reference not allowed"); - if (relationship.createdTimestamp >= block.timestamp) { revert("ERC_5521: the referred rNFT needs to be a predecessor"); } // Make sure the reference complies with the timing sequence - - relationship.referred[address(this)].push(tokenId); - emitEvents(_tokenIds[i][j], ownerOf(_tokenIds[i][j])); - } - } else { - TargetContract targetContractInstance = TargetContract(addresses[i]); - targetContractInstance.setNodeReferredExternal(address(this), tokenId, _tokenIds[i]); - } - } - } - - /// @notice set the referred list of an rNFT associated with different contract addresses - /// @param _tokenIds array of rNFTs associated with addresses, recommended to check duplication at the caller's end - function setNodeReferredExternal(address _address, uint256 tokenId, uint256[] memory _tokenIds) external { - for (uint i = 0; i < _tokenIds.length; i++) { - if (_relationship[_tokenIds[i]].referred[_address].length == 0) { referredKeys[_tokenIds[i]].push(_address); } // Add the address if it's a new entry - Relationship storage relationship = _relationship[_tokenIds[i]]; - - require(_address != address(this), "ERC_5521: this must be an external contract address"); - if (relationship.createdTimestamp >= block.timestamp) { revert("ERC_5521: the referred rNFT needs to be a predecessor"); } // Make sure the reference complies with the timing sequence - - relationship.referred[_address].push(tokenId); - emitEvents(_tokenIds[i], ownerOf(_tokenIds[i])); - } - } - - /// @notice Get the referring list of an rNFT - /// @param tokenId The considered rNFT, _address The corresponding contract address - /// @return The referring mapping of an rNFT - function referringOf(address _address, uint256 tokenId) external view virtual override(IERC_5521, TargetContract) returns (address[] memory, uint256[][] memory) { - address[] memory _referringKeys; - uint256[][] memory _referringValues; - - if (_address == address(this)) { - require(_exists(tokenId), "ERC_5521: token ID not existed"); - (_referringKeys, _referringValues) = convertMap(tokenId, true); - } else { - TargetContract targetContractInstance = TargetContract(_address); - (_referringKeys, _referringValues) = targetContractInstance.referringOf(_address, tokenId); - } - return (_referringKeys, _referringValues); - } - - /// @notice Get the referred list of an rNFT - /// @param tokenId The considered rNFT, _address The corresponding contract address - /// @return The referred mapping of an rNFT - function referredOf(address _address, uint256 tokenId) external view virtual override(IERC_5521, TargetContract) returns (address[] memory, uint256[][] memory) { - address[] memory _referredKeys; - uint256[][] memory _referredValues; - - if (_address == address(this)) { - require(_exists(tokenId), "ERC_5521: token ID not existed"); - (_referredKeys, _referredValues) = convertMap(tokenId, false); - } else { - TargetContract targetContractInstance = TargetContract(_address); - (_referredKeys, _referredValues) = targetContractInstance.referredOf(_address, tokenId); - } - return (_referredKeys, _referredValues); - } - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IERC_5521).interfaceId - || interfaceId == type(TargetContract).interfaceId - || super.supportsInterface(interfaceId); - } - - // @notice Emit an event of UpdateNode - function emitEvents(uint256 tokenId, address sender) private { - (address[] memory _referringKeys, uint256[][] memory _referringValues) = convertMap(tokenId, true); - (address[] memory _referredKeys, uint256[][] memory _referredValues) = convertMap(tokenId, false); - - emit UpdateNode(tokenId, sender, _referringKeys, _referringValues, _referredKeys, _referredValues); - } - - // @notice Convert a specific `local` token mapping to a key array and a value array - function convertMap(uint256 tokenId, bool isReferring) private view returns (address[] memory, uint256[][] memory) { - Relationship storage relationship = _relationship[tokenId]; - - address[] memory returnKeys; - uint256[][] memory returnValues; - - if (isReferring) { - returnKeys = referringKeys[tokenId]; - returnValues = new uint256[][](returnKeys.length); - for (uint i = 0; i < returnKeys.length; i++) { - returnValues[i] = relationship.referring[returnKeys[i]]; - } - } else { - returnKeys = referredKeys[tokenId]; - returnValues = new uint256[][](returnKeys.length); - for (uint i = 0; i < returnKeys.length; i++) { - returnValues[i] = relationship.referred[returnKeys[i]]; - } - } - return (returnKeys, returnValues); - } -} \ No newline at end of file diff --git a/assets/eip-5521/ERC_5521.test.js b/assets/eip-5521/ERC_5521.test.js deleted file mode 100644 index 43c5f4083e22f6..00000000000000 --- a/assets/eip-5521/ERC_5521.test.js +++ /dev/null @@ -1,244 +0,0 @@ -// Right click on the script name and hit "Run" to execute -const { expect } = require("chai"); -const { ethers } = require("hardhat"); - -const TOKEN_NAME = "ERC_5521_NAME"; -const TOKEN_SYMBOL = "ERC_5521_SYMBOL"; -const TOKEN_NAME1 = "ERC_5521_NAME1"; -const TOKEN_SYMBOL1 = "ERC_5521_SYMBOL1"; -const TOKEN_NAME2 = "ERC_5521_NAME2"; -const TOKEN_SYMBOL2 = "ERC_5521_SYMBOL2"; - -function tokenIds2Number(tokenIds) { - return tokenIds.map(tIds => tIds.map(tId => tId.toNumber())); -} - -function assertRelationship(rel, tokenAddresses, tokenIds) { - expect(rel[0]).to.deep.equal(tokenAddresses); - expect(tokenIds2Number(rel[1])).to.deep.equal(tokenIds); -} - -describe("ERC_5521 - single token contract scenario", function () { - let tokenContract1; - - beforeEach(async () => { - const RNFT = await ethers.getContractFactory("ERC_5521"); - const rNFT = await RNFT.deploy(TOKEN_NAME,TOKEN_SYMBOL); - await rNFT.deployed(); - console.log('ERC_5521 deployed at:'+ rNFT.address); - tokenContract1 = rNFT; - }); - - it("should report correct token name and symbol", async function () { - expect((await tokenContract1.symbol())).to.equal(TOKEN_SYMBOL); - expect((await tokenContract1.name())).to.equal(TOKEN_NAME); - }); - - it("can mint a token with empty referredOf and referringOf", async function () { - await tokenContract1.safeMint(1, [], []); - assertRelationship(await tokenContract1.referredOf(tokenContract1.address, 1), [], []); - assertRelationship(await tokenContract1.referringOf(tokenContract1.address, 1), [], []); - }) - - it("cannot query relationships of a non-existent token", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - await tokenContract1.safeMint(2, [tokenContract1.address], [[1]]); - - // tokenContract1 didn't mint any token with id 3 - await expect(tokenContract1.referringOf(tokenContract1.address, 3)).to.be.revertedWith("token ID not existed"); - await expect(tokenContract1.referredOf(tokenContract1.address, 3)).to.be.revertedWith("token ID not existed"); - }) - - it("must not mint two tokens with the same token id", async function () { - await tokenContract1.safeMint(1, [], []); - await expect(tokenContract1.safeMint(1, [], [])).to.be.revertedWith("ERC721: token already minted"); - }) - - it("can mint a token referring to another minted token", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - await tokenContract1.safeMint(2, [tokenContract1.address], [[1]]); - - const referringOfT2 = await tokenContract1.referringOf(tokenContract1.address, 2) - assertRelationship(referringOfT2, [tokenContract1.address], [[1]]); - - const referredOfT2 = await tokenContract1.referredOf(tokenContract1.address, 2) - assertRelationship(referredOfT2, [], []); - - const referringOfT1 = await tokenContract1.referringOf(tokenContract1.address, 1) - assertRelationship(referringOfT1, [], []); - - const referredOfT1 = await tokenContract1.referredOf(tokenContract1.address, 1) - assertRelationship(referredOfT1, [tokenContract1.address], [[2]]); - }) - - it("cannot mint a token referring to a token that is not yet minted", async function () { - await expect(tokenContract1.safeMint(2, [tokenContract1.address], [[1]])).to.be.revertedWith("invalid token ID"); - }) - - it("can mint 3 tokens forming a simple DAG", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - const mintToken2Tx = await tokenContract1.safeMint(2, [tokenContract1.address], [[1]]); - await mintToken2Tx.wait(); - await new Promise(r => setTimeout(r, 1000)); - const mintToken3Tx = await tokenContract1.safeMint(3, [tokenContract1.address], [[1, 2]]); - await mintToken3Tx.wait(); - - const referringOfT2 = await tokenContract1.referringOf(tokenContract1.address, 2) - assertRelationship(referringOfT2, [tokenContract1.address], [[1]]); - - const referredOfT2 = await tokenContract1.referredOf(tokenContract1.address, 2) - assertRelationship(referredOfT2, [tokenContract1.address], [[3]]); - - const referringOfT1 = await tokenContract1.referringOf(tokenContract1.address, 1) - assertRelationship(referringOfT1, [], []); - - const referredOfT1 = await tokenContract1.referredOf(tokenContract1.address, 1) - assertRelationship(referredOfT1, [tokenContract1.address], [[2, 3]]); - - const referringOfT3 = await tokenContract1.referringOf(tokenContract1.address, 3) - assertRelationship(referringOfT3, [tokenContract1.address], [[1, 2]]); - - const referredOfT3 = await tokenContract1.referredOf(tokenContract1.address, 3) - assertRelationship(referredOfT3, [], []); - }) - - it("should revert when trying to create a cycle in the relationship DAG", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - await tokenContract1.safeMint(2, [tokenContract1.address], [[1]]); - await expect(tokenContract1.safeMint(1, [tokenContract1.address], [[2]])).to.be.reverted; - }) - - it("should revert when attempting to create an invalid relationship", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - // Intentionally creating an invalid relationship - await expect(tokenContract1.safeMint(2, [tokenContract1.address], [[1, 2, 3]])).to.be.revertedWith("ERC_5521: self-reference not allowed"); - await expect(tokenContract1.safeMint(2, [tokenContract1.address], [[1, 3]])).to.be.revertedWith("invalid token ID"); - await expect(tokenContract1.safeMint(2, [tokenContract1.address], [])).to.be.revertedWith("Addresses and TokenID arrays must have the same length"); - await expect(tokenContract1.safeMint(2, [tokenContract1.address], [[]])).to.be.revertedWith("the referring list cannot be empty"); - }); -}); - -describe("ERC_5521 - multi token contracts scenario", function () { - let tokenContract1; - let tokenContract2; - - beforeEach(async () => { - const RNFT = await ethers.getContractFactory("ERC_5521"); - - const rNFT1 = await RNFT.deploy(TOKEN_NAME1,TOKEN_SYMBOL1); - await rNFT1.deployed(); - console.log('ERC_5521 deployed at:'+ rNFT1.address); - tokenContract1 = rNFT1; - - const rNFT2 = await RNFT.deploy(TOKEN_NAME2,TOKEN_SYMBOL2); - await rNFT2.deployed(); - console.log('ERC_5521 deployed at:'+ rNFT2.address); - tokenContract2 = rNFT2; - }); - - it("should revert when referring and referred lists have mismatched lengths", async function () { - await expect(tokenContract1.safeMint(1, [tokenContract1.address], [[1], [2]])).to.be.reverted; - }); - - it("can mint a token referring to another minted token", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - await tokenContract2.safeMint(2, [tokenContract1.address], [[1]]); - - // relationships of token 2 can be queried using any ERC5521 contract, not necessarily the contract that minted token 2 - const referringOfT2QueriedByC1 = await tokenContract1.referringOf(tokenContract2.address, 2) - const referringOfT2QueriedByByC2 = await tokenContract2.referringOf(tokenContract2.address, 2) - assertRelationship(referringOfT2QueriedByC1, [tokenContract1.address], [[1]]); - assertRelationship(referringOfT2QueriedByByC2, [tokenContract1.address], [[1]]); - - const referredOfT2QueriedByC1 = await tokenContract1.referredOf(tokenContract2.address, 2) - const referredOfT2QueriedByC2 = await tokenContract2.referredOf(tokenContract2.address, 2) - assertRelationship(referredOfT2QueriedByC1, [], []); - assertRelationship(referredOfT2QueriedByC2, [], []); - - const referringOfT1QueriedByC1 = await tokenContract1.referringOf(tokenContract1.address, 1) - const referringOfT1QueriedByC2 = await tokenContract2.referringOf(tokenContract1.address, 1) - assertRelationship(referringOfT1QueriedByC1, [], []); - assertRelationship(referringOfT1QueriedByC2, [], []); - - const referredOfT1QueriedByC1 = await tokenContract1.referredOf(tokenContract1.address, 1) - const referredOfT1QueriedByC2 = await tokenContract2.referredOf(tokenContract1.address, 1) - assertRelationship(referredOfT1QueriedByC1, [tokenContract2.address], [[2]]); - assertRelationship(referredOfT1QueriedByC2, [tokenContract2.address], [[2]]); - }) - - it("cannot query relationships of a non-existent token", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - await tokenContract2.safeMint(2, [tokenContract1.address], [[1]]); - - // tokenContract1 didn't mint any token with id 2 - await expect(tokenContract1.referringOf(tokenContract1.address, 2)).to.be.revertedWith("token ID not existed"); - await expect(tokenContract1.referredOf(tokenContract1.address, 2)).to.be.revertedWith("token ID not existed"); - }) - - it("cannot mint a token referring to a token that is not yet minted", async function () { - await expect(tokenContract2.safeMint(2, [tokenContract1.address], [[1]])).to.be.revertedWith("invalid token ID"); - }) - - it("can mint 3 tokens forming a simple DAG", async function () { - const mintToken1Tx = await tokenContract1.safeMint(1, [], []); - // mint tx of token 1 must be mined before it can be referred to - await mintToken1Tx.wait(); - // wait 1 sec to ensure that token 2 is minted at a later block timestamp (block timestamp is in second) - await new Promise(r => setTimeout(r, 1000)); - const mintToken2Tx = await tokenContract2.safeMint(2, [tokenContract1.address], [[1]]); - await mintToken2Tx.wait(); - await new Promise(r => setTimeout(r, 1000)); - const mintToken3Tx = await tokenContract2.safeMint(3, [tokenContract1.address, tokenContract2.address], [[1], [2]]); - await mintToken3Tx.wait(); - - const referringOfT2 = await tokenContract1.referringOf(tokenContract2.address, 2) - assertRelationship(referringOfT2, [tokenContract1.address], [[1]]); - - const referredOfT2 = await tokenContract1.referredOf(tokenContract2.address, 2) - assertRelationship(referredOfT2, [tokenContract2.address], [[3]]); - - const referringOfT1 = await tokenContract1.referringOf(tokenContract1.address, 1) - assertRelationship(referringOfT1, [], []); - - const referredOfT1 = await tokenContract1.referredOf(tokenContract1.address, 1) - assertRelationship(referredOfT1, [tokenContract2.address], [[2, 3]]); - - const referringOfT3 = await tokenContract1.referringOf(tokenContract2.address, 3) - assertRelationship(referringOfT3, [tokenContract1.address, tokenContract2.address], [[1], [2]]); - - const referringOfT3fromContract2 = await tokenContract2.referringOf(tokenContract2.address, 3) - assertRelationship(referringOfT3fromContract2, [tokenContract1.address, tokenContract2.address], [[1], [2]]); - - const referredOfT3 = await tokenContract1.referredOf(tokenContract2.address, 3) - assertRelationship(referredOfT3, [], []); - }) - -}); \ No newline at end of file diff --git a/assets/eip-5521/IERC_5521.sol b/assets/eip-5521/IERC_5521.sol deleted file mode 100644 index ae9c3fbb86811f..00000000000000 --- a/assets/eip-5521/IERC_5521.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -interface IERC_5521 { - - /// Logged when a node in the rNFT gets referred and changed - /// @notice Emitted when the `node` (i.e., an rNFT) is changed - event UpdateNode(uint256 indexed tokenId, - address indexed owner, - address[] _address_referringList, - uint256[][] _tokenIds_referringList, - address[] _address_referredList, - uint256[][] _tokenIds_referredList - ); - - /// @notice set the referred list of an rNFT associated with different contract addresses and update the referring list of each one in the referred list - /// @param tokenIds array of rNFTs, recommended to check duplication at the caller's end - function setNode(uint256 tokenId, address[] memory addresses, uint256[][] memory tokenIds) external; - - /// @notice Get the referring list of an rNFT - /// @param tokenId The considered rNFT, _address The corresponding contract address - /// @return The referring mapping of an rNFT - function referringOf(address _address, uint256 tokenId) external view returns(address[] memory, uint256[][] memory); - - /// @notice Get the referred list of an rNFT - /// @param tokenId The considered rNFT, _address The corresponding contract address - /// @return The referred mapping of an rNFT - function referredOf(address _address, uint256 tokenId) external view returns(address[] memory, uint256[][] memory); -} - -interface TargetContract { - function setNodeReferredExternal(address successor, uint256 tokenId, uint256[] memory _tokenIds) external; - function referringOf(address _address, uint256 tokenId) external view returns(address[] memory, uint256[][] memory); - function referredOf(address _address, uint256 tokenId) external view returns(address[] memory, uint256[][] memory); -} diff --git a/assets/eip-5528/ERC20Mockup.sol b/assets/eip-5528/ERC20Mockup.sol deleted file mode 100644 index d5dd437a6452d5..00000000000000 --- a/assets/eip-5528/ERC20Mockup.sol +++ /dev/null @@ -1,55 +0,0 @@ -pragma solidity ^0.4.24; - - - - -contract ERC20Mockup { - mapping(address => uint256) _balances; - uint256 _totalSupply; - address _owner; - - constructor(address initialAccount, uint256 initialBalance) { - _owner = initialAccount; - _totalSupply = initialBalance; - _balances[initialAccount] = initialBalance; - } - - function balanceOf(address account) public view returns (uint256) { - return _balances[account]; - } - function transfer(address to, uint256 amount) public returns (bool) { - address owner = msg.sender; - _transfer(owner, to, amount); - return true; - } - - function _transfer( - address from, - address to, - uint256 amount - ) internal { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - _balances[from] = fromBalance - amount; - _balances[to] += amount; - } - /* - From there, escrow related function - */ - function escrowFund(address to, uint256 amount) public returns (bool) { - bool res = ERC20Mockup(to).escrowFund(msg.sender, amount); - require(res, "Fund Failed"); - _transfer(msg.sender, to, amount); - - return true; - } - function escrowRefund(address to, uint256 amount) public returns (bool) { - bool res = ERC20Mockup(to).escrowRefund(msg.sender, amount); - require(res, "Refund Failed"); - _transfer(to, msg.sender, amount); - return true; - } -} diff --git a/assets/eip-5528/EscrowContractAccount.sol b/assets/eip-5528/EscrowContractAccount.sol deleted file mode 100644 index 99ae11bc0ecee8..00000000000000 --- a/assets/eip-5528/EscrowContractAccount.sol +++ /dev/null @@ -1,171 +0,0 @@ -pragma solidity ^0.4.24; - -import "./ERC20Mockup.sol"; - - -contract ErcEscrowAccount { - struct BalanceData { - uint256 seller; - uint256 buyer; - } - - enum State { Inited, Running, Success, Failed } - - struct EscrowStatus { - uint256 numberOfBuyer; - uint256 fundTotal; - uint256 fundFilled; - State state; - } - mapping(address => BalanceData) _balances; - - address _addrSeller; - address _addrBuyer; - address _addrEscrow; - address _addrCreator; - - EscrowStatus _status; - - constructor(uint256 fundAmount, address sellerContract, address buyerContract) { - - //require(sellerContract.code.length > 0, "seller is not contract"); - //require(buyerContract.code.length > 0, "buyer is not contract"); - - _addrBuyer = buyerContract; - _addrSeller = sellerContract; - - _status.numberOfBuyer = 0; - _status.fundTotal = fundAmount; - _status.fundFilled = 0; - - _addrEscrow = address(this); - _addrCreator = msg.sender; - _status.state = State.Inited; - } - - - function helper_bigInt256(uint256 _u256Val) public view returns (uint256) { - return _u256Val; - } - - function helper_numberOfBuyers() public view returns (uint256) { - return _status.numberOfBuyer; - } - - function _updateRunningState() { - if(_status.state == State.Running){ - if(_status.numberOfBuyer == 2){ - _status.state = State.Success; - } - } - } - - function escrowStatus() public view returns (string) { - if(_status.state == State.Inited){ - return "init"; - }else if(_status.state == State.Running){ - return "Running"; - }else if(_status.state == State.Success){ - return "Success"; - }else if(_status.state == State.Failed){ - return "Failed"; - } - return "unknown state"; - } - - - function balanceOf(address account) public view returns (uint256) { - return _balances[account].buyer; - } - - function escrowBalanceOf(address account) public view returns (uint256 o_buyer, uint256 o_seller) { - o_buyer = _balances[account].buyer; - o_seller = _balances[account].seller; - } - - function escrowFund(address to, uint256 amount) public returns (bool) { - require(amount > 0, "amount is too small"); - if(msg.sender == _addrSeller){ - - require(_status.state == State.Inited, "must be init state"); - require(to == _addrCreator, "to is only with creator"); - require(amount == _status.fundTotal, "amount must be total fund"); - require(_status.fundFilled == 0, "fund filled must be zero"); - - _status.fundFilled = amount; - - _balances[to].seller = _balances[to].seller + amount; - _balances[to].buyer = 0; - _status.state = State.Running; - - }else if(msg.sender == _addrBuyer){ - require(_status.state == State.Running, "must be running state"); - require(_status.fundTotal > 0, "escrow might be not started or already finished"); - require(_status.fundFilled == _status.fundTotal, "fund does not filled yet"); - - // TODO: this logic is only for 1:1 exchange rate - require(amount <= _balances[_addrCreator].seller, "no more token left to exchange"); - - _balances[_addrCreator].seller = _balances[_addrCreator].seller - amount; - _balances[_addrCreator].buyer = _balances[_addrCreator].buyer + amount; - - if(_balances[to].seller == 0){ - _status.numberOfBuyer = _status.numberOfBuyer + 1; - } - _balances[to].seller = _balances[to].seller + amount; - _balances[to].buyer = _balances[to].buyer + amount; - - _updateRunningState(); - }else{ - require(false, "Todo other cases"); - } - - - - return true; - } - - function escrowRefund(address to, uint256 amount) public returns (bool) { - require(amount > 0, "amount is too small"); - require(_status.state == State.Running || _status.state == State.Failed, "must be running state to refund"); - require(msg.sender == _addrBuyer, "must be buyer contract to refund"); - require(_balances[to].buyer >= amount, "buyer fund is not enough to refund"); - - - _balances[to].buyer = _balances[to].buyer - amount; - _balances[to].seller = _balances[to].seller - amount; - - _balances[_addrCreator].seller = _balances[_addrCreator].seller + amount; - _balances[_addrCreator].buyer = _balances[_addrCreator].buyer - amount; - - if(_balances[to].buyer == 0){ - _status.numberOfBuyer = _status.numberOfBuyer - 1; - } - - _updateRunningState(); - return true; - } - - function escrowWithdraw() public returns (bool) { - address from = msg.sender; - - if(from == _addrCreator){ - if(_status.state == State.Success){ - ERC20Mockup(_addrBuyer).transfer(from, _balances[from].buyer); - ERC20Mockup(_addrSeller).transfer(from, _balances[from].seller); - - }else if(_status.state == State.Failed){ - ERC20Mockup(_addrSeller).transfer(from, _status.fundFilled); - }else{ - require(false, "invalid state for seller withdraw"); - } - }else{ - require(_status.state == State.Success, "withdraw is only in success, otherwise use refund"); - ERC20Mockup(_addrSeller).transfer(from, _balances[from].seller); - } - - delete _balances[from]; - return true; - } - -} diff --git a/assets/eip-5528/truffule-test.js b/assets/eip-5528/truffule-test.js deleted file mode 100644 index 3247e8e5968890..00000000000000 --- a/assets/eip-5528/truffule-test.js +++ /dev/null @@ -1,115 +0,0 @@ -const EscrowContractAccount = artifacts.require('./EscrowContractAccount') -const ERC20Mockup = artifacts.require('./ERC20Mockup') - -const util = require('util') -contract('ERCEscrowMockup', accounts => { - const [userCreator, userSeller, userBuyer01, userBuyer02, ...others] = accounts - - let contracts - - const BNConst = { - totalFund: 100, - user1: 10, - user2: 33, - zero: 0, - DigOne: 1, - DigTwo: 2, - } - - before(async () => { - const seller = await ERC20Mockup.new(userCreator, 10000) - await seller.transfer(userSeller, 1000, {from: userCreator}) - - const buyer = await ERC20Mockup.new(userCreator, 10000) - await buyer.transfer(userBuyer01, 1000, {from: userCreator}) - await buyer.transfer(userBuyer02, 1000, {from: userCreator}) - - const escrow = await EscrowContractAccount.new(BNConst.totalFund, seller.address, buyer.address, {from: userSeller}) - - contracts = { - escrow, - seller, - buyer, - } - for (const key in BNConst) { - const v = BNConst[key] - BNConst[key] = { - origin: v, - bn: await escrow.helper_bigInt256(v), - } - } - //console.log('-check point-1-', util.inspect(contracts, false, null, true)) - }) - - it('escrow start', async () => { - const result = await contracts.seller.escrowFund(contracts.escrow.address, BNConst.totalFund.origin, { - from: userSeller, - }) - const [buyer, seller] = await contracts.escrow.escrowBalanceOf(userSeller) - const state = await contracts.escrow.escrowStatus() - //console.log('---check00000', state) - //console.log('-check point-1-', util.inspect(result, false, null, true)) - //console.log('--balance---', {buyer, seller, bigNumberPrefix}) - assert(buyer.eq(BNConst.zero.bn)) - assert(seller.eq(BNConst.totalFund.bn)) - }) - - it('purchase first buyer', async () => { - await contracts.buyer.escrowFund(contracts.escrow.address, BNConst.user1.origin, {from: userBuyer01}) - const [buyer, seller] = await contracts.escrow.escrowBalanceOf(userBuyer01) - }) - it('first buyer refund and purchase', async () => { - await contracts.buyer.escrowRefund(contracts.escrow.address, BNConst.user1.origin, {from: userBuyer01}) - let result = await contracts.escrow.helper_numberOfBuyers() - //console.log('-----1----', result) - assert(result.eq(BNConst.zero.bn)) - - await contracts.buyer.escrowFund(contracts.escrow.address, BNConst.user1.origin, {from: userBuyer01}) - result = await contracts.escrow.helper_numberOfBuyers() - //console.log('-----1----', result) - assert(result.eq(BNConst.DigOne.bn)) - }) - it('send buyer purchase can finialize fund', async () => { - await contracts.buyer.escrowFund(contracts.escrow.address, BNConst.user2.origin, {from: userBuyer02}) - let result = await contracts.escrow.helper_numberOfBuyers() - //console.log('-----1----', result) - assert(result.eq(BNConst.DigTwo.bn)) - result = await contracts.escrow.escrowStatus() - //console.log('-----2----', result) - assert(result === 'Success') - }) - it('check balance of seller and buyer', async () => { - const balance = { - sellerToken: { - issuer: await contracts.seller.balanceOf(userSeller), - b01: await contracts.seller.balanceOf(userBuyer01), - b02: await contracts.seller.balanceOf(userBuyer02), - }, - buyerToken: { - issuer: await contracts.buyer.balanceOf(userSeller), - b01: await contracts.buyer.balanceOf(userBuyer01), - b02: await contracts.buyer.balanceOf(userBuyer02), - }, - } - //console.log('-check point-1-', util.inspect(balance, false, null, true)) - }) - - it('check balance after withdraw', async () => { - await contracts.escrow.escrowWithdraw({from: userSeller}) - await contracts.escrow.escrowWithdraw({from: userBuyer01}) - await contracts.escrow.escrowWithdraw({from: userBuyer02}) - const balance = { - sellerToken: { - issuer: await contracts.seller.balanceOf(userSeller), - b01: await contracts.seller.balanceOf(userBuyer01), - b02: await contracts.seller.balanceOf(userBuyer02), - }, - buyerToken: { - issuer: await contracts.buyer.balanceOf(userSeller), - b01: await contracts.buyer.balanceOf(userBuyer01), - b02: await contracts.buyer.balanceOf(userBuyer02), - }, - } - //console.log('-check point-1-', util.inspect(balance, false, null, true)) - }) -}) diff --git a/assets/eip-5564/minimal_poc.ipynb b/assets/eip-5564/minimal_poc.ipynb deleted file mode 100644 index c7e589d14fc45b..00000000000000 --- a/assets/eip-5564/minimal_poc.ipynb +++ /dev/null @@ -1,639 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "d2c6b4cd", - "metadata": {}, - "outputs": [], - "source": [ - "# PoC using scaning and spending keys" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "6bcea120", - "metadata": {}, - "outputs": [], - "source": [ - "import hashlib\n", - "from py_ecc.secp256k1 import *\n", - "import sha3\n", - "from eth_account import Account" - ] - }, - { - "cell_type": "markdown", - "id": "4e25cb04", - "metadata": {}, - "source": [ - "## Sender" - ] - }, - { - "cell_type": "markdown", - "id": "22ca0bf7", - "metadata": {}, - "source": [ - "$S = G*s$" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "bb9355a0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(22246744184454969143801186698733154500632648736073949898323976612504587645286,\n", - " 110772761940586493986212935445517909380300793379795289150161960681985511655321)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# privkey: 0xd952fe0740d9d14011fc8ead3ab7de3c739d3aa93ce9254c10b0134d80d26a30\n", - "# address: 0x3CB39EA2f14B16B69B451719A7BEd55e0aFEcE8F\n", - "s = int(0xd952fe0740d9d14011fc8ead3ab7de3c739d3aa93ce9254c10b0134d80d26a30) # private key\n", - "S = secp256k1.privtopub(s.to_bytes(32, \"big\")) # public key\n", - "S" - ] - }, - { - "cell_type": "markdown", - "id": "c8240f67", - "metadata": {}, - "source": [ - "## Recipient" - ] - }, - { - "cell_type": "markdown", - "id": "6895e603", - "metadata": {}, - "source": [ - "$P = G*p$" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "c8e2d6ad", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "((89565891926547004231252920425935692360644145829622209833684329913297188986597,\n", - " 12158399299693830322967808612713398636155367887041628176798871954788371653930),\n", - " (112711660439710606056748659173929673102114977341539408544630613555209775888121,\n", - " 25583027980570883691656905877401976406448868254816295069919888960541586679410))" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# privkey: 0x0000000000000000000000000000000000000000000000000000000000000001\n", - "# address: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf\n", - "p_scan = int(0x0000000000000000000000000000000000000000000000000000000000000002) # private key\n", - "p_spend = int(0x0000000000000000000000000000000000000000000000000000000000000003) # private key\n", - "\n", - "P_scan = secp256k1.privtopub(p_scan.to_bytes(32, \"big\")) # public key\n", - "P_spend = secp256k1.privtopub(p_spend.to_bytes(32, \"big\")) # public key\n", - "P_scan, P_spend" - ] - }, - { - "cell_type": "markdown", - "id": "174929d7", - "metadata": {}, - "source": [ - "## Calculate Stealth Address: $P_{spend} + G*hash(Q)$" - ] - }, - { - "cell_type": "markdown", - "id": "8b39ed39", - "metadata": {}, - "source": [ - "$Q = S * p_{scan}$" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "63a022d7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(65311808848028536848162101908966111079795231803322390815513763038079235257196,\n", - " 43767810034999830518515787564234053904327508763526333662117780420755425490082)" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q = secp256k1.multiply(P_scan, s)\n", - "Q" - ] - }, - { - "cell_type": "markdown", - "id": "d79c69fc", - "metadata": {}, - "source": [ - "$Q = S * p_{scan} = P_{scan} * s$" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "5f5fbcf4", - "metadata": {}, - "outputs": [], - "source": [ - "assert Q == secp256k1.multiply(S, p_scan)" - ] - }, - { - "cell_type": "markdown", - "id": "0d5803ff", - "metadata": {}, - "source": [ - "$h(Q)$" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "f1b38cb0", - "metadata": {}, - "outputs": [], - "source": [ - "Q_hex = sha3.keccak_256(Q[0].to_bytes(32, \"big\") \n", - " + Q[1].to_bytes(32, \"big\")\n", - " ).hexdigest()\n", - "Q_hased = bytearray.fromhex(Q_hex)" - ] - }, - { - "cell_type": "markdown", - "id": "a0647821", - "metadata": {}, - "source": [ - "$ stA = h(Q) * G + P_{spend}$" - ] - }, - { - "cell_type": "markdown", - "id": "865e7f72", - "metadata": {}, - "source": [ - "#### Sender sends funds to..." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "d9dd755f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "stP = secp256k1.add(P_spend, secp256k1.privtopub(Q_hased))\n", - "stA = \"0x\"+ sha3.keccak_256(stP[0].to_bytes(32, \"big\")\n", - " +stP[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "stA" - ] - }, - { - "cell_type": "markdown", - "id": "38e69080", - "metadata": {}, - "source": [ - "#### Sender broadcasts" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cdf57fef", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "((22246744184454969143801186698733154500632648736073949898323976612504587645286,\n", - " 110772761940586493986212935445517909380300793379795289150161960681985511655321),\n", - " '0xfed69df0a27f1dae0d7430ead82aaedfad6332bb')" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "S, stA" - ] - }, - { - "cell_type": "markdown", - "id": "588ccc7c", - "metadata": {}, - "source": [ - "## Parse received funds" - ] - }, - { - "cell_type": "markdown", - "id": "462f8c8d", - "metadata": {}, - "source": [ - "* Note that $p_{scan}$ and $P_{spend}$ can be shared with a trusted party\n", - "* There may be many S to be parsed" - ] - }, - { - "cell_type": "markdown", - "id": "8ba2a295", - "metadata": {}, - "source": [ - "$h(p_{scan}*S)*G + P_{spend} => toAddress$" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "50b63208", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q = secp256k1.multiply(S, p_scan)\n", - "Q_hex = sha3.keccak_256(Q[0].to_bytes(32, \"big\")+Q[1].to_bytes(32, \"big\")).hexdigest()\n", - "Q_hased = bytearray.fromhex(Q_hex)\n", - "\n", - "P_stealth = secp256k1.add(P_spend, secp256k1.privtopub(Q_hased))\n", - "P_stealthAddress = \"0x\"+ sha3.keccak_256(stP[0].to_bytes(32, \"big\")\n", - " + stP[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "P_stealthAddress" - ] - }, - { - "cell_type": "markdown", - "id": "8055d075", - "metadata": {}, - "source": [ - "logged stealth address $stA$ equals the derived stealth address $P_stealthAddress$" - ] - }, - { - "cell_type": "markdown", - "id": "26758ea5", - "metadata": {}, - "source": [ - "$stA==stA_d$" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "3faed6a3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "P_stealthAddress == stA" - ] - }, - { - "cell_type": "markdown", - "id": "050e346c", - "metadata": {}, - "source": [ - "## Derive private key" - ] - }, - { - "cell_type": "markdown", - "id": "44801516", - "metadata": {}, - "source": [ - "#### Only the recipient has access to $p_{spend}$" - ] - }, - { - "cell_type": "markdown", - "id": "7673e439", - "metadata": {}, - "source": [ - "$p_{stealth}=p_{spend}+hash(Q)$" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "4013b57e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "39153944482575822531387237249775711740128993925789544779866399859639729033274" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q = secp256k1.multiply(S, p_scan)\n", - "Q_hex = sha3.keccak_256(Q[0].to_bytes(32, \"big\")+Q[1].to_bytes(32, \"big\")).hexdigest()\n", - "p_stealth = p_spend + int(Q_hex, 16)\n", - "p_stealth" - ] - }, - { - "cell_type": "markdown", - "id": "dc31c1aa", - "metadata": {}, - "source": [ - "$P_{stealth} = p_{stealth}*G$" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "09b5ccc2", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(67663851387124608323744162645277269585638670865381831245083336172545348387042,\n", - " 80449904826544093817252981338261706033086352950841917067356875711772573870404)" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Recipient has private key to ...\n", - "P_stealth = secp256k1.privtopub(p_stealth.to_bytes(32, \"big\"))\n", - "P_stealth" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "a3ead30e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "P_stealthAddress_d = \"0x\"+ sha3.keccak_256(P_stealth[0].to_bytes(32, \"big\")\n", - " + P_stealth[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "P_stealthAddress_d" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "2712c07b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfEd69Df0a27F1daE0D7430EAd82aaEdfAD6332bb'" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Account.from_key((p_stealth).to_bytes(32, \"big\")).address" - ] - }, - { - "cell_type": "markdown", - "id": "74f0325e", - "metadata": {}, - "source": [ - "## Additionally add view tags" - ] - }, - { - "cell_type": "markdown", - "id": "ac45bb87", - "metadata": {}, - "source": [ - "In addition to S and stA, the sender also broadcasts the first byte of h(Q)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "9645b880", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "86" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Q_hased[0]" - ] - }, - { - "cell_type": "markdown", - "id": "8788f2f5", - "metadata": {}, - "source": [ - "The recipient can do the the same a before without one EC Multiplication, one EC Addition and on Public Key to Address Conversion in order to check being a potential recipient." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "bb9f5852", - "metadata": {}, - "outputs": [], - "source": [ - "Q_derived = secp256k1.multiply(S, p_scan)\n", - "Q_hex_derived = sha3.keccak_256(Q_derived[0].to_bytes(32, \"big\")\n", - " +Q_derived[1].to_bytes(32, \"big\")\n", - " ).hexdigest()\n", - "Q_hashed_derived = bytearray.fromhex(Q_hex_derived)" - ] - }, - { - "cell_type": "markdown", - "id": "f7dc4624", - "metadata": {}, - "source": [ - "Check view tag" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "953bf07d", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "run = Q_hased[0] == Q_hashed_derived[0] \n", - "run" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "e11ec134", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'0xfed69df0a27f1dae0d7430ead82aaedfad6332bb'" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "if run:\n", - " P_stealth = secp256k1.add(P_spend, secp256k1.privtopub(Q_hased))\n", - " P_stealthAddress = \"0x\"+ sha3.keccak_256(stP[0].to_bytes(32, \"big\")\n", - " + stP[1].to_bytes(32, \"big\")\n", - " ).hexdigest()[-40:]\n", - "P_stealthAddress" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "bd06ffc5", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "P_stealthAddress==stA" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "hackathon", - "language": "python", - "name": "hackathon" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/assets/eip-5564/scheme_ids.md b/assets/eip-5564/scheme_ids.md deleted file mode 100644 index 090da4ab52f08c..00000000000000 --- a/assets/eip-5564/scheme_ids.md +++ /dev/null @@ -1,8 +0,0 @@ -# EIP-5564 - Scheme Id Mapping - - -Last edited 07.02.2023 - -| ID | Scheme | EIP | Added | -|---|---|---|---| -| 0 | SECP256k1, with view tags. | [EIP-5564](https://eips.ethereum.org/EIPS/eip-5564) | 07.02.2023 | diff --git a/assets/eip-5606/contracts/MultiverseNFT.sol b/assets/eip-5606/contracts/MultiverseNFT.sol deleted file mode 100644 index b2506198be5ba2..00000000000000 --- a/assets/eip-5606/contracts/MultiverseNFT.sol +++ /dev/null @@ -1,421 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.9; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/math/SafeMath.sol"; -import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; -import "@openzeppelin/contracts/access/AccessControl.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; - -contract ERC721Full is ERC721Enumerable, ERC721URIStorage { - /// @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. - /// @param name is a non-empty string - /// @param symbol is a non-empty string - constructor(string memory name, string memory symbol) - ERC721(name, symbol) - {} - - /// @dev Hook that is called before any token transfer. This includes minting and burning. `from`'s `tokenId` will be transferred to `to` - /// @param from is an non-zero address - /// @param to is an non-zero address - /// @param tokenId is an uint256 which determine token transferred from `from` to `to` - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual override(ERC721Enumerable, ERC721) { - ERC721Enumerable._beforeTokenTransfer(from, to, tokenId); - } - - /// @notice Interface of the ERC165 standard - /// @param interfaceId is a byte4 which determine interface used - /// @return true if this contract implements the interface defined by `interfaceId` - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721Enumerable, ERC721) - returns (bool) - { - return - ERC721.supportsInterface(interfaceId) || - ERC721Enumerable.supportsInterface(interfaceId); - } - - /// @notice the Uniform Resource Identifier (URI) for `tokenId` token - /// @param tokenId is unit256 - /// @return string of (URI) for `tokenId` token - function tokenURI(uint256 tokenId) - public - view - virtual - override(ERC721URIStorage, ERC721) - returns (string memory) - { - return ERC721URIStorage.tokenURI(tokenId); - } - - function _burn(uint256 tokenId) - internal - override(ERC721, ERC721URIStorage) - {} -} - -/** - * @dev Interface of the Multiverse NFT standard as defined in the EIP. - */ -interface IMultiverseNFT { - /** - * @dev struct to store delegate token details - * - */ - struct DelegateData { - address contractAddress; - uint256 tokenId; - uint256 quantity; - } - - /** - * @dev Emitted when one or more new delegate NFTs are added to a Multiverse NFT - * - */ - event Bundled( - uint256 multiverseTokenID, - DelegateData[] delegateData, - address ownerAddress - ); - - /** - * @dev Emitted when one or more delegate NFTs are removed from a Multiverse NFT - */ - event Unbundled(uint256 multiverseTokenID, DelegateData[] delegateData); - - /** - * @dev Accepts the tokenId of the Multiverse NFT and returns an array of delegate token data - */ - function delegateTokens(uint256 multiverseTokenID) - external - view - returns (DelegateData[] memory); - - /** - * @dev Removes one or more delegate NFTs from a Multiverse NFT - * This function accepts the delegate NFT details, and transfer those NFTs out of the Multiverse NFT contract to the owner's wallet - */ - function unbundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external; - - /** - * @dev Adds one or more delegate NFTs to a Multiverse NFT - * This function accepts the delegate NFT details, and transfers those NFTs to the Multiverse NFT contract - * Need to ensure that approval is given to this Multiverse NFT contract for the delegate NFTs so that they can be transferred programmatically - */ - function bundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external; - - /** - * @dev Initializes a new bundle, mints a Multiverse NFT and assigns it to msg.sender - * Returns the token ID of a new Multiverse NFT - * Note - When a new Multiverse NFT is initialized, it is empty, it does not contain any delegate NFTs - */ - function initBundle(DelegateData[] memory delegateData) external; -} - -abstract contract MultiverseNFT is - IMultiverseNFT, - Ownable, - ERC721Full, - IERC1155Receiver, - AccessControl -{ - using SafeMath for uint256; - bytes32 public constant BUNDLER_ROLE = keccak256("BUNDLER_ROLE"); - - uint256 currentMultiverseTokenID; - - mapping(uint256 => DelegateData[]) public multiverseNFTDelegateData; - mapping(uint256 => mapping(address => mapping(uint256 => uint256))) - public tokenBalances; - - constructor(address bundlerAddress) { - _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); - _setupRole(BUNDLER_ROLE, msg.sender); - _setRoleAdmin(BUNDLER_ROLE, DEFAULT_ADMIN_ROLE); - _setupRole(BUNDLER_ROLE, bundlerAddress); - } - - function delegateTokens(uint256 multiverseTokenID) - external - view - returns (DelegateData[] memory) - { - return multiverseNFTDelegateData[multiverseTokenID]; - } - - function initBundle(DelegateData[] memory delegateData) external { - uint256 tokenId = currentMultiverseTokenID.add(1); - for (uint256 i = 0; i < delegateData.length; i = i.add(1)) { - bool isERC721 = _isERC721(delegateData[i].contractAddress); - if (isERC721) { - require( - delegateData[i].quantity == 1, - "ERC721 quantity must be 1" - ); - } - multiverseNFTDelegateData[tokenId].push(delegateData[i]); - } - - _incrementMultiverseTokenID(); - _safeMint(msg.sender, tokenId); - } - - function bundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external { - require( - hasRole(BUNDLER_ROLE, msg.sender) || - ownerOf(multiverseTokenID) == msg.sender, - "msg.sender neither have bundler role nor multiversetoken owner" - ); - _bundle(delegateData, multiverseTokenID); - } - - function unbundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) external { - require( - ownerOf(multiverseTokenID) == msg.sender, - "msg.sender is not a multiversetoken owner" - ); - for (uint256 i = 0; i < delegateData.length; i = i.add(1)) { - require( - _ensureDelegateBelongsToMultiverseNFT( - delegateData[i], - multiverseTokenID - ), - "delegate not assigned to multiverse token" - ); - uint256 balance = tokenBalances[multiverseTokenID][ - delegateData[i].contractAddress - ][delegateData[i].tokenId]; - require( - delegateData[i].quantity <= balance, - "quantity exceeds balance" - ); - require( - _ensureMultiverseContractOwnsDelegate(delegateData[i]), - "delegate not owned by contract" - ); - - address contractAddress = delegateData[i].contractAddress; - uint256 tokenId = delegateData[i].tokenId; - uint256 quantity = delegateData[i].quantity; - - _updateDelegateBalances(delegateData[i], multiverseTokenID); - - if (_isERC721(contractAddress)) { - ERC721Full erc721Instance = ERC721Full(contractAddress); - erc721Instance.transferFrom(address(this), msg.sender, tokenId); - } else if (_isERC1155(contractAddress)) { - ERC1155Supply erc1155Instance = ERC1155Supply(contractAddress); - erc1155Instance.safeTransferFrom( - address(this), - msg.sender, - tokenId, - quantity, - "" - ); - } - } - emit Unbundled(multiverseTokenID, delegateData); - } - - function supportsInterface(bytes4 interfaceId) - public - view - override(AccessControl, ERC721Full, IERC165) - returns (bool) - { - return - AccessControl.supportsInterface(interfaceId) || - ERC721Full.supportsInterface(interfaceId); - } - - function _bundle( - DelegateData[] memory delegateData, - uint256 multiverseTokenID - ) internal { - for (uint256 i = 0; i < delegateData.length; i = i.add(1)) { - require( - _ensureDelegateBelongsToMultiverseNFT( - delegateData[i], - multiverseTokenID - ), - "delegate not assigned to multiversetoken" - ); - require( - _ensureDelegateQuantityLimitForMMultiverseNFT( - delegateData[i], - multiverseTokenID - ), - "delegate quantity assigned to multiversetoken exceeds" - ); - - address contractAddress = delegateData[i].contractAddress; - uint256 tokenId = delegateData[i].tokenId; - uint256 quantity = delegateData[i].quantity; - - tokenBalances[multiverseTokenID][contractAddress][ - tokenId - ] = tokenBalances[multiverseTokenID][contractAddress][tokenId].add( - quantity - ); - - if (_isERC721(contractAddress)) { - require( - quantity == 1, - "ERC721 cannot have quantity more than 1" - ); - ERC721Full erc721Instance = ERC721Full(contractAddress); - erc721Instance.transferFrom(msg.sender, address(this), tokenId); - } else if (_isERC1155(contractAddress)) { - ERC1155Supply erc1155Instance = ERC1155Supply(contractAddress); - erc1155Instance.safeTransferFrom( - msg.sender, - address(this), - tokenId, - quantity, - "" - ); - } - } - emit Bundled( - multiverseTokenID, - delegateData, - ownerOf(multiverseTokenID) - ); - } - - function _ensureDelegateBelongsToMultiverseNFT( - DelegateData memory delegateData, - uint256 multiverseTokenID - ) internal view returns (bool) { - DelegateData[] memory storedData = multiverseNFTDelegateData[ - multiverseTokenID - ]; - for (uint256 i = 0; i < storedData.length; i = i.add(1)) { - if ( - delegateData.contractAddress == storedData[i].contractAddress && - delegateData.tokenId == storedData[i].tokenId - ) { - return true; - } - } - return false; - } - - function _ensureMultiverseContractOwnsDelegate( - DelegateData memory delegateData - ) internal view returns (bool) { - if (_isERC721(delegateData.contractAddress)) { - ERC721Full erc721Instance = ERC721Full( - delegateData.contractAddress - ); - if (address(this) == erc721Instance.ownerOf(delegateData.tokenId)) { - return true; - } - } else if (_isERC1155(delegateData.contractAddress)) { - ERC1155Supply erc1155Instance = ERC1155Supply( - delegateData.contractAddress - ); - if ( - erc1155Instance.balanceOf( - address(this), - delegateData.tokenId - ) >= delegateData.quantity - ) { - return true; - } - } - return false; - } - - function _ensureDelegateQuantityLimitForMMultiverseNFT( - DelegateData memory delegateData, - uint256 multiverseTokenID - ) internal view returns (bool) { - DelegateData[] memory storedData = multiverseNFTDelegateData[ - multiverseTokenID - ]; - for (uint256 i = 0; i < storedData.length; i = i.add(1)) { - if ( - delegateData.contractAddress == storedData[i].contractAddress && - delegateData.tokenId == storedData[i].tokenId - ) { - uint256 balance = tokenBalances[multiverseTokenID][ - delegateData.contractAddress - ][delegateData.tokenId]; - if ( - balance.add(delegateData.quantity) <= storedData[i].quantity - ) { - return true; - } - return false; - } - } - } - - function _updateDelegateBalances( - DelegateData memory delegateData, - uint256 multiverseTokenID - ) internal returns (uint256) { - address contractAddress = delegateData.contractAddress; - uint256 tokenId = delegateData.tokenId; - tokenBalances[multiverseTokenID][contractAddress][ - tokenId - ] = tokenBalances[multiverseTokenID][contractAddress][tokenId].sub( - delegateData.quantity - ); - return tokenBalances[multiverseTokenID][contractAddress][tokenId]; - } - - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external pure override returns (bytes4) { - return this.onERC1155Received.selector; - } - - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external pure override returns (bytes4) { - return this.onERC1155BatchReceived.selector; - } - - function _isERC1155(address contractAddress) internal view returns (bool) { - return IERC1155(contractAddress).supportsInterface(0xd9b67a26); - } - - function _isERC721(address contractAddress) internal view returns (bool) { - return IERC721(contractAddress).supportsInterface(0x80ac58cd); - } - - function _incrementMultiverseTokenID() internal { - currentMultiverseTokenID = currentMultiverseTokenID.add(1); - } -} diff --git a/assets/eip-5633/contracts/ERC5633.sol b/assets/eip-5633/contracts/ERC5633.sol deleted file mode 100644 index 121f6bf0a27b7a..00000000000000 --- a/assets/eip-5633/contracts/ERC5633.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "./IERC5633.sol"; - -/** - * @dev Extension of ERC1155 that adds soulbound property per token id. - * - */ -abstract contract ERC5633 is ERC1155, IERC5633 { - mapping(uint256 => bool) private _soulbounds; - - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155) returns (bool) { - return interfaceId == type(IERC5633).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * @dev Returns true if a token type `id` is soulbound. - */ - function isSoulbound(uint256 id) public view virtual returns (bool) { - return _soulbounds[id]; - } - - function _setSoulbound(uint256 id, bool soulbound) internal { - _soulbounds[id] = soulbound; - emit Soulbound(id, soulbound); - } - - /** - * @dev See {ERC1155-_beforeTokenTransfer}. - */ - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) internal virtual override { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - - for (uint256 i = 0; i < ids.length; ++i) { - if (isSoulbound(ids[i])) { - require( - from == address(0) || to == address(0), - "ERC5633: Soulbound, Non-Transferable" - ); - } - } - } -} \ No newline at end of file diff --git a/assets/eip-5633/contracts/ERC5633Demo.sol b/assets/eip-5633/contracts/ERC5633Demo.sol deleted file mode 100644 index 8427d37b27136a..00000000000000 --- a/assets/eip-5633/contracts/ERC5633Demo.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -import "./ERC5633.sol"; - -contract ERC5633Demo is ERC1155, ERC1155Burnable, Ownable, ERC5633 { - constructor() ERC1155("") ERC5633() {} - - function mint(address account, uint256 id, uint256 amount, bytes memory data) - public - onlyOwner - { - _mint(account, id, amount, data); - } - - function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) - public - onlyOwner - { - _mintBatch(to, ids, amounts, data); - } - - function setSoulbound(uint256 id, bool soulbound) - public - onlyOwner - { - _setSoulbound(id, soulbound); - } - - // The following functions are overrides required by Solidity. - function supportsInterface(bytes4 interfaceId) - public - view - override(ERC1155, ERC5633) - returns (bool) - { - return super.supportsInterface(interfaceId); - } - - function _beforeTokenTransfer(address operator, address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) - internal - override(ERC1155, ERC5633) - { - super._beforeTokenTransfer(operator, from, to, ids, amounts, data); - } - - function getInterfaceId() public view returns (bytes4) { - return type(IERC5633).interfaceId; - } -} diff --git a/assets/eip-5633/contracts/IERC5633.sol b/assets/eip-5633/contracts/IERC5633.sol deleted file mode 100644 index 3496ea57c5758b..00000000000000 --- a/assets/eip-5633/contracts/IERC5633.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC5633 { - /** - * @dev Emitted when a token type `id` is set or cancel to soulbound, according to `bounded`. - */ - event Soulbound(uint256 indexed id, bool bounded); - - /** - * @dev Returns true if a token type `id` is soulbound. - */ - function isSoulbound(uint256 id) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-5633/hardhat.config.js b/assets/eip-5633/hardhat.config.js deleted file mode 100644 index 7a30b5c7de62af..00000000000000 --- a/assets/eip-5633/hardhat.config.js +++ /dev/null @@ -1,14 +0,0 @@ -require("@nomicfoundation/hardhat-toolbox"); - -/** @type import('hardhat/config').HardhatUserConfig */ -module.exports = { - solidity: { - version: "0.8.9", - settings: { - optimizer: { - enabled: true, - runs: 200 - } - } - }, -}; diff --git a/assets/eip-5633/package.json b/assets/eip-5633/package.json deleted file mode 100644 index c3dbe0ab3f1f6a..00000000000000 --- a/assets/eip-5633/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "eip-5633", - "devDependencies": { - "@ethersproject/providers": "^5.7.0", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", - "@nomicfoundation/hardhat-network-helpers": "^1.0.6", - "@nomicfoundation/hardhat-toolbox": "^1.0.2", - "@nomiclabs/hardhat-ethers": "^2.1.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/contracts": "^4.7.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.3", - "@types/mocha": "^9.1.1", - "chai": "^4.3.6", - "ethers": "^5.7.0", - "hardhat": "^2.11.0", - "hardhat-gas-reporter": "^1.0.9", - "solidity-coverage": "^0.7.22", - "ts-node": "^10.9.1", - "typechain": "^8.1.0", - "typescript": "^4.8.3" - } -} diff --git a/assets/eip-5633/test/test.js b/assets/eip-5633/test/test.js deleted file mode 100644 index 089f559c9b1ad1..00000000000000 --- a/assets/eip-5633/test/test.js +++ /dev/null @@ -1,51 +0,0 @@ -const { expect } = require("chai"); -const { ethers } = require("hardhat"); - -describe("ERC5633Demo contract", function () { - - it("InterfaceId should equals 0x911ec470", async function () { - const [owner, addr1, addr2] = await ethers.getSigners(); - - const ERC5633Demo = await ethers.getContractFactory("ERC5633Demo"); - - const demo = await ERC5633Demo.deploy(); - await demo.deployed(); - - expect(await demo.getInterfaceId()).equals("0x911ec470"); - }); - - it("Test soulbound", async function () { - const [owner, addr1, addr2] = await ethers.getSigners(); - - const ERC5633Demo = await ethers.getContractFactory("ERC5633Demo"); - - const demo = await ERC5633Demo.deploy(); - await demo.deployed(); - - await demo.setSoulbound(1, true); - expect(await demo.isSoulbound(1)).to.equal(true); - expect(await demo.isSoulbound(2)).to.equal(false); - - await demo.mint(addr1.address, 1, 2, "0x"); - await demo.mint(addr1.address, 2, 2, "0x"); - - await expect(demo.connect(addr1).safeTransferFrom(addr1.address, addr2.address, 1, 1, "0x")).to.be.revertedWith( - "ERC5633: Soulbound, Non-Transferable" - ); - await expect(demo.connect(addr1).safeBatchTransferFrom(addr1.address, addr2.address, [1], [1], "0x")).to.be.revertedWith( - "ERC5633: Soulbound, Non-Transferable" - ); - await expect(demo.connect(addr1).safeBatchTransferFrom(addr1.address, addr2.address, [1,2], [1,1], "0x")).to.be.revertedWith( - "ERC5633: Soulbound, Non-Transferable" - ); - - await demo.mint(addr1.address, 2, 1, "0x"); - demo.connect(addr1).safeTransferFrom(addr1.address, addr2.address, 2, 1, "0x"); - demo.connect(addr1).safeBatchTransferFrom(addr1.address, addr2.address, [2], [1], "0x"); - - await demo.connect(addr1).burn(addr1.address, 1, 1); - await demo.connect(addr1).burnBatch(addr1.address, [1], [1]); - await demo.connect(addr2).burn(addr2.address, 2, 1); - await demo.connect(addr2).burnBatch(addr2.address, [2], [1]); - }); -}); diff --git a/assets/eip-5639/DelegationRegistry.sol b/assets/eip-5639/DelegationRegistry.sol deleted file mode 100644 index b48fa001f765c1..00000000000000 --- a/assets/eip-5639/DelegationRegistry.sol +++ /dev/null @@ -1,449 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IDelegationRegistry} from "./IDelegationRegistry.sol"; -import {EnumerableSet} from "openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import {ERC165} from "openzeppelin-contracts/contracts/utils/introspection/ERC165.sol"; - -/** - * @title DelegationRegistry - * @custom:version 0.2 - * @notice An immutable registry contract to be deployed as a standalone primitive. - * New project launches can read previous cold wallet -> hot wallet delegations - * from here and integrate those permissions into their flow. - * @custom:coauthor foobar (0xfoobar) - * @custom:coauthor wwchung (manifoldxyz) - * @custom:coauthor purplehat (artblocks) - * @custom:coauthor ryley-o (artblocks) - * @custom:coauthor andy8052 (tessera) - * @custom:coauthor punk6529 (open metaverse) - * @custom:coauthor loopify (loopiverse) - * @custom:coauthor emiliano (nftrentals) - * @custom:coauthor arran (proof) - * @custom:coauthor james (collabland) - * @custom:coauthor john (gnosis safe) - * @custom:coauthor 0xrusowsky - */ -contract DelegationRegistry is IDelegationRegistry, ERC165 { - using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableSet for EnumerableSet.Bytes32Set; - - /// @notice The global mapping and single source of truth for delegations - /// @dev vault -> vaultVersion -> delegationHash - mapping(address => mapping(uint256 => EnumerableSet.Bytes32Set)) internal delegations; - - /// @notice A mapping of wallets to versions (for cheap revocation) - mapping(address => uint256) internal vaultVersion; - - /// @notice A mapping of wallets to delegates to versions (for cheap revocation) - mapping(address => mapping(address => uint256)) internal delegateVersion; - - /// @notice A secondary mapping to return onchain enumerability of delegations that a given address can perform - /// @dev delegate -> delegationHashes - mapping(address => EnumerableSet.Bytes32Set) internal delegationHashes; - - /// @notice A secondary mapping used to return delegation information about a delegation - /// @dev delegationHash -> DelegateInfo - mapping(bytes32 => IDelegationRegistry.DelegationInfo) internal delegationInfo; - - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) public view virtual override (ERC165) returns (bool) { - return interfaceId == type(IDelegationRegistry).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * ----------- WRITE ----------- - */ - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForAll(address delegate, bool value) external override { - bytes32 delegationHash = _computeAllDelegationHash(msg.sender, delegate); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.ALL, msg.sender, address(0), 0 - ); - emit IDelegationRegistry.DelegateForAll(msg.sender, delegate, value); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForContract(address delegate, address contract_, bool value) external override { - bytes32 delegationHash = _computeContractDelegationHash(msg.sender, delegate, contract_); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.CONTRACT, msg.sender, contract_, 0 - ); - emit IDelegationRegistry.DelegateForContract(msg.sender, delegate, contract_, value); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external override { - bytes32 delegationHash = _computeTokenDelegationHash(msg.sender, delegate, contract_, tokenId); - _setDelegationValues( - delegate, delegationHash, value, IDelegationRegistry.DelegationType.TOKEN, msg.sender, contract_, tokenId - ); - emit IDelegationRegistry.DelegateForToken(msg.sender, delegate, contract_, tokenId, value); - } - - /** - * @dev Helper function to set all delegation values and enumeration sets - */ - function _setDelegationValues( - address delegate, - bytes32 delegateHash, - bool value, - IDelegationRegistry.DelegationType type_, - address vault, - address contract_, - uint256 tokenId - ) - internal - { - if (value) { - delegations[vault][vaultVersion[vault]].add(delegateHash); - delegationHashes[delegate].add(delegateHash); - delegationInfo[delegateHash] = - DelegationInfo({vault: vault, delegate: delegate, type_: type_, contract_: contract_, tokenId: tokenId}); - } else { - delegations[vault][vaultVersion[vault]].remove(delegateHash); - delegationHashes[delegate].remove(delegateHash); - delete delegationInfo[delegateHash]; - } - } - - /** - * @dev Helper function to compute delegation hash for wallet delegation - */ - function _computeAllDelegationHash(address vault, address delegate) internal view returns (bytes32) { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, vaultVersion_, delegateVersion_)); - } - - /** - * @dev Helper function to compute delegation hash for contract delegation - */ - function _computeContractDelegationHash(address vault, address delegate, address contract_) - internal - view - returns (bytes32) - { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, contract_, vaultVersion_, delegateVersion_)); - } - - /** - * @dev Helper function to compute delegation hash for token delegation - */ - function _computeTokenDelegationHash(address vault, address delegate, address contract_, uint256 tokenId) - internal - view - returns (bytes32) - { - uint256 vaultVersion_ = vaultVersion[vault]; - uint256 delegateVersion_ = delegateVersion[vault][delegate]; - return keccak256(abi.encode(delegate, vault, contract_, tokenId, vaultVersion_, delegateVersion_)); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeAllDelegates() external override { - ++vaultVersion[msg.sender]; - emit IDelegationRegistry.RevokeAllDelegates(msg.sender); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeDelegate(address delegate) external override { - _revokeDelegate(delegate, msg.sender); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function revokeSelf(address vault) external override { - _revokeDelegate(msg.sender, vault); - } - - /** - * @dev Revoke the `delegate` hotwallet from the `vault` coldwallet. - */ - function _revokeDelegate(address delegate, address vault) internal { - ++delegateVersion[vault][delegate]; - // For enumerations, filter in the view functions - emit IDelegationRegistry.RevokeDelegate(vault, msg.sender); - } - - /** - * ----------- READ ----------- - */ - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegationsByDelegate(address delegate) - external - view - returns (IDelegationRegistry.DelegationInfo[] memory info) - { - EnumerableSet.Bytes32Set storage potentialDelegationHashes = delegationHashes[delegate]; - uint256 potentialDelegationHashesLength = potentialDelegationHashes.length(); - uint256 delegationCount = 0; - info = new IDelegationRegistry.DelegationInfo[](potentialDelegationHashesLength); - for (uint256 i = 0; i < potentialDelegationHashesLength;) { - bytes32 delegateHash = potentialDelegationHashes.at(i); - IDelegationRegistry.DelegationInfo memory delegationInfo_ = delegationInfo[delegateHash]; - address vault = delegationInfo_.vault; - IDelegationRegistry.DelegationType type_ = delegationInfo_.type_; - bool valid = false; - if (type_ == IDelegationRegistry.DelegationType.ALL) { - if (delegateHash == _computeAllDelegationHash(vault, delegate)) { - valid = true; - } - } else if (type_ == IDelegationRegistry.DelegationType.CONTRACT) { - if (delegateHash == _computeContractDelegationHash(vault, delegate, delegationInfo_.contract_)) { - valid = true; - } - } else if (type_ == IDelegationRegistry.DelegationType.TOKEN) { - if ( - delegateHash - == _computeTokenDelegationHash(vault, delegate, delegationInfo_.contract_, delegationInfo_.tokenId) - ) { - valid = true; - } - } - if (valid) { - info[delegationCount++] = delegationInfo_; - } - unchecked { - ++i; - } - } - if (potentialDelegationHashesLength > delegationCount) { - assembly { - let decrease := sub(potentialDelegationHashesLength, delegationCount) - mstore(info, sub(mload(info), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForAll(address vault) external view returns (address[] memory delegates) { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.ALL, address(0), 0); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForContract(address vault, address contract_) - external - view - override - returns (address[] memory delegates) - { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.CONTRACT, contract_, 0); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getDelegatesForToken(address vault, address contract_, uint256 tokenId) - external - view - override - returns (address[] memory delegates) - { - return _getDelegatesForLevel(vault, IDelegationRegistry.DelegationType.TOKEN, contract_, tokenId); - } - - function _getDelegatesForLevel( - address vault, - IDelegationRegistry.DelegationType delegationType, - address contract_, - uint256 tokenId - ) - internal - view - returns (address[] memory delegates) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialDelegatesLength = delegationHashes_.length(); - uint256 delegatesCount = 0; - delegates = new address[](potentialDelegatesLength); - for (uint256 i = 0; i < potentialDelegatesLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == delegationType) { - if (delegationType == IDelegationRegistry.DelegationType.ALL) { - // check delegate version by validating the hash - if (delegationHash == _computeAllDelegationHash(vault, delegationInfo_.delegate)) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } else if (delegationType == IDelegationRegistry.DelegationType.CONTRACT) { - if (delegationInfo_.contract_ == contract_) { - // check delegate version by validating the hash - if ( - delegationHash == _computeContractDelegationHash(vault, delegationInfo_.delegate, contract_) - ) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } - } else if (delegationType == IDelegationRegistry.DelegationType.TOKEN) { - if (delegationInfo_.contract_ == contract_ && delegationInfo_.tokenId == tokenId) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeTokenDelegationHash(vault, delegationInfo_.delegate, contract_, tokenId) - ) { - delegates[delegatesCount++] = delegationInfo_.delegate; - } - } - } - } - unchecked { - ++i; - } - } - if (potentialDelegatesLength > delegatesCount) { - assembly { - let decrease := sub(potentialDelegatesLength, delegatesCount) - mstore(delegates, sub(mload(delegates), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getContractLevelDelegations(address vault) - external - view - returns (IDelegationRegistry.ContractDelegation[] memory contractDelegations) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialLength = delegationHashes_.length(); - uint256 delegationCount = 0; - contractDelegations = new IDelegationRegistry.ContractDelegation[](potentialLength); - for (uint256 i = 0; i < potentialLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == IDelegationRegistry.DelegationType.CONTRACT) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeContractDelegationHash(vault, delegationInfo_.delegate, delegationInfo_.contract_) - ) { - contractDelegations[delegationCount++] = IDelegationRegistry.ContractDelegation({ - contract_: delegationInfo_.contract_, - delegate: delegationInfo_.delegate - }); - } - } - unchecked { - ++i; - } - } - if (potentialLength > delegationCount) { - assembly { - let decrease := sub(potentialLength, delegationCount) - mstore(contractDelegations, sub(mload(contractDelegations), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function getTokenLevelDelegations(address vault) - external - view - returns (IDelegationRegistry.TokenDelegation[] memory tokenDelegations) - { - EnumerableSet.Bytes32Set storage delegationHashes_ = delegations[vault][vaultVersion[vault]]; - uint256 potentialLength = delegationHashes_.length(); - uint256 delegationCount = 0; - tokenDelegations = new IDelegationRegistry.TokenDelegation[](potentialLength); - for (uint256 i = 0; i < potentialLength;) { - bytes32 delegationHash = delegationHashes_.at(i); - DelegationInfo storage delegationInfo_ = delegationInfo[delegationHash]; - if (delegationInfo_.type_ == IDelegationRegistry.DelegationType.TOKEN) { - // check delegate version by validating the hash - if ( - delegationHash - == _computeTokenDelegationHash( - vault, delegationInfo_.delegate, delegationInfo_.contract_, delegationInfo_.tokenId - ) - ) { - tokenDelegations[delegationCount++] = IDelegationRegistry.TokenDelegation({ - contract_: delegationInfo_.contract_, - tokenId: delegationInfo_.tokenId, - delegate: delegationInfo_.delegate - }); - } - } - unchecked { - ++i; - } - } - if (potentialLength > delegationCount) { - assembly { - let decrease := sub(potentialLength, delegationCount) - mstore(tokenDelegations, sub(mload(tokenDelegations), decrease)) - } - } - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForAll(address delegate, address vault) public view override returns (bool) { - bytes32 delegateHash = - keccak256(abi.encode(delegate, vault, vaultVersion[vault], delegateVersion[vault][delegate])); - return delegations[vault][vaultVersion[vault]].contains(delegateHash); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForContract(address delegate, address vault, address contract_) - public - view - override - returns (bool) - { - bytes32 delegateHash = - keccak256(abi.encode(delegate, vault, contract_, vaultVersion[vault], delegateVersion[vault][delegate])); - return - delegations[vault][vaultVersion[vault]].contains(delegateHash) - ? true - : checkDelegateForAll(delegate, vault); - } - - /** - * @inheritdoc IDelegationRegistry - */ - function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId) - public - view - override - returns (bool) - { - bytes32 delegateHash = keccak256( - abi.encode(delegate, vault, contract_, tokenId, vaultVersion[vault], delegateVersion[vault][delegate]) - ); - return - delegations[vault][vaultVersion[vault]].contains(delegateHash) - ? true - : checkDelegateForContract(delegate, vault, contract_); - } -} diff --git a/assets/eip-5639/IDelegationRegistry.sol b/assets/eip-5639/IDelegationRegistry.sol deleted file mode 100644 index bbdcb41a87253e..00000000000000 --- a/assets/eip-5639/IDelegationRegistry.sol +++ /dev/null @@ -1,184 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/** - * @title An immutable registry contract to be deployed as a standalone primitive - * @dev New project launches can read previous cold wallet -> hot wallet delegations - * from here and integrate those permissions into their flow - */ -interface IDelegationRegistry { - /// @notice Delegation type - enum DelegationType { - NONE, - ALL, - CONTRACT, - TOKEN - } - - /// @notice Info about a single delegation, used for onchain enumeration - struct DelegationInfo { - DelegationType type_; - address vault; - address delegate; - address contract_; - uint256 tokenId; - } - - /// @notice Info about a single contract-level delegation - struct ContractDelegation { - address contract_; - address delegate; - } - - /// @notice Info about a single token-level delegation - struct TokenDelegation { - address contract_; - uint256 tokenId; - address delegate; - } - - /// @notice Emitted when a user delegates their entire wallet - event DelegateForAll(address vault, address delegate, bool value); - - /// @notice Emitted when a user delegates a specific contract - event DelegateForContract(address vault, address delegate, address contract_, bool value); - - /// @notice Emitted when a user delegates a specific token - event DelegateForToken(address vault, address delegate, address contract_, uint256 tokenId, bool value); - - /// @notice Emitted when a user revokes all delegations - event RevokeAllDelegates(address vault); - - /// @notice Emitted when a user revoes all delegations for a given delegate - event RevokeDelegate(address vault, address delegate); - - /** - * ----------- WRITE ----------- - */ - - /** - * @notice Allow the delegate to act on your behalf for all contracts - * @param delegate The hotwallet to act on your behalf - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForAll(address delegate, bool value) external; - - /** - * @notice Allow the delegate to act on your behalf for a specific contract - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForContract(address delegate, address contract_, bool value) external; - - /** - * @notice Allow the delegate to act on your behalf for a specific token - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param tokenId The token id for the token you're delegating - * @param value Whether to enable or disable delegation for this address, true for setting and false for revoking - */ - function delegateForToken(address delegate, address contract_, uint256 tokenId, bool value) external; - - /** - * @notice Revoke all delegates - */ - function revokeAllDelegates() external; - - /** - * @notice Revoke a specific delegate for all their permissions - * @param delegate The hotwallet to revoke - */ - function revokeDelegate(address delegate) external; - - /** - * @notice Remove yourself as a delegate for a specific vault - * @param vault The vault which delegated to the msg.sender, and should be removed - */ - function revokeSelf(address vault) external; - - /** - * ----------- READ ----------- - */ - - /** - * @notice Returns all active delegations a given delegate is able to claim on behalf of - * @param delegate The delegate that you would like to retrieve delegations for - * @return info Array of DelegationInfo structs - */ - function getDelegationsByDelegate(address delegate) external view returns (DelegationInfo[] memory); - - /** - * @notice Returns an array of wallet-level delegates for a given vault - * @param vault The cold wallet who issued the delegation - * @return addresses Array of wallet-level delegates for a given vault - */ - function getDelegatesForAll(address vault) external view returns (address[] memory); - - /** - * @notice Returns an array of contract-level delegates for a given vault and contract - * @param vault The cold wallet who issued the delegation - * @param contract_ The address for the contract you're delegating - * @return addresses Array of contract-level delegates for a given vault and contract - */ - function getDelegatesForContract(address vault, address contract_) external view returns (address[] memory); - - /** - * @notice Returns an array of contract-level delegates for a given vault's token - * @param vault The cold wallet who issued the delegation - * @param contract_ The address for the contract holding the token - * @param tokenId The token id for the token you're delegating - * @return addresses Array of contract-level delegates for a given vault's token - */ - function getDelegatesForToken(address vault, address contract_, uint256 tokenId) - external - view - returns (address[] memory); - - /** - * @notice Returns all contract-level delegations for a given vault - * @param vault The cold wallet who issued the delegations - * @return delegations Array of ContractDelegation structs - */ - function getContractLevelDelegations(address vault) - external - view - returns (ContractDelegation[] memory delegations); - - /** - * @notice Returns all token-level delegations for a given vault - * @param vault The cold wallet who issued the delegations - * @return delegations Array of TokenDelegation structs - */ - function getTokenLevelDelegations(address vault) external view returns (TokenDelegation[] memory delegations); - - /** - * @notice Returns true if the address is delegated to act on the entire vault - * @param delegate The hotwallet to act on your behalf - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForAll(address delegate, address vault) external view returns (bool); - - /** - * @notice Returns true if the address is delegated to act on your behalf for a token contract or an entire vault - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForContract(address delegate, address vault, address contract_) - external - view - returns (bool); - - /** - * @notice Returns true if the address is delegated to act on your behalf for a specific token, the token's contract or an entire vault - * @param delegate The hotwallet to act on your behalf - * @param contract_ The address for the contract you're delegating - * @param tokenId The token id for the token you're delegating - * @param vault The cold wallet who issued the delegation - */ - function checkDelegateForToken(address delegate, address vault, address contract_, uint256 tokenId) - external - view - returns (bool); -} diff --git a/assets/eip-5646/support-per-abi.png b/assets/eip-5646/support-per-abi.png deleted file mode 100644 index d9efc72cfe3c78..00000000000000 Binary files a/assets/eip-5646/support-per-abi.png and /dev/null differ diff --git a/assets/eip-5646/support-per-eip.png b/assets/eip-5646/support-per-eip.png deleted file mode 100644 index b985faa786a759..00000000000000 Binary files a/assets/eip-5646/support-per-eip.png and /dev/null differ diff --git a/assets/eip-5700/erc1155/ERC1155.sol b/assets/eip-5700/erc1155/ERC1155.sol deleted file mode 100644 index 652ebb7e7f6729..00000000000000 --- a/assets/eip-5700/erc1155/ERC1155.sol +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; -import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; - -import {IERC1155Errors} from "../interfaces/IERC1155Errors.sol"; - -/// @title Dopamine Minimal ERC-1155 Contract -/// @notice This is a minimal ERC-1155 implementation that -contract ERC1155 is IERC1155, IERC1155Errors { - - /// @notice Checks for an owner if an address is an authorized operator. - mapping(address => mapping(address => bool)) public isApprovedForAll; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC1155_INTERFACE_ID = 0xd9b67a26; - - /// @notice Gets an address' number of tokens owned of a specific type. - mapping(address => mapping(uint256 => uint256)) public _balanceOf; - - /// @notice Transfers `amount` tokens of id `id` from address `from` to - /// address `to`, while ensuring `to` is capable of receiving the token. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the token to be transferred. - /// @param to The new owner address of the token being transferred. - /// @param id The id of the token being transferred. - /// @param amount The number of tokens being transferred. - /// @param data Additional transfer data to pass to the receiving contract. - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) public virtual { - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - _balanceOf[from][id] -= amount; - _balanceOf[to][id] += amount; - - emit TransferSingle(msg.sender, from, to, id, amount); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155Received( - msg.sender, - address(0), - id, - amount, - data - ) != - IERC1155Receiver.onERC1155Received.selector - ) { - revert SafeTransferUnsupported(); - } else if (to == address(0)) { - revert ReceiverInvalid(); - } - } - - /// @notice Transfers tokens `ids` in corresponding batches `amounts` from - /// address `from` to address `to`, while ensuring `to` can receive tokens. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the token to be transferred. - /// @param to The new owner address of the token being transferred. - /// @param ids A list of the token ids being transferred. - /// @param amounts A list of the amounts of each token id being transferred. - /// @param data Additional transfer data to pass to the receiving contract. - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) public virtual { - if (ids.length != amounts.length) { - revert ArityMismatch(); - } - - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - uint256 id; - uint256 amount; - - for (uint256 i = 0; i < ids.length; ) { - id = ids[i]; - amount = amounts[i]; - _balanceOf[from][id] -= amount; - _balanceOf[to][id] += amount; - unchecked { - ++i; - } - } - - emit TransferBatch(msg.sender, from, to, ids, amounts); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155BatchReceived( - msg.sender, - from, - ids, - amounts, - data - ) != - IERC1155Receiver.onERC1155BatchReceived.selector - ) { - revert SafeTransferUnsupported(); - } else if (to == address(0)) { - revert ReceiverInvalid(); - } - } - - /// @notice Retrieves balance of address `owner` for token of id `id`. - /// @param owner The token owner's address. - /// @param id The id of the token being queried. - /// @return The number of tokens address `owner` owns of type `id`. - function balanceOf(address owner, uint256 id) public view virtual returns (uint256) { - return _balanceOf[owner][id]; - } - - /// @notice Retrieves balances of multiple owner / token type pairs. - /// @param owners List of token owner addresses. - /// @param ids List of token type identifiers. - /// @return balances List of balances corresponding to the owner / id pairs. - function balanceOfBatch(address[] memory owners, uint256[] memory ids) - public - view - virtual - returns (uint256[] memory balances) - { - if (owners.length != ids.length) { - revert ArityMismatch(); - } - - balances = new uint256[](owners.length); - - unchecked { - for (uint256 i = 0; i < owners.length; ++i) { - balances[i] = _balanceOf[owners[i]][ids[i]]; - } - } - } - - /// @notice Sets the operator for the sender address. - function setApprovalForAll(address operator, bool approved) public virtual { - isApprovedForAll[msg.sender][operator] = approved; - } - - /// @notice Checks if interface of identifier `id` is supported. - /// @param id The ERC-165 interface identifier. - /// @return True if interface id `id` is supported, False otherwise. - function supportsInterface(bytes4 id) public pure virtual returns (bool) { - return - id == _ERC165_INTERFACE_ID || - id == _ERC1155_INTERFACE_ID; - } - - /// @notice Mints token of id `id` to address `to`. - /// @param to Address receiving the minted NFT. - /// @param id The id of the token type being minted. - function _mint(address to, uint256 id) internal virtual { - unchecked { - ++_balanceOf[to][id]; - } - - emit TransferSingle(msg.sender, address(0), to, id, 1); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155Received( - msg.sender, - address(0), - id, - 1, - "" - ) != - IERC1155Receiver.onERC1155Received.selector - ) { - revert SafeTransferUnsupported(); - } else if (to == address(0)) { - revert ReceiverInvalid(); - } - } - -} - diff --git a/assets/eip-5700/erc1155/ERC1155Bindable.sol b/assets/eip-5700/erc1155/ERC1155Bindable.sol deleted file mode 100644 index b55f361aca6c3f..00000000000000 --- a/assets/eip-5700/erc1155/ERC1155Bindable.sol +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.20; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; - -import {ERC1155} from "./ERC1155.sol"; -import {IERC1155Bindable} from "../interfaces/IERC1155Bindable.sol"; - -/// @title ERC-1155 Bindable Reference Implementation. -contract ERC1155Bindable is ERC1155, IERC1155Bindable { - - /// @notice Tracks the bound balance of an asset for a specific token type. - mapping(address => mapping(uint256 => mapping(uint256 => uint256))) public boundBalanceOf; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC1155_BINDER_INTERFACE_ID = 0x2ac2d2bc; - bytes4 private constant _ERC1155_BINDABLE_INTERFACE_ID = 0xd92c3ff0; - - /// @inheritdoc IERC1155Bindable - function boundBalanceOfBatch( - address bindAddress, - uint256[] calldata bindIds, - uint256[] calldata tokenIds - ) public view returns (uint256[] memory balances) { - if (bindIds.length != tokenIds.length) { - revert ArityMismatch(); - } - - balances = new uint256[](bindIds.length); - - unchecked { - for (uint256 i = 0; i < bindIds.length; ++i) { - balances[i] = boundBalanceOf[bindAddress][bindIds[i]][tokenIds[i]]; - } - } - } - - /// @inheritdoc IERC1155Bindable - function bind( - address from, - address bindAddress, - uint256 bindId, - uint256 tokenId, - uint256 amount - ) public { - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - if (IERC721(bindAddress).ownerOf(bindId) == address(0)) { - revert BindInvalid(); - } - - boundBalanceOf[bindAddress][bindId][tokenId] += amount; - _balanceOf[from][tokenId] -= amount; - _balanceOf[bindAddress][tokenId] += amount; - - emit TransferSingle(msg.sender, from, bindAddress, tokenId, amount); - emit Bind(msg.sender, from, bindAddress, bindId, tokenId, amount); - - } - - /// @notice Binds `amounts` tokens of `tokenIds` to NFT `bindId` at address `bindAddress`. - /// @param from The owner address of the unbound tokens. - /// @param bindAddress The contract address of the NFTs being bound to. - /// @param bindId The identifiers of the NFT being bound to. - /// @param tokenIds The identifiers of the binding token types. - /// @param amounts The number of tokens per type binding to the NFTs. - function batchBind( - address from, - address bindAddress, - uint256 bindId, - uint256[] calldata tokenIds, - uint256[] calldata amounts - ) public { - if (msg.sender != from && !isApprovedForAll[from][msg.sender]) { - revert SenderUnauthorized(); - } - - if (IERC721(bindAddress).ownerOf(bindId) == address(0)) { - revert BindInvalid(); - } - - if (tokenIds.length != amounts.length) { - revert ArityMismatch(); - } - - for (uint256 i = 0; i < tokenIds.length; i++) { - _balanceOf[from][tokenIds[i]] -= amounts[i]; - _balanceOf[bindAddress][tokenIds[i]] += amounts[i]; - boundBalanceOf[bindAddress][bindId][tokenIds[i]] += amounts[i]; - } - - emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts); - emit BindBatch(msg.sender, from, bindAddress, bindId, tokenIds, amounts); - - } - - /// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`. - /// @param from The owner address of the NFT the tokens are bound to. - /// @param to The address of the unbound tokens' new owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token type. - /// @param amount The number of tokens unbinding from the NFT. - function unbind( - address from, - address to, - address bindAddress, - uint256 bindId, - uint256 tokenId, - uint256 amount - ) public { - IERC721 binder = IERC721(bindAddress); - - if (binder.ownerOf(bindId) != from) { - revert OwnerInvalid(); - } - - if ( - msg.sender != from && - msg.sender != binder.getApproved(tokenId) && - !binder.isApprovedForAll(from, msg.sender) - ) { - revert SenderUnauthorized(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - _balanceOf[to][tokenId] += amount; - _balanceOf[bindAddress][tokenId] -= amount; - boundBalanceOf[bindAddress][bindId][tokenId] -= amount; - - emit Unbind(msg.sender, bindAddress, to, bindAddress, bindId, tokenId, amount); - emit TransferSingle(msg.sender, bindAddress, to, tokenId, amount); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155Received(msg.sender, from, amount, tokenId, "") - != - IERC1155Receiver.onERC1155Received.selector - ) { - revert SafeTransferUnsupported(); - } - } - - /// @notice Unbinds `amount` tokens of `tokenId` from NFT `bindId` at address `bindAddress`. - /// @param from The owner address of the unbound tokens. - /// @param to The address of the unbound tokens' new owner. - /// @param bindAddress The contract address of the NFTs being unbound from. - /// @param bindId The identifiers of the NFT being unbound from. - /// @param tokenIds The identifiers of the unbinding token types. - /// @param amounts The number of tokens per type unbinding from the NFTs. - function batchUnbind( - address from, - address to, - address bindAddress, - uint256 bindId, - uint256[] calldata tokenIds, - uint256[] calldata amounts - ) public { - IERC721 binder = IERC721(bindAddress); - - if (binder.ownerOf(bindId) != from) { - revert OwnerInvalid(); - } - - if ( - msg.sender != from && - msg.sender != binder.getApproved(bindId) && - !binder.isApprovedForAll(from, msg.sender) - ) { - revert SenderUnauthorized(); - } - - if (tokenIds.length != amounts.length) { - revert ArityMismatch(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - for (uint256 i = 0; i < tokenIds.length; i++) { - - _balanceOf[to][tokenIds[i]] += amounts[i]; - _balanceOf[bindAddress][tokenIds[i]] -= amounts[i]; - boundBalanceOf[bindAddress][bindId][tokenIds[i]] -= amounts[i]; - } - - emit UnbindBatch(msg.sender, from, to, bindAddress, bindId, tokenIds, amounts); - emit TransferBatch(msg.sender, from, bindAddress, tokenIds, amounts); - - if ( - to.code.length != 0 && - IERC1155Receiver(to).onERC1155BatchReceived(msg.sender, from, tokenIds, amounts, "") - != - IERC1155Receiver.onERC1155BatchReceived.selector - ) { - revert SafeTransferUnsupported(); - } - } - - function supportsInterface(bytes4 id) public pure override(ERC1155, IERC165) returns (bool) { - return super.supportsInterface(id) || id == _ERC1155_BINDABLE_INTERFACE_ID; - } - -} diff --git a/assets/eip-5700/erc721/ERC721.sol b/assets/eip-5700/erc721/ERC721.sol deleted file mode 100644 index c3f50d98bbdf00..00000000000000 --- a/assets/eip-5700/erc721/ERC721.sol +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -import {IERC721Errors} from "../interfaces/IERC721Errors.sol"; - -/// @title Reference Minimal ERC-721 Contract -contract ERC721 is IERC721, IERC721Errors { - - /// @notice The total number of NFTs in circulation. - uint256 public totalSupply; - - /// @notice Gets the approved address for an NFT. - /// @dev This implementation does not throw for zero-address queries. - mapping(uint256 => address) public getApproved; - - /// @notice Gets the number of NFTs owned by an address. - mapping(address => uint256) internal _balanceOf; - - /// @dev Tracks the assigned owner of an address. - mapping(uint256 => address) internal _ownerOf; - - /// @dev Checks for an owner if an address is an authorized operator. - mapping(address => mapping(address => bool)) internal _operatorApprovals; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC721_INTERFACE_ID = 0x80ac58cd; - - /// @notice Gets the assigned owner for token `id`. - /// @param id The id of the NFT being queried. - /// @return The address of the owner of the NFT of id `id`. - function ownerOf(uint256 id) external view virtual returns (address) { - return _ownerOf[id]; - } - - /// @notice Gets number of NFTs owned by address `owner`. - /// @param owner The address whose balance is being queried. - /// @return The number of NFTs owned by address `owner`. - function balanceOf(address owner) external view virtual returns (uint256) { - return _balanceOf[owner]; - } - - /// @notice Sets approved address of NFT of id `id` to address `approved`. - /// @param approved The new approved address for the NFT. - /// @param id The id of the NFT to approve. - function approve(address approved, uint256 id) external virtual { - address owner = _ownerOf[id]; - - if (msg.sender != owner && !_operatorApprovals[owner][msg.sender]) { - revert SenderUnauthorized(); - } - - getApproved[id] = approved; - emit Approval(owner, approved, id); - } - - /// @notice Checks if `operator` is an authorized operator for `owner`. - /// @param owner The address of the owner. - /// @param operator The address of the owner's operator. - /// @return True if `operator` is approved operator of `owner`, else False. - function isApprovedForAll(address owner, address operator) - external - view - virtual returns (bool) - { - return _operatorApprovals[owner][operator]; - } - - /// @notice Sets the operator for `msg.sender` to `operator`. - /// @param operator The operator address that will manage the sender's NFTs. - /// @param approved Whether operator is allowed to operate sender's NFTs. - function setApprovalForAll(address operator, bool approved) external virtual { - _operatorApprovals[msg.sender][operator] = approved; - emit ApprovalForAll(msg.sender, operator, approved); - } - - /// @notice Checks if interface of identifier `id` is supported. - /// @param id The ERC-165 interface identifier. - /// @return True if interface id `id` is supported, false otherwise. - function supportsInterface(bytes4 id) public pure virtual returns (bool) { - return - id == _ERC165_INTERFACE_ID || - id == _ERC721_INTERFACE_ID; - } - - /// @notice Transfers NFT of id `id` from address `from` to address `to`, - /// with safety checks ensuring `to` is capable of receiving the NFT. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the NFT to be transferred. - /// @param to The new owner address of the NFT being transferred. - /// @param id The id of the NFT being transferred. - /// @param data Additional transfer data to pass to the receiving contract. - function safeTransferFrom( - address from, - address to, - uint256 id, - bytes memory data - ) public virtual { - transferFrom(from, to, id); - - if ( - to.code.length != 0 && - IERC721Receiver(to).onERC721Received(msg.sender, from, id, data) - != - IERC721Receiver.onERC721Received.selector - ) { - revert SafeTransferUnsupported(); - } - } - - /// @notice Transfers NFT of id `id` from address `from` to address `to`, - /// with safety checks ensuring `to` is capable of receiving the NFT. - /// @dev Safety checks are only performed if `to` is a smart contract. - /// @param from The existing owner address of the NFT to be transferred. - /// @param to The new owner address of the NFT being transferred. - /// @param id The id of the NFT being transferred. - function safeTransferFrom( - address from, - address to, - uint256 id - ) public virtual { - transferFrom(from, to, id); - - if ( - to.code.length != 0 && - IERC721Receiver(to).onERC721Received(msg.sender, from, id, "") - != - IERC721Receiver.onERC721Received.selector - ) { - revert SafeTransferUnsupported(); - } - } - - /// @notice Transfers NFT of id `id` from address `from` to address `to`, - /// without performing any safety checks. - /// @dev Existence of an NFT is inferred by having a non-zero owner address. - /// Transfers clear owner approvals, but `Approval` events are omitted. - /// @param from The existing owner address of the NFT being transferred. - /// @param to The new owner address of the NFT being transferred. - /// @param id The id of the NFT being transferred. - function transferFrom( - address from, - address to, - uint256 id - ) public virtual { - if (from != _ownerOf[id]) { - revert OwnerInvalid(); - } - - if ( - msg.sender != from && - msg.sender != getApproved[id] && - !_operatorApprovals[from][msg.sender] - ) { - revert SenderUnauthorized(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - _beforeTokenTransfer(from, to, id); - - delete getApproved[id]; - - unchecked { - _balanceOf[from]--; - _balanceOf[to]++; - } - - _ownerOf[id] = to; - emit Transfer(from, to, id); - } - - /// @dev Mints NFT of id `id` to address `to`. To save gas, it is assumed - /// that `maxSupply` < `type(uint256).max` (ex. for tabs, cap is very low). - /// @param to Address receiving the minted NFT. - /// @param id Identifier of the NFT being minted. - /// @return The id of the minted NFT. - function _mint(address to, uint256 id) internal virtual returns (uint256) { - if (to == address(0)) { - revert ReceiverInvalid(); - } - if (_ownerOf[id] != address(0)) { - revert TokenAlreadyMinted(); - } - - _beforeTokenTransfer(address(0), to, id); - - unchecked { - totalSupply++; - _balanceOf[to]++; - } - - _ownerOf[id] = to; - emit Transfer(address(0), to, id); - return id; - } - - /// @dev Burns NFT of id `id`, removing it from existence. - /// @param id Identifier of the NFT being burned - function _burn(uint256 id) internal virtual { - address owner = _ownerOf[id]; - - if (owner == address(0)) { - revert TokenNonExistent(); - } - - _beforeTokenTransfer(owner, address(0), id); - - unchecked { - totalSupply--; - _balanceOf[owner]--; - } - - delete _ownerOf[id]; - emit Transfer(owner, address(0), id); - } - - /// @notice Pre-transfer hook for embedding additional transfer behavior. - /// @param from The address of the existing owner of the NFT. - /// @param to The address of the new owner of the NFT. - /// @param id The id of the NFT being transferred. - function _beforeTokenTransfer(address from, address to, uint256 id) - internal - virtual - {} - -} diff --git a/assets/eip-5700/erc721/ERC721Bindable.sol b/assets/eip-5700/erc721/ERC721Bindable.sol deleted file mode 100644 index d71916ff069fd2..00000000000000 --- a/assets/eip-5700/erc721/ERC721Bindable.sol +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.20; - -import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; - -import {ERC721} from "./ERC721.sol"; -import {IERC721Bindable} from "../interfaces/IERC721Bindable.sol"; - -/// @title ERC-721 Bindable Reference Implementation. -contract ERC721Bindable is ERC721, IERC721Bindable { - - /// @notice Encapsulates a bound NFT contract address and identifier. - struct Binder { - address bindAddress; - uint256 bindId; - } - - /// @notice Tracks the token balance for a token-bound NFT. - mapping(address => mapping(uint256 => uint256)) public boundBalanceOf; - - /// @notice Tracks NFTs that bindable tokens are bound to. - mapping(uint256 => Binder) internal _bound; - - /// @dev EIP-165 identifiers for all supported interfaces. - bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 private constant _ERC721_BINDER_INTERFACE_ID = 0x2ac2d2bc; - bytes4 private constant _ERC721_BINDABLE_INTERFACE_ID = 0xd92c3ff0; - - /// @notice Gets the NFT address and identifier token `tokenId` is bound to. - /// @param tokenId The identifier of the token being queried. - /// @return The token-bound NFT contract address and numerical identifier. - function binderOf(uint256 tokenId) public view returns (address, uint256) { - Binder memory bound = _bound[tokenId]; - return (bound.bindAddress, bound.bindId); - } - - /// @notice Binds token `tokenId` to NFT `bindId` at address `bindAddress`. - /// @param from The address of the unbound token owner. - /// @param bindAddress The contract address of the NFT being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenId The identifier of the binding token. - function bind( - address from, - address bindAddress, - uint256 bindId, - uint256 tokenId - ) public { - if ( - _bound[tokenId].bindAddress != address(0) || - IERC721(bindAddress).ownerOf(bindId) == address(0) - ) { - revert BindInvalid(); - } - - if (from != _ownerOf[tokenId]) { - revert OwnerInvalid(); - } - - if ( - msg.sender != from && - msg.sender != getApproved[tokenId] && - !_operatorApprovals[from][msg.sender] - ) { - revert SenderUnauthorized(); - } - - delete getApproved[tokenId]; - - unchecked { - _balanceOf[from]--; - _balanceOf[bindAddress]++; - boundBalanceOf[bindAddress][bindId]++; - } - - _ownerOf[tokenId] = bindAddress; - _bound[tokenId] = Binder(bindAddress, bindId); - - emit Transfer(from, bindAddress, tokenId); - emit Bind(msg.sender, from, bindAddress, bindId, tokenId); - - } - - /// @notice Unbinds token `tokenId` from NFT `bindId` at address `bindAddress`. - /// @param from The address of the owner of the NFT the token is bound to. - /// @param to The address of the unbound token new owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token. - function unbind( - address from, - address to, - address bindAddress, - uint256 bindId, - uint256 tokenId - ) public { - Binder memory bound = _bound[tokenId]; - if ( - bound.bindAddress != bindAddress || - bound.bindId != bindId || - _ownerOf[tokenId] != bindAddress - ) { - revert BindInvalid(); - } - - IERC721 binder = IERC721(bindAddress); - - if (from != binder.ownerOf(bindId)) { - revert OwnerInvalid(); - } - - if ( - msg.sender != from && - msg.sender != binder.getApproved(tokenId) && - !binder.isApprovedForAll(from, msg.sender) - ) { - revert SenderUnauthorized(); - } - - if (to == address(0)) { - revert ReceiverInvalid(); - } - - delete getApproved[tokenId]; - - unchecked { - _balanceOf[to]++; - _balanceOf[bindAddress]--; - boundBalanceOf[bindAddress][bindId]--; - } - - _ownerOf[tokenId] = to; - delete _bound[tokenId]; - - emit Unbind(msg.sender, from, to, bindAddress, bindId, tokenId); - emit Transfer(bindAddress, to, tokenId); - - if ( - to.code.length != 0 && - IERC721Receiver(to).onERC721Received(msg.sender, bindAddress, tokenId, "") - != - IERC721Receiver.onERC721Received.selector - ) { - revert SafeTransferUnsupported(); - } - - } - - function supportsInterface(bytes4 id) public pure override(ERC721, IERC165) returns (bool) { - return super.supportsInterface(id) || id == _ERC721_BINDABLE_INTERFACE_ID; - } - -} diff --git a/assets/eip-5700/interfaces/IBindableErrors.sol b/assets/eip-5700/interfaces/IBindableErrors.sol deleted file mode 100644 index 43dcb86b99d3a3..00000000000000 --- a/assets/eip-5700/interfaces/IBindableErrors.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.20; - -/// @title ERC-721 Bindable Errors Interface -interface IBindableErrors { - - /// @notice Bind is not valid. - error BindInvalid(); - -} diff --git a/assets/eip-5700/interfaces/IERC1155Bindable.sol b/assets/eip-5700/interfaces/IERC1155Bindable.sol deleted file mode 100644 index c4d491a6e981af..00000000000000 --- a/assets/eip-5700/interfaces/IERC1155Bindable.sol +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; - -import {IERC1155BindableErrors} from "./IERC1155BindableErrors.sol"; - -/// @title ERC-1155 Bindable Token Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-5656 -/// Note: the ERC-165 identifier for this interface is 0xd0d555c6. -interface IERC1155Bindable is IERC1155, IERC1155BindableErrors { - - /// @notice The `Bind` event MUST emit when token ownership is delegated - /// through an asset and when minting tokens bound to an existing asset. - /// @dev When minting bound tokens, `from` MUST be set to the zero address. - /// @param operator The address calling the bind (SHOULD be `msg.sender`). - /// @param from The address which owns the unbound token(s). - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the token type being bound. - /// @param amount The number of tokens of type `tokenId` being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param bindAddress The contract address handling asset ownership. - event Bind( - address indexed operator, - address indexed from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address indexed bindAddress - ); - - /// @notice The `BindBatch` event MUST emit when token ownership of - /// different token types are delegated through different assets at once - /// and when minting multiple token types bound to existing assets at once. - /// @dev When minting bound tokens, `from` MUST be set to the zero address. - /// @param operator The address calling the bind (SHOULD be `msg.sender`). - /// @param from The address which owns the unbound token(s). - /// @param to The address which owns the asset being bound to. - /// @param tokenIds The identifiers of the token types being bound. - /// @param amounts The number of tokens for each token type being bound. - /// @param bindIds The identifiers of the assets being bound to. - /// @param bindAddress The contract address handling asset ownership. - event BindBatch( - address indexed operator, - address indexed from, - address to, - uint256[] tokenIds, - uint256[] amounts, - uint256[] bindIds, - address indexed bindAddress - ); - - /// @notice The `Unbind` event MUST emit when asset-delegated token - /// ownership is revoked and when burning tokens bound to existing assets. - /// @dev When burning bound tokens, `to` MUST be set to the zero address. - /// @param operator The address calling the unbind (SHOULD be `msg.sender`). - /// @param from The address which owns the asset the token(s) are bound to. - /// @param to The address which will own the token(s) once unbound. - /// @param tokenId The identifier of the token type being unbound. - /// @param amount The number of tokens of type `tokenId` being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - event Unbind( - address indexed operator, - address indexed from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address indexed bindAddress - ); - - /// @notice The `UnbindBatch` event MUST emit when asset-delegated token - /// ownership is revoked for multiple token types at once and when burning - /// multiple token types bound to existing assets at once. - /// @dev When burning bound tokens, `to` MUST be set to the zero address. - /// @param operator The address calling the unbind (SHOULD be `msg.sender`). - /// @param from The address which owns the asset the token(s) are bound to. - /// @param to The address which will own the token(s) once unbound. - /// @param tokenIds The identifiers of the token types being unbound. - /// @param amounts The number of tokens for each token type being unbound. - /// @param bindIds The identifier of the assets being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - event UnbindBatch( - address indexed operator, - address indexed from, - address to, - uint256[] tokenIds, - uint256[] amounts, - uint256[] bindIds, - address indexed bindAddress - ); - - /// @notice Delegates ownership of `amount` tokens of type `tokenId` from - /// address `from` through asset `bindId` owned by address `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. The function also MUST throw if `from` owns fewer than - /// `amount` unbound tokens, or if `to` is not the asset owner. After - /// delegation of ownership, the function MUST check if `bindAddress` is a - /// valid contract (code size > 0), and if so, call `onERC1155Bind` on the - /// contract, throwing if the wrong identifier is returned (see "Binding - /// Rules") or if the contract is invalid. On bind completion, the function - /// MUST emit both `Bind` and IERC-1155 `TransferSingle` events to reflect - /// delegated ownership change. - /// @param from The address which owns the unbound token(s). - /// @param to The address which owns the asset being bound to. - /// @param tokenId The identifier of the token type being bound. - /// @param amount The number of tokens of type `tokenId` being bound. - /// @param bindId The identifier of the asset being bound to. - /// @param bindAddress The contract address handling asset ownership. - /// @param data Additional data sent with the `onERC1155Bind` hook. - function bind( - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Delegates ownership of `amounts` tokens of types `tokenIds` from - /// address `from` through assets `bindIds` owned by address `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// for `from`. The function also MUST throw if length of `amounts` is not - /// the same as `tokenIds` or `bindIds`, if any unbound balances of - /// `tokenIds` for `from` is less than that of `amounts`, or if `to` is not - /// the asset owner. After delegating ownership, the function MUST check if - /// `bindAddress` is a valid contract (code size > 0), and if so, call - /// `onERC1155BatchBind` on the contract, throwing if the wrong identifier - /// is returned (see "Binding Rules") or if the contract is invalid. On - /// bind completion, the function MUST emit both `BindBatch` and IERC-1155 - /// `TransferBatch` events to reflect delegated ownership changes. - /// @param from The address which owns the unbound tokens. - /// @param to The address which owns the assets being bound to. - /// @param tokenIds The identifiers of the token types being bound. - /// @param amounts The number of tokens for each token type being bound. - /// @param bindIds The identifiers of the assets being bound to. - /// @param bindAddress The contract address handling asset ownership. - /// @param data Additional data sent with the `onERC1155BatchBind` hook. - function batchBind( - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Revokes delegated ownership of `amount` tokens of type `tokenId` - /// owned by `from` bound to `bindId`, binding direct ownership to `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// or owner of the delegated asset `tokenId` is bound to. It also MUST - /// throw if `from` owns fewer than `amount` bound tokens, or if `to` is - /// the zero address. Once delegated ownership is revoked, the function - /// MUST check if `bindAddress` is a valid contract (code size > 0), and if - /// so, call `onERC1155Unbind` on the contract, throwing if the wrong - /// identifier is returned (see "Binding Rules") or if the contract is - /// invalid. The function also MUST check if `to` is a contract, and if so, - /// call on it `onERC1155Received`, throwing if the wrong identifier is - /// returned. On unbind completion, the function MUST emit both `Unbind` - /// and IERC-1155 `TransferSingle` events to reflect delegated ownership change. - /// @param from The address which owns the asset the token(s) are bound to. - /// @param to The address which will own the tokens once unbound. - /// @param tokenId The identifier of the token type being unbound. - /// @param amount The number of tokens of type `tokenId` being unbound. - /// @param bindId The identifier of the asset being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param data Additional data sent with the `onERC1155Unbind` hook. - function unbind( - address from, - address to, - uint256 tokenId, - uint256 amount, - uint256 bindId, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Revokes delegated ownership of `amounts` tokens of `tokenIds` - /// bound to assets `bindIds`, binding direct ownership to `to`. - /// @dev The function MUST throw unless `msg.sender` is an approved operator - /// or owner of all delegated assets `tokenIds` are bound to. It also MUST - /// throw if the length of `amounts` is not the same as `tokenIds` or - /// `bindIds`, if any bound balances of `tokenId` for `from` is less than - /// that of `amounts`, or if `to` is the zero address. Once delegated - /// ownership is revoked, the function MUST check if `bindAddress` is a - /// valid contract (code size > 0), and if so, call onERC1155BatchUnbind` - /// on it, throwing if a wrong identifier is returned (see "Binding Rules") - /// or if the contract is invalid. The function also MUST check if `to` is - /// a valid contract, and if so, call `onERC1155BatchReceived`, throwing if - /// the wrong identifier is returned. On unbind completion, the function - /// MUST emit the `BatchUnbind` and IERC-1155 `TransferBatch` events to - /// reflect delegated ownership changes. - /// @param from The address which owns the asset the tokens are bound to. - /// @param to The address which will own the tokens once unbound. - /// @param tokenIds The identifiers of the token types being unbound. - /// @param amounts The number of tokens for each token type being unbound. - /// @param bindIds The identifier of the assets being unbound from. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param data Additional data sent with the `onERC1155BatchUnbind` hook. - function batchUnbind( - address from, - address to, - uint256[] calldata tokenIds, - uint256[] calldata amounts, - uint256[] calldata bindIds, - address bindAddress, - bytes calldata data - ) external; - - /// @notice Gets the balance of bound tokens of type `tokenId` bound to the - /// asset `bindId` at address `bindAddress`. - /// @param bindId The identifier of the bound asset. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param tokenId The identifier of the bound token type being counted. - /// @return The total number of NFTs bound to the asset. - function boundBalanceOf( - address bindAddress, - uint256 bindId, - uint256 tokenId - ) external returns (uint256); - - /// @notice Gets the balance of bound tokens for multiple token types given - /// by `tokenIds` bound to assets `bindIds` at address `bindAddress`. - /// @notice Retrieves bound balances of multiple asset / token type pairs. - /// @param bindIds List of bound asset identifiers. - /// @param bindAddress The contract address handling bound asset ownership. - /// @param tokenIds The identifiers of the token type being counted. - /// @return balances The bound balances for each asset / token type pair. - function boundBalanceOfBatch( - address bindAddress, - uint256[] calldata bindIds, - uint256[] calldata tokenIds - ) external returns (uint256[] memory balances); - -} diff --git a/assets/eip-5700/interfaces/IERC1155Errors.sol b/assets/eip-5700/interfaces/IERC1155Errors.sol deleted file mode 100644 index d3f07129986394..00000000000000 --- a/assets/eip-5700/interfaces/IERC1155Errors.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-1155 Errors Interface -interface IERC1155Errors { - - /// @notice Arity mismatch between two arrays. - error ArityMismatch(); - - /// @notice Originating address does not own the NFT. - error OwnerInvalid(); - - /// @notice Receiving address cannot be the zero address. - error ReceiverInvalid(); - - /// @notice Receiving contract does not implement the ERC-1155 wallet interface. - error SafeTransferUnsupported(); - - /// @notice Sender is not NFT owner, approved address, or owner operator. - error SenderUnauthorized(); - - /// @notice Token has already minted. - error TokenAlreadyMinted(); - - /// @notice NFT does not exist. - error TokenNonExistent(); - -} diff --git a/assets/eip-5700/interfaces/IERC721Bindable.sol b/assets/eip-5700/interfaces/IERC721Bindable.sol deleted file mode 100644 index 17a8bb4e362249..00000000000000 --- a/assets/eip-5700/interfaces/IERC721Bindable.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.20; - -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import {IERC721BindableErrors} from "./IERC721BindableErrors.sol"; - -/// @title ERC-721 Bindable Token Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-5700 -/// Note: the ERC-165 identifier for this interface is 0x82a34a7d. -interface IERC721Bindable is IERC721, IERC721BindableErrors { - - /// @notice This event emits when an unbound token is bound to an NFT. - /// @param operator The address approved to perform the binding. - /// @param from The address of the unbound token owner. - /// @param bindAddress The contract address of the NFT being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenId The identifier of binding token. - event Bind( - address indexed operator, - address indexed from, - address indexed bindAddress, - uint256 bindId, - uint256 tokenId - ); - - /// @notice This event emits when an NFT-bound token is unbound. - /// @param operator The address approved to perform the unbinding. - /// @param from The owner of the NFT the token is bound to. - /// @param to The address of the new unbound token owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token. - event Unbind( - address indexed operator, - address indexed from, - address to, - address indexed bindAddress, - uint256 bindId, - uint256 tokenId - ); - - /// @notice Binds token `tokenId` to NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is the current owner, - /// an authorized operator, or the approved address for the token. It also - /// MUST throw if the token is already bound or if `from` is not the token - /// owner. Finally, it MUST throw if the NFT contract does not support the - /// ERC-721 interface or if the NFT being bound to does not exist. Before - /// binding, token ownership MUST be transferred to the contract address of - /// the NFT. On bind completion, the function MUST emit `Transfer` & `Bind` - /// events to reflect the implicit token transfer and subsequent bind. - /// @param from The address of the unbound token owner. - /// @param bindAddress The contract address of the NFT being bound to. - /// @param bindId The identifier of the NFT being bound to. - /// @param tokenId The identifier of the binding token. - function bind( - address from, - address bindAddress, - uint256 bindId, - uint256 tokenId - ) external; - - /// @notice Unbinds token `tokenId` from NFT `bindId` at address `bindAddress`. - /// @dev The function MUST throw unless `msg.sender` is the current owner, - /// an authorized operator, or the approved address for the NFT the token - /// is bound to. It also MUST throw if the token is unbound, if `from` is - /// not the owner of the bound NFT, or if `to` is the zero address. After - /// unbinding, token ownership MUST be transferred to `to`, during which - /// the function MUST check if `to` is a valid contract (code size > 0), - /// and if so, call `onERC721Received`, throwing if the wrong identifier is - /// returned. On unbind completion, the function MUST emit `Unbind` & - /// `Transfer` events to reflect the unbind and subsequent transfer. - /// @param from The address of the owner of the NFT the token is bound to. - /// @param to The address of the unbound token new owner. - /// @param bindAddress The contract address of the NFT being unbound from. - /// @param bindId The identifier of the NFT being unbound from. - /// @param tokenId The identifier of the unbinding token. - function unbind( - address from, - address to, - address bindAddress, - uint256 bindId, - uint256 tokenId - ) external; - - /// @notice Gets the NFT address and identifier token `tokenId` is bound to. - /// @dev When the token is unbound, this function MUST return the zero - /// address for the address portion to indicate no binding exists. - /// @param tokenId The identifier of the token being queried. - /// @return The token-bound NFT contract address and numerical identifier. - function binderOf(uint256 tokenId) external view returns (address, uint256); - - /// @notice Gets total tokens bound to NFT `bindId` at address `bindAddress`. - /// @param bindAddress The contract address of the NFT being queried. - /// @param bindId The identifier of the NFT being queried. - /// @return The total number of tokens bound to the queried NFT. - function boundBalanceOf(address bindAddress, uint256 bindId) external view returns (uint256); - -} diff --git a/assets/eip-5700/interfaces/IERC721Errors.sol b/assets/eip-5700/interfaces/IERC721Errors.sol deleted file mode 100644 index 2fc6494c098d5b..00000000000000 --- a/assets/eip-5700/interfaces/IERC721Errors.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.16; - -/// @title ERC-721 Errors Interface -interface IERC721Errors { - - /// @notice Originating address does not own the NFT. - error OwnerInvalid(); - - /// @notice Receiving address cannot be the zero address. - error ReceiverInvalid(); - - /// @notice Receiving contract does not implement the ERC-721 wallet interface. - error SafeTransferUnsupported(); - - /// @notice Sender is not NFT owner, approved address, or owner operator. - error SenderUnauthorized(); - - /// @notice NFT supply has hit maximum capacity. - error SupplyMaxCapacity(); - - /// @notice Token has already minted. - error TokenAlreadyMinted(); - - /// @notice NFT does not exist. - error TokenNonExistent(); - -} - diff --git a/assets/eip-5725/README.md b/assets/eip-5725/README.md deleted file mode 100644 index a46df03e6f6500..00000000000000 --- a/assets/eip-5725/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# EIP-5725: Transferrable Vesting NFT - Reference Implementation - -This repository serves as a reference implementation for **EIP-5725 Transferrable Vesting NFT Standard**. A Non-Fungible Token (NFT) standard used to vest ERC-20 tokens over a vesting release curve. - -## Contents - -- [EIP-5725 Specification](./contracts/IERC5725.sol): Interface and definitions for the EIP-5725 specification. -- [ERC-5725 Implementation (abstract)](./contracts/ERC5725.sol): ERC-5725 contract which can be extended to implement the specification. -- [VestingNFT Implementation](./contracts/reference/LinearVestingNFT.sol): Full ERC-5725 implementation using cliff vesting curve. -- [LinearVestingNFT Implementation](./contracts/reference/VestingNFT.sol): Full ERC-5725 implementation using linear vesting curve. diff --git a/assets/eip-5725/contracts/ERC5725.sol b/assets/eip-5725/contracts/ERC5725.sol deleted file mode 100644 index 298421487a8ab9..00000000000000 --- a/assets/eip-5725/contracts/ERC5725.sol +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "./IERC5725.sol"; - -abstract contract ERC5725 is IERC5725, ERC721 { - using SafeERC20 for IERC20; - - /// @dev mapping for claimed payouts - mapping(uint256 => uint256) /*tokenId*/ /*claimed*/ internal _payoutClaimed; - - /// @dev Mapping from token ID to approved tokenId operator - mapping(uint256 => address) private _tokenIdApprovals; - - /// @dev Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) /* owner */ /*(operator, isApproved)*/ internal _operatorApprovals; - - /** - * @notice Checks if the tokenId exists and its valid - * @param tokenId The NFT token id - */ - modifier validToken(uint256 tokenId) { - require(_exists(tokenId), "ERC5725: invalid token ID"); - _; - } - - /** - * @dev See {IERC5725}. - */ - function claim(uint256 tokenId) external override(IERC5725) validToken(tokenId) { - require(isApprovedClaimOrOwner(msg.sender, tokenId), "ERC5725: not owner or operator"); - - uint256 amountClaimed = claimablePayout(tokenId); - require(amountClaimed > 0, "ERC5725: No pending payout"); - - emit PayoutClaimed(tokenId, msg.sender, amountClaimed); - - _payoutClaimed[tokenId] += amountClaimed; - IERC20(payoutToken(tokenId)).safeTransfer(msg.sender, amountClaimed); - } - - /** - * @dev See {IERC5725}. - */ - function setClaimApprovalForAll(address operator, bool approved) external override(IERC5725) { - _setClaimApprovalForAll(operator, approved); - emit ClaimApprovalForAll(msg.sender, operator, approved); - } - - /** - * @dev See {IERC5725}. - */ - function setClaimApproval( - address operator, - bool approved, - uint256 tokenId - ) external override(IERC5725) validToken(tokenId) { - _setClaimApproval(operator, tokenId); - emit ClaimApproval(msg.sender, operator, tokenId, approved); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayout(uint256 tokenId) public view override(IERC5725) returns (uint256 payout) { - return vestedPayoutAtTime(tokenId, block.timestamp); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayoutAtTime( - uint256 tokenId, - uint256 timestamp - ) public view virtual override(IERC5725) returns (uint256 payout); - - /** - * @dev See {IERC5725}. - */ - function vestingPayout( - uint256 tokenId - ) public view override(IERC5725) validToken(tokenId) returns (uint256 payout) { - return _payout(tokenId) - vestedPayout(tokenId); - } - - /** - * @dev See {IERC5725}. - */ - function claimablePayout( - uint256 tokenId - ) public view override(IERC5725) validToken(tokenId) returns (uint256 payout) { - return vestedPayout(tokenId) - _payoutClaimed[tokenId]; - } - - /** - * @dev See {IERC5725}. - */ - function claimedPayout( - uint256 tokenId - ) public view override(IERC5725) validToken(tokenId) returns (uint256 payout) { - return _payoutClaimed[tokenId]; - } - - /** - * @dev See {IERC5725}. - */ - function vestingPeriod( - uint256 tokenId - ) public view override(IERC5725) validToken(tokenId) returns (uint256 vestingStart, uint256 vestingEnd) { - return (_startTime(tokenId), _endTime(tokenId)); - } - - /** - * @dev See {IERC5725}. - */ - function payoutToken(uint256 tokenId) public view override(IERC5725) validToken(tokenId) returns (address token) { - return _payoutToken(tokenId); - } - - /** - * @dev See {IERC165-supportsInterface}. - * IERC5725 interfaceId = 0xbd3a202b - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC721, IERC165) returns (bool supported) { - return interfaceId == type(IERC5725).interfaceId || super.supportsInterface(interfaceId); - } - - /** - * @dev See {IERC5725}. - */ - function getClaimApproved(uint256 tokenId) public view returns (address operator) { - return _tokenIdApprovals[tokenId]; - } - - /** - * @dev Returns true if `owner` has set `operator` to manage all `tokenId`s. - * @param owner The owner allowing `operator` to manage all `tokenId`s. - * @param operator The address who is given permission to spend tokens on behalf of the `owner`. - */ - function isClaimApprovedForAll(address owner, address operator) public view returns (bool isClaimApproved) { - return _operatorApprovals[owner][operator]; - } - - /** - * @dev Public view which returns true if the operator has permission to claim for `tokenId` - * @notice To remove permissions, set operator to zero address. - * - * @param operator The address that has permission for a `tokenId`. - * @param tokenId The NFT `tokenId`. - */ - function isApprovedClaimOrOwner(address operator, uint256 tokenId) public view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (operator == owner || isClaimApprovedForAll(owner, operator) || getClaimApproved(tokenId) == operator); - } - - /** - * @dev Internal function to set the operator status for a given owner to manage all `tokenId`s. - * @notice To remove permissions, set approved to false. - * - * @param operator The address who is given permission to spend vested tokens. - * @param approved The approved status. - */ - function _setClaimApprovalForAll(address operator, bool approved) internal virtual { - _operatorApprovals[msg.sender][operator] = approved; - } - - /** - * @dev Internal function to set the operator status for a given tokenId. - * @notice To remove permissions, set operator to zero address. - * - * @param operator The address who is given permission to spend vested tokens. - * @param tokenId The NFT `tokenId`. - */ - function _setClaimApproval(address operator, uint256 tokenId) internal virtual { - require(ownerOf(tokenId) == msg.sender, "ERC5725: not owner of tokenId"); - _tokenIdApprovals[tokenId] = operator; - } - - /** - * @dev Internal function to hook into {IERC721-_afterTokenTransfer}, when a token is being transferred. - * Removes permissions to _tokenIdApprovals[tokenId] when the tokenId is transferred, burnt, but not on mint. - * - * @param from The address from which the tokens are being transferred. - * @param to The address to which the tokens are being transferred. - * @param firstTokenId The first tokenId in the batch that is being transferred. - * @param batchSize The number of tokens being transferred in this batch. - */ - function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal override { - super._beforeTokenTransfer(from, to, firstTokenId, batchSize); - if (from != address(0)) { - delete _tokenIdApprovals[firstTokenId]; - } - } - - /** - * @dev Internal function to get the payout token of a given vesting NFT - * - * @param tokenId on which to check the payout token address - * @return address payout token address - */ - function _payoutToken(uint256 tokenId) internal view virtual returns (address); - - /** - * @dev Internal function to get the total payout of a given vesting NFT. - * @dev This is the total that will be paid out to the NFT owner, including historical tokens. - * - * @param tokenId to check - * @return uint256 the total payout of a given vesting NFT - */ - function _payout(uint256 tokenId) internal view virtual returns (uint256); - - /** - * @dev Internal function to get the start time of a given vesting NFT - * - * @param tokenId to check - * @return uint256 the start time in epoch timestamp - */ - function _startTime(uint256 tokenId) internal view virtual returns (uint256); - - /** - * @dev Internal function to get the end time of a given vesting NFT - * - * @param tokenId to check - * @return uint256 the end time in epoch timestamp - */ - function _endTime(uint256 tokenId) internal view virtual returns (uint256); -} diff --git a/assets/eip-5725/contracts/IERC5725.sol b/assets/eip-5725/contracts/IERC5725.sol deleted file mode 100644 index ba71f10e531cd2..00000000000000 --- a/assets/eip-5725/contracts/IERC5725.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/** - * @title Non-Fungible Vesting Token Standard. - * @notice A non-fungible token standard used to vest ERC-20 tokens over a vesting release curve - * scheduled using timestamps. - * @dev Because this standard relies on timestamps for the vesting schedule, it's important to keep track of the - * tokens claimed per Vesting NFT so that a user cannot withdraw more tokens than allotted for a specific Vesting NFT. - * @custom:interface-id 0xbd3a202b - */ -interface IERC5725 is IERC721 { - /** - * This event is emitted when the payout is claimed through the claim function. - * @param tokenId the NFT tokenId of the assets being claimed. - * @param recipient The address which is receiving the payout. - * @param claimAmount The amount of tokens being claimed. - */ - event PayoutClaimed(uint256 indexed tokenId, address indexed recipient, uint256 claimAmount); - - /** - * This event is emitted when an `owner` sets an address to manage token claims for all tokens. - * @param owner The address setting a manager to manage all tokens. - * @param spender The address being permitted to manage all tokens. - * @param approved A boolean indicating whether the spender is approved to claim for all tokens. - */ - event ClaimApprovalForAll(address indexed owner, address indexed spender, bool approved); - - /** - * This event is emitted when an `owner` sets an address to manage token claims for a `tokenId`. - * @param owner The `owner` of `tokenId`. - * @param spender The address being permitted to manage a tokenId. - * @param tokenId The unique identifier of the token being managed. - * @param approved A boolean indicating whether the spender is approved to claim for `tokenId`. - */ - event ClaimApproval(address indexed owner, address indexed spender, uint256 indexed tokenId, bool approved); - - /** - * @notice Claim the pending payout for the NFT. - * @dev MUST grant the claimablePayout value at the time of claim being called to `msg.sender`. - * MUST revert if not called by the token owner or approved users. - * MUST emit PayoutClaimed. - * SHOULD revert if there is nothing to claim. - * @param tokenId The NFT token id. - */ - function claim(uint256 tokenId) external; - - /** - * @notice Number of tokens for the NFT which have been claimed at the current timestamp. - * @param tokenId The NFT token id. - * @return payout The total amount of payout tokens claimed for this NFT. - */ - function claimedPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Number of tokens for the NFT which can be claimed at the current timestamp. - * @dev It is RECOMMENDED that this is calculated as the `vestedPayout()` subtracted from `payoutClaimed()`. - * @param tokenId The NFT token id. - * @return payout The amount of unlocked payout tokens for the NFT which have not yet been claimed. - */ - function claimablePayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Total amount of tokens which have been vested at the current timestamp. - * This number also includes vested tokens which have been claimed. - * @dev It is RECOMMENDED that this function calls `vestedPayoutAtTime` - * with `block.timestamp` as the `timestamp` parameter. - * @param tokenId The NFT token id. - * @return payout Total amount of tokens which have been vested at the current timestamp. - */ - function vestedPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice Total amount of vested tokens at the provided timestamp. - * This number also includes vested tokens which have been claimed. - * @dev `timestamp` MAY be both in the future and in the past. - * Zero MUST be returned if the timestamp is before the token was minted. - * @param tokenId The NFT token id. - * @param timestamp The timestamp to check on, can be both in the past and the future. - * @return payout Total amount of tokens which have been vested at the provided timestamp. - */ - function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) external view returns (uint256 payout); - - /** - * @notice Number of tokens for an NFT which are currently vesting. - * @dev The sum of vestedPayout and vestingPayout SHOULD always be the total payout. - * @param tokenId The NFT token id. - * @return payout The number of tokens for the NFT which are vesting until a future date. - */ - function vestingPayout(uint256 tokenId) external view returns (uint256 payout); - - /** - * @notice The start and end timestamps for the vesting of the provided NFT. - * MUST return the timestamp where no further increase in vestedPayout occurs for `vestingEnd`. - * @param tokenId The NFT token id. - * @return vestingStart The beginning of the vesting as a unix timestamp. - * @return vestingEnd The ending of the vesting as a unix timestamp. - */ - function vestingPeriod(uint256 tokenId) external view returns (uint256 vestingStart, uint256 vestingEnd); - - /** - * @notice Token which is used to pay out the vesting claims. - * @param tokenId The NFT token id. - * @return token The token which is used to pay out the vesting claims. - */ - function payoutToken(uint256 tokenId) external view returns (address token); - - /** - * @notice Sets a global `operator` with permission to manage all tokens owned by the current `msg.sender`. - * @param operator The address to let manage all tokens. - * @param approved A boolean indicating whether the spender is approved to claim for all tokens. - */ - function setClaimApprovalForAll(address operator, bool approved) external; - - /** - * @notice Sets a tokenId `operator` with permission to manage a single `tokenId` owned by the `msg.sender`. - * @param operator The address to let manage a single `tokenId`. - * @param tokenId the `tokenId` to be managed. - * @param approved A boolean indicating whether the spender is approved to claim for all tokens. - */ - function setClaimApproval(address operator, bool approved, uint256 tokenId) external; - - /** - * @notice Returns true if `owner` has set `operator` to manage all `tokenId`s. - * @param owner The owner allowing `operator` to manage all `tokenId`s. - * @param operator The address who is given permission to spend tokens on behalf of the `owner`. - */ - function isClaimApprovedForAll(address owner, address operator) external view returns (bool isClaimApproved); - - /** - * @notice Returns the operating address for a `tokenId`. - * If `tokenId` is not managed, then returns the zero address. - * @param tokenId The NFT `tokenId` to query for a `tokenId` manager. - */ - function getClaimApproved(uint256 tokenId) external view returns (address operator); -} diff --git a/assets/eip-5725/contracts/mocks/ERC20Mock.sol b/assets/eip-5725/contracts/mocks/ERC20Mock.sol deleted file mode 100644 index cdf0e7a63fee6b..00000000000000 --- a/assets/eip-5725/contracts/mocks/ERC20Mock.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.17; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract ERC20Mock is ERC20 { - uint8 private _decimals; - - constructor(uint256 supply_, uint8 decimals_, string memory name_, string memory symbol_) ERC20(name_, symbol_) { - _mint(msg.sender, supply_); - _decimals = decimals_; - } - - function mint(uint256 amount, address to) public { - _mint(to, amount); - } - - function decimals() public view virtual override returns (uint8) { - return _decimals; - } -} diff --git a/assets/eip-5725/contracts/reference/LinearVestingNFT.sol b/assets/eip-5725/contracts/reference/LinearVestingNFT.sol deleted file mode 100644 index 2f5e49c25ba552..00000000000000 --- a/assets/eip-5725/contracts/reference/LinearVestingNFT.sol +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "../ERC5725.sol"; - -contract LinearVestingNFT is ERC5725 { - using SafeERC20 for IERC20; - - struct VestDetails { - IERC20 payoutToken; /// @dev payout token - uint256 payout; /// @dev payout token remaining to be paid - uint128 startTime; /// @dev when vesting starts - uint128 endTime; /// @dev when vesting end - uint128 cliff; /// @dev duration in seconds of the cliff in which tokens will be begin releasing - } - mapping(uint256 => VestDetails) public vestDetails; /// @dev maps the vesting data with tokenIds - - /// @dev tracker of current NFT id - uint256 private _tokenIdTracker; - - /** - * @dev See {IERC5725}. - */ - constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - - /** - * @notice Creates a new vesting NFT and mints it - * @dev Token amount should be approved to be transferred by this contract before executing create - * @param to The recipient of the NFT - * @param amount The total assets to be locked over time - * @param startTime When the vesting starts in epoch timestamp - * @param duration The vesting duration in seconds - * @param cliff The cliff duration in seconds - * @param token The ERC20 token to vest over time - */ - function create( - address to, - uint256 amount, - uint128 startTime, - uint128 duration, - uint128 cliff, - IERC20 token - ) public virtual { - require(startTime >= block.timestamp, "startTime cannot be on the past"); - require(to != address(0), "to cannot be address 0"); - require(cliff <= duration, "duration needs to be more than cliff"); - - uint256 newTokenId = _tokenIdTracker; - - vestDetails[newTokenId] = VestDetails({ - payoutToken: token, - payout: amount, - startTime: startTime, - endTime: startTime + duration, - cliff: startTime + cliff - }); - - _tokenIdTracker++; - _mint(to, newTokenId); - IERC20(payoutToken(newTokenId)).safeTransferFrom(msg.sender, address(this), amount); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayoutAtTime( - uint256 tokenId, - uint256 timestamp - ) public view override(ERC5725) validToken(tokenId) returns (uint256 payout) { - if (timestamp < _cliff(tokenId)) { - return 0; - } - if (timestamp > _endTime(tokenId)) { - return _payout(tokenId); - } - return (_payout(tokenId) * (timestamp - _startTime(tokenId))) / (_endTime(tokenId) - _startTime(tokenId)); - } - - /** - * @dev See {ERC5725}. - */ - function _payoutToken(uint256 tokenId) internal view override returns (address) { - return address(vestDetails[tokenId].payoutToken); - } - - /** - * @dev See {ERC5725}. - */ - function _payout(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].payout; - } - - /** - * @dev See {ERC5725}. - */ - function _startTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].startTime; - } - - /** - * @dev See {ERC5725}. - */ - function _endTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].endTime; - } - - /** - * @dev Internal function to get the cliff time of a given linear vesting NFT - * - * @param tokenId to check - * @return uint256 the cliff time in seconds - */ - function _cliff(uint256 tokenId) internal view returns (uint256) { - return vestDetails[tokenId].cliff; - } -} diff --git a/assets/eip-5725/contracts/reference/VestingNFT.sol b/assets/eip-5725/contracts/reference/VestingNFT.sol deleted file mode 100644 index 2b62f934b9250e..00000000000000 --- a/assets/eip-5725/contracts/reference/VestingNFT.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "../ERC5725.sol"; - -contract VestingNFT is ERC5725 { - using SafeERC20 for IERC20; - - struct VestDetails { - IERC20 payoutToken; /// @dev payout token - uint256 payout; /// @dev payout token remaining to be paid - uint128 startTime; /// @dev when vesting starts - uint128 endTime; /// @dev when vesting end - } - mapping(uint256 => VestDetails) public vestDetails; /// @dev maps the vesting data with tokenIds - - /// @dev tracker of current NFT id - uint256 private _tokenIdTracker; - - /** - * @dev Initializes the contract by setting a `name` and a `symbol` to the token. - */ - constructor(string memory name, string memory symbol) ERC721(name, symbol) {} - - /** - * @notice Creates a new vesting NFT and mints it - * @dev Token amount should be approved to be transferred by this contract before executing create - * @param to The recipient of the NFT - * @param amount The total assets to be locked over time - * @param releaseTimestamp When the full amount of tokens get released - * @param token The ERC20 token to vest over time - */ - function create(address to, uint256 amount, uint128 releaseTimestamp, IERC20 token) public virtual { - require(to != address(0), "to cannot be address 0"); - require(releaseTimestamp > block.timestamp, "release must be in future"); - - uint256 newTokenId = _tokenIdTracker; - - vestDetails[newTokenId] = VestDetails({ - payoutToken: token, - payout: amount, - startTime: uint128(block.timestamp), - endTime: releaseTimestamp - }); - - _tokenIdTracker++; - _mint(to, newTokenId); - IERC20(payoutToken(newTokenId)).safeTransferFrom(msg.sender, address(this), amount); - } - - /** - * @dev See {IERC5725}. - */ - function vestedPayoutAtTime( - uint256 tokenId, - uint256 timestamp - ) public view override(ERC5725) validToken(tokenId) returns (uint256 payout) { - if (timestamp >= _endTime(tokenId)) { - return _payout(tokenId); - } - return 0; - } - - /** - * @dev See {ERC5725}. - */ - function _payoutToken(uint256 tokenId) internal view override returns (address) { - return address(vestDetails[tokenId].payoutToken); - } - - /** - * @dev See {ERC5725}. - */ - function _payout(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].payout; - } - - /** - * @dev See {ERC5725}. - */ - function _startTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].startTime; - } - - /** - * @dev See {ERC5725}. - */ - function _endTime(uint256 tokenId) internal view override returns (uint256) { - return vestDetails[tokenId].endTime; - } -} diff --git a/assets/eip-5725/test/LinearVestingNFT.test.ts b/assets/eip-5725/test/LinearVestingNFT.test.ts deleted file mode 100644 index 4eb55e03a69338..00000000000000 --- a/assets/eip-5725/test/LinearVestingNFT.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { ethers } from 'hardhat' -import { Signer } from 'ethers' -import { expect } from 'chai' -import { time } from '@nomicfoundation/hardhat-network-helpers' -// typechain -import { - ERC20Mock__factory, - ERC20Mock, - LinearVestingNFT__factory, - LinearVestingNFT, -} from '../typechain-types' - -const testValues = { - payout: '1000000000', - lockTime: 60, - buffer: 10, - totalLock: 70, -} - -describe('LinearVestingNFT', function () { - let accounts: Signer[] - let linearVestingNFT: LinearVestingNFT - let mockToken: ERC20Mock - let receiverAccount: string - let unlockTime: number - - beforeEach(async function () { - const LinearVestingNFT = (await ethers.getContractFactory( - 'LinearVestingNFT' - )) as LinearVestingNFT__factory - linearVestingNFT = await LinearVestingNFT.deploy('LinearVestingNFT', 'TLV') - await linearVestingNFT.deployed() - - const ERC20Mock = (await ethers.getContractFactory( - 'ERC20Mock' - )) as ERC20Mock__factory - mockToken = await ERC20Mock.deploy( - '1000000000000000000000', - 18, - 'LockedToken', - 'LOCK' - ) - await mockToken.deployed() - await mockToken.approve(linearVestingNFT.address, '1000000000000000000000') - - accounts = await ethers.getSigners() - receiverAccount = await accounts[1].getAddress() - unlockTime = await createVestingNft( - linearVestingNFT, - receiverAccount, - mockToken - ) - }) - - it('Returns a valid vested payout', async function () { - // TODO: More extensive testing of linear vesting functionality - const totalPayout = await linearVestingNFT.vestedPayoutAtTime(0, unlockTime) - expect(await linearVestingNFT.vestedPayout(0)).to.equal(0) - await time.increase(testValues.totalLock) - expect(await linearVestingNFT.vestedPayout(0)).to.equal(totalPayout) - }) - - it('Reverts when creating to account 0', async function () { - const latestBlock = await ethers.provider.getBlock('latest') - await expect( - linearVestingNFT.create( - '0x0000000000000000000000000000000000000000', - testValues.payout, - latestBlock.timestamp + testValues.buffer, - testValues.lockTime, - 0, - mockToken.address - ) - ).to.revertedWith('to cannot be address 0') - }) - - it('Reverts when creating to past start date 0', async function () { - await expect( - linearVestingNFT.create( - receiverAccount, - testValues.payout, - 0, - testValues.lockTime, - 0, - mockToken.address - ) - ).to.revertedWith('startTime cannot be on the past') - }) - - it('Reverts when duration is less than cliff', async function () { - const latestBlock = await ethers.provider.getBlock('latest') - await expect( - linearVestingNFT.create( - receiverAccount, - testValues.payout, - latestBlock.timestamp + testValues.buffer, - testValues.lockTime, - 100, - mockToken.address - ) - ).to.revertedWith('duration needs to be more than cliff') - }) -}) - -async function createVestingNft( - linearVestingNFT: LinearVestingNFT, - receiverAccount: string, - mockToken: ERC20Mock -) { - const latestBlock = await ethers.provider.getBlock('latest') - const unlockTime = - latestBlock.timestamp + testValues.lockTime + testValues.buffer - const txReceipt = await linearVestingNFT.create( - receiverAccount, - testValues.payout, - latestBlock.timestamp + testValues.buffer, - testValues.lockTime, - 0, - mockToken.address - ) - await txReceipt.wait() - return unlockTime -} diff --git a/assets/eip-5725/test/VestingNFT.test.ts b/assets/eip-5725/test/VestingNFT.test.ts deleted file mode 100644 index 05567a3eed393b..00000000000000 --- a/assets/eip-5725/test/VestingNFT.test.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { ethers } from 'hardhat' -import { BigNumber, Signer } from 'ethers' -import { expect } from 'chai' -import { time } from '@nomicfoundation/hardhat-network-helpers' -// typechain -import { ERC20Mock, VestingNFT } from '../typechain-types' -import { IERC5725_InterfaceId } from '../src/erc5725' - -const IERC721_InterfaceId = '0x80ac58cd' - -const testValues = { - payout: '1000000000', - payoutDecimals: 18, - lockTime: 60, -} - -describe('VestingNFT', function () { - let accounts: Signer[] - let vestingNFT: VestingNFT - let mockToken: ERC20Mock - let receiverAccount: string - let operatorAccount: string - let transferToAccount: string - let unlockTime: number - let invalidTokenID = 1337 - - beforeEach(async function () { - const VestingNFT = await ethers.getContractFactory('VestingNFT') - vestingNFT = await VestingNFT.deploy('VestingNFT', 'TLV') - await vestingNFT.deployed() - - const ERC20Mock = await ethers.getContractFactory('ERC20Mock') - mockToken = await ERC20Mock.deploy( - '1000000000000000000000', - testValues.payoutDecimals, - 'LockedToken', - 'LOCK' - ) - await mockToken.deployed() - await mockToken.approve(vestingNFT.address, '1000000000000000000000') - - accounts = await ethers.getSigners() - receiverAccount = await accounts[1].getAddress() - operatorAccount = await accounts[2].getAddress() - transferToAccount = await accounts[3].getAddress() - unlockTime = await createVestingNft( - vestingNFT, - receiverAccount, - mockToken, - 5 - ) - }) - - /** - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified - * // Solidity export interface id: - * bytes4 public constant IID_TEST = type(IERC5725).interfaceId; - * // Pull out the interfaceId in tests - * const interfaceId = await vestingNFT.IID_TEST(); - */ - it('Supports ERC721 and IERC5725 interfaces', async function () { - // IERC721 - expect(await vestingNFT.supportsInterface(IERC721_InterfaceId)).to.equal( - true - ) - // Vesting NFT Interface IERC5725 - expect(await vestingNFT.supportsInterface(IERC5725_InterfaceId)).to.equal( - true - ) - }) - - it('Returns a valid vested payout', async function () { - const totalPayout = await vestingNFT.vestedPayoutAtTime(0, unlockTime) - expect(await vestingNFT.vestedPayout(0)).to.equal(0) - await time.increase(testValues.lockTime) - expect(await vestingNFT.vestedPayout(0)).to.equal(totalPayout) - }) - - it('Reverts with invalid ID', async function () { - await expect(vestingNFT.vestedPayout(invalidTokenID)).to.revertedWith( - 'ERC5725: invalid token ID' - ) - await expect( - vestingNFT.vestedPayoutAtTime(invalidTokenID, unlockTime) - ).to.revertedWith('ERC5725: invalid token ID') - await expect(vestingNFT.vestingPayout(invalidTokenID)).to.revertedWith( - 'ERC5725: invalid token ID' - ) - await expect(vestingNFT.claimablePayout(invalidTokenID)).to.revertedWith( - 'ERC5725: invalid token ID' - ) - await expect(vestingNFT.vestingPeriod(invalidTokenID)).to.revertedWith( - 'ERC5725: invalid token ID' - ) - await expect(vestingNFT.payoutToken(invalidTokenID)).to.revertedWith( - 'ERC5725: invalid token ID' - ) - await expect(vestingNFT.claim(invalidTokenID)).to.revertedWith( - 'ERC5725: invalid token ID' - ) - }) - - it('Returns a valid pending payout', async function () { - expect(await vestingNFT.vestingPayout(0)).to.equal(testValues.payout) - }) - - it('Returns a valid releasable payout', async function () { - const totalPayout = await vestingNFT.vestedPayoutAtTime(0, unlockTime) - expect(await vestingNFT.claimablePayout(0)).to.equal(0) - await time.increase(testValues.lockTime) - expect(await vestingNFT.claimablePayout(0)).to.equal(totalPayout) - }) - - it('Returns a valid vesting period', async function () { - const vestingPeriod = await vestingNFT.vestingPeriod(0) - expect(vestingPeriod.vestingEnd).to.equal(unlockTime) - }) - - it('Returns a valid payout token', async function () { - expect(await vestingNFT.payoutToken(0)).to.equal(mockToken.address) - }) - - it('Is able to claim', async function () { - const connectedVestingNft = vestingNFT.connect(accounts[1]) - expect(await vestingNFT.claimedPayout(0)).to.equal(0) - await time.increase(testValues.lockTime) - const txReceipt = await connectedVestingNft.claim(0) - await txReceipt.wait() - expect(await mockToken.balanceOf(receiverAccount)).to.equal( - testValues.payout - ) - expect(await vestingNFT.claimedPayout(0)).to.equal(testValues.payout) - }) - - it('Reverts claim when payout is 0', async function () { - const connectedVestingNft = vestingNFT.connect(accounts[1]) - await expect(connectedVestingNft.claim(0)).to.revertedWith( - 'ERC5725: No pending payout' - ) - }) - - it('Reverts claim when payout is not from owner or account with permission', async function () { - const connectedVestingNft = vestingNFT.connect(accounts[2]) - await expect(connectedVestingNft.claim(0)).to.revertedWith( - 'ERC5725: not owner or operator' - ) - }) - - it('Reverts when creating to account 0', async function () { - await expect( - vestingNFT.create( - '0x0000000000000000000000000000000000000000', - testValues.payout, - unlockTime, - mockToken.address - ) - ).to.revertedWith('to cannot be address 0') - }) - - it('Revert when setting an setClaimApproval for a tokenId you do not own', async function () { - // Account without permission tries to call setClaimApproval(self,tokenId) - const connectedVestingNft = vestingNFT.connect(accounts[2]) - - await expect( - connectedVestingNft.setClaimApproval(operatorAccount, true, 1) - ).to.revertedWith('ERC5725: not owner of tokenId') - }) - - it("Should allow a designated operator to manage specific tokenId's owned by the owner through _tokenIdApprovals, until such rights are revoked", async function () { - // Give permission for SPECIFIC tokenId to be managed - const ownersConnectedVestingNft = vestingNFT.connect(accounts[1]) - const operatorsConnectedVestingNft = vestingNFT.connect(accounts[2]) - let approveToken1 = ownersConnectedVestingNft.setClaimApproval( - operatorAccount, - true, - 1 - ) - await expect(approveToken1).to.be.fulfilled - await expect(approveToken1) - .to.emit(ownersConnectedVestingNft, 'ClaimApproval') - .withArgs(receiverAccount, operatorAccount, 1, true) - - // Elapse time, operator can claim - await time.increase(testValues.lockTime) - await expect(operatorsConnectedVestingNft.claim(1)).to.be.fulfilled - - // Owner revokes permission for SPECIFIC tokenId - let unapprovedToken1 = ownersConnectedVestingNft.setClaimApproval( - operatorAccount, - false, - 1 - ) - await expect(unapprovedToken1).to.be.fulfilled - await expect(unapprovedToken1) - .to.emit(ownersConnectedVestingNft, 'ClaimApproval') - .withArgs(receiverAccount, operatorAccount, 1, false) - - // Elapse time, operator can't claim for that specific tokenId because no permissions - await time.increase(testValues.lockTime) - await expect(operatorsConnectedVestingNft.claim(0)).to.revertedWith( - 'ERC5725: not owner or operator' - ) - }) - - it("Should allow a designated operator to manage all tokenId's owned by the owner through _operatorApprovals, until such rights are revoked", async function () { - // Give permission for ALL tokenId to be managed - const ownersConnectedVestingNft = vestingNFT.connect(accounts[1]) - const operatorsConnectedVestingNft = vestingNFT.connect(accounts[2]) - let approveGlobalOperator = - ownersConnectedVestingNft.setClaimApprovalForAll(operatorAccount, true) - await expect(approveGlobalOperator).to.be.fulfilled - await expect(approveGlobalOperator) - .to.emit(ownersConnectedVestingNft, 'ClaimApprovalForAll') - .withArgs(receiverAccount, operatorAccount, true) - - // Elapse time, operator can claim - await time.increase(testValues.lockTime) - await expect(operatorsConnectedVestingNft.claim(1)).to.be.fulfilled - await expect(operatorsConnectedVestingNft.claim(2)).to.be.fulfilled - await expect(operatorsConnectedVestingNft.claim(3)).to.be.fulfilled - - // Owner revokes permission for SPECIFIC tokenId - let unapprovedGlobalOperator = - ownersConnectedVestingNft.setClaimApprovalForAll(operatorAccount, false) - await expect(unapprovedGlobalOperator).to.be.fulfilled - await expect(unapprovedGlobalOperator) - .to.emit(ownersConnectedVestingNft, 'ClaimApprovalForAll') - .withArgs(receiverAccount, operatorAccount, false) - - // Elapse time, operator can't claim for that specific tokenId because no permissions - await time.increase(testValues.lockTime) - await expect(operatorsConnectedVestingNft.claim(1)).to.revertedWith( - 'ERC5725: not owner or operator' - ) - await expect(operatorsConnectedVestingNft.claim(2)).to.revertedWith( - 'ERC5725: not owner or operator' - ) - await expect(operatorsConnectedVestingNft.claim(3)).to.revertedWith( - 'ERC5725: not owner or operator' - ) - }) - - it("Should revoke an operator's management rights from _tokenIdApprovals for a specific tokenId when the token is transferred", async function () { - // Give permission for SPECIFIC tokenId to be managed - const ownersConnectedVestingNft = vestingNFT.connect(accounts[1]) - const operatorsConnectedVestingNft = vestingNFT.connect(accounts[2]) - let approveToken1 = ownersConnectedVestingNft.setClaimApproval( - operatorAccount, - true, - 1 - ) - await expect(approveToken1).to.be.fulfilled - - // permissions added - expect(await ownersConnectedVestingNft.getClaimApproved(1)).to.equal( - operatorAccount - ) - - // Transfer tokenId 1 to other address which removes the _tokenIdApprovals permission but we keep the global OP status - const transferNft = ownersConnectedVestingNft.transferFrom( - receiverAccount, - transferToAccount, - 1 - ) - await expect(transferNft).to.be.fulfilled - - // permissions removed - expect(await ownersConnectedVestingNft.getClaimApproved(1)).to.equal( - '0x0000000000000000000000000000000000000000' - ) - - // Operator can't claim for tokenId 1 permissions removed - await expect(operatorsConnectedVestingNft.claim(1)).to.revertedWith( - 'ERC5725: not owner or operator' - ) - }) - - it("Should keep an operator's management rights to _operatorApprovals for all tokenIds when one or more are transfered", async function () { - // Give permission for ALL tokenId to be managed - const ownersConnectedVestingNft = vestingNFT.connect(accounts[1]) - const operatorsConnectedVestingNft = vestingNFT.connect(accounts[2]) - let approveGlobalOperator = - ownersConnectedVestingNft.setClaimApprovalForAll(operatorAccount, true) - await expect(approveGlobalOperator).to.be.fulfilled - - // permissions added - expect( - await ownersConnectedVestingNft.isClaimApprovedForAll( - receiverAccount, - operatorAccount - ) - ).to.equal(true) - - // Transfer tokenId 1 to other address which removes the _tokenIdApprovals permission but we keep the global OP status - const transferNft = ownersConnectedVestingNft.transferFrom( - receiverAccount, - transferToAccount, - 1 - ) - await expect(transferNft).to.be.fulfilled - - // permissions kept - expect( - await ownersConnectedVestingNft.isClaimApprovedForAll( - receiverAccount, - operatorAccount - ) - ).to.equal(true) - - // Operator can't claim for tokenId 1 they don't own it anymore - await expect(operatorsConnectedVestingNft.claim(1)).to.revertedWith( - 'ERC5725: not owner or operator' - ) - // Operator can claim for other tokenIds - await time.increase(testValues.lockTime) - await expect(operatorsConnectedVestingNft.claim(2)).to.be.fulfilled - await expect(operatorsConnectedVestingNft.claim(3)).to.be.fulfilled - }) -}) - -async function createVestingNft( - vestingNFT: VestingNFT, - receiverAccount: string, - mockToken: ERC20Mock, - batchMintAmount: number = 1 -) { - const latestBlock = await ethers.provider.getBlock('latest') - const unlockTime = latestBlock.timestamp + testValues.lockTime - - for (let i = 0; i <= batchMintAmount; i++) { - const txReceipt = await vestingNFT.create( - receiverAccount, - testValues.payout, - unlockTime, - mockToken.address - ) - await txReceipt.wait() - } - - return unlockTime -} diff --git a/assets/eip-5727/ERC5727.sol b/assets/eip-5727/ERC5727.sol deleted file mode 100644 index 7d075a04d035a3..00000000000000 --- a/assets/eip-5727/ERC5727.sol +++ /dev/null @@ -1,332 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -import "../ERC3525/ERC3525.sol"; -import "../ERC5192/interfaces/IERC5192.sol"; -import "./interfaces/IERC5727Metadata.sol"; -import "./interfaces/IERC5727Enumerable.sol"; - -contract ERC5727 is EIP712, Ownable, ERC3525, IERC5727Metadata { - using EnumerableSet for EnumerableSet.UintSet; - using EnumerableSet for EnumerableSet.AddressSet; - using ECDSA for bytes32; - - mapping(uint256 => address) internal _issuers; - mapping(uint256 => address) internal _verifiers; - mapping(uint256 => BurnAuth) internal _burnAuths; - mapping(uint256 => bool) internal _unlocked; - - mapping(uint256 => address) internal _slotVerifiers; - mapping(uint256 => BurnAuth) internal _slotBurnAuths; - - mapping(uint256 => mapping(address => bool)) internal _minterRole; - mapping(uint256 => mapping(address => bool)) internal _burnerRole; - - bytes32 private constant _TOKEN_TYPEHASH = - keccak256( - "Token(uint256 tokenId,address owner,uint256 value,uint256 slot,address issuer,address verifier,BurnAuth burnAuth)" - ); - - modifier onlyAdmin() { - if (owner() != _msgSender()) revert Unauthorized(_msgSender()); - _; - } - - modifier onlyMinter(uint256 slot) { - if (!_checkMintAuth(_msgSender(), slot)) - revert Unauthorized(_msgSender()); - _; - } - - modifier onlyBurner(uint256 tokenId) { - if (!_checkBurnAuth(_msgSender(), tokenId)) - revert Unauthorized(_msgSender()); - _; - } - - modifier onlyIssuer(uint256 tokenId) { - if (_msgSender() != _issuers[tokenId]) - revert Unauthorized(_msgSender()); - _; - } - - constructor( - string memory name_, - string memory symbol_, - address admin_, - string memory version_ - ) ERC3525(name_, symbol_, 18) EIP712(name_, version_) Ownable() { - transferOwnership(admin_); - } - - function verifierOf( - uint256 tokenId - ) public view virtual override returns (address) { - address verifier = _verifiers[tokenId]; - if (verifier == address(0)) revert NotFound(tokenId); - - return verifier; - } - - function issuerOf( - uint256 tokenId - ) public view virtual override returns (address) { - address issuer = _issuers[tokenId]; - if (issuer == address(0)) revert NotFound(tokenId); - - return issuer; - } - - function issue( - address to, - uint256 tokenId, - uint256 slot, - BurnAuth auth, - address verifier, - bytes calldata data - ) public payable virtual override onlyMinter(slot) { - if (tokenId == 0 || slot == 0 || to == address(0)) revert NullValue(); - - _issue(_msgSender(), to, tokenId, slot, auth, verifier); - - data; - } - - function issue( - uint256 tokenId, - uint256 amount, - bytes calldata data - ) public payable virtual override onlyIssuer(tokenId) { - _requireMinted(tokenId); - - _issue(_msgSender(), tokenId, amount); - - data; - } - - function _issue( - address from, - address to, - uint256 tokenId, - uint256 slot, - BurnAuth auth, - address verifier - ) internal virtual { - _mint(to, tokenId, slot); - - _issuers[tokenId] = from; - _burnAuths[tokenId] = auth; - _verifiers[tokenId] = verifier; - - if (auth == BurnAuth.IssuerOnly || auth == BurnAuth.Both) { - _burnerRole[tokenId][from] = true; - _approve(from, tokenId); - } - if (auth == BurnAuth.OwnerOnly || auth == BurnAuth.Both) { - _burnerRole[tokenId][to] = true; - } - - emit Issued(from, to, tokenId, auth); - emit Locked(tokenId); - - _beforeValueTransfer(address(0), to, 0, tokenId, slot, 0); - _afterValueTransfer(address(0), to, 0, tokenId, slot, 0); - } - - function _issue( - address from, - uint256 tokenId, - uint256 amount - ) internal virtual { - _mint(tokenId, amount); - - BurnAuth auth = _burnAuths[tokenId]; - - if (auth == BurnAuth.IssuerOnly || auth == BurnAuth.Both) { - _approve(tokenId, from, amount); - } - } - - function revoke( - uint256 tokenId, - bytes calldata data - ) public payable virtual override onlyBurner(tokenId) { - _requireMinted(tokenId); - - _revoke(_msgSender(), tokenId); - - data; - } - - function revoke( - uint256 tokenId, - uint256 amount, - bytes calldata data - ) public payable virtual override onlyBurner(tokenId) { - _requireMinted(tokenId); - - _revoke(_msgSender(), tokenId, amount); - - data; - } - - function locked( - uint256 tokenId - ) public view virtual override returns (bool) { - _requireMinted(tokenId); - - return !_unlocked[tokenId]; - } - - function burnAuth( - uint256 tokenId - ) public view virtual override returns (BurnAuth) { - _requireMinted(tokenId); - - return _burnAuths[tokenId]; - } - - function _checkBurnAuth( - address from, - uint256 tokenId - ) internal view virtual returns (bool) { - return _burnerRole[tokenId][from]; - } - - function _checkMintAuth( - address from, - uint256 slot - ) internal view virtual returns (bool) { - return owner() == from || _minterRole[slot][from]; - } - - function hasMintRole( - address from, - uint256 tokenId - ) external view virtual returns (bool) { - return _minterRole[tokenId][from]; - } - - function hasBurnRole( - address from, - uint256 tokenId - ) external view virtual returns (bool) { - return _burnerRole[tokenId][from]; - } - - function _burn(uint256 tokenId) internal virtual override { - super._burn(tokenId); - - delete _issuers[tokenId]; - delete _verifiers[tokenId]; - delete _burnAuths[tokenId]; - } - - function _revoke(address from, uint256 tokenId) internal virtual { - _burn(tokenId); - - emit Revoked(from, tokenId); - } - - function _revoke( - address from, - uint256 tokenId, - uint256 amount - ) internal virtual { - _burn(tokenId, amount); - - from; - } - - function verify( - uint256 tokenId, - bytes calldata data - ) public virtual override returns (bool result) { - _requireMinted(tokenId); - - // TODO: use actual verifier - result = _verify(_msgSender(), tokenId, data); - - data; - } - - function _verify( - address by, - uint256 tokenId, - bytes memory data - ) internal virtual returns (bool result) { - bytes memory signature = abi.decode(data, (bytes)); - bytes32 digest = _hashTypedDataV4( - keccak256( - abi.encode( - _TOKEN_TYPEHASH, - tokenId, - ownerOf(tokenId), - balanceOf(tokenId), - slotOf(tokenId), - issuerOf(tokenId), - verifierOf(tokenId), - burnAuth(tokenId) - ) - ) - ); - - address issuer = _issuers[tokenId]; - result = digest.recover(signature) == issuer; - - emit Verified(by, tokenId, result); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 firstTokenId, - uint256 batchSize - ) internal virtual override { - if (from != address(0) && to != address(0) && !_unlocked[firstTokenId]) - revert Soulbound(); - - super._beforeTokenTransfer(from, to, firstTokenId, batchSize); - } - - function _beforeValueTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 slot, - uint256 value - ) internal virtual override { - if (from != address(0) && to != address(0)) revert Soulbound(); - - super._beforeValueTransfer( - from, - to, - fromTokenId, - toTokenId, - slot, - value - ); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC3525) returns (bool) { - return - interfaceId == type(IERC5727).interfaceId || - interfaceId == type(IERC5727Metadata).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5727/ERC5727Claimable.sol b/assets/eip-5727/ERC5727Claimable.sol deleted file mode 100644 index 07510f64eac66c..00000000000000 --- a/assets/eip-5727/ERC5727Claimable.sol +++ /dev/null @@ -1,82 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; - -import "./ERC5727.sol"; -import "./interfaces/IERC5727Claimable.sol"; - -abstract contract ERC5727Claimable is IERC5727Claimable, ERC5727 { - using EnumerableSet for EnumerableSet.UintSet; - using MerkleProof for bytes32[]; - - // slot => merkelRoot - mapping(uint256 => bytes32) private _merkelRoots; - mapping(uint256 => address) private _slotIssuers; - - mapping(address => EnumerableSet.UintSet) private _claimed; - - function setClaimEvent( - address issuer, - uint256 slot, - bytes32 merkelRoot - ) external virtual onlyAdmin { - if ( - _slotIssuers[slot] != address(0) || _merkelRoots[slot] != bytes32(0) - ) revert Conflict(slot); - - _slotIssuers[slot] = issuer; - _merkelRoots[slot] = merkelRoot; - } - - function claim( - address to, - uint256 tokenId, - uint256 amount, - uint256 slot, - BurnAuth burnAuth, - address verifier, - bytes calldata data, - bytes32[] calldata proof - ) external virtual override { - if (to == address(0) || tokenId == 0 || slot == 0) revert NullValue(); - if (to != _msgSender()) revert Unauthorized(_msgSender()); - - bytes32 merkelRoot = _merkelRoots[slot]; - if (merkelRoot == bytes32(0)) revert NotClaimable(); - if (_claimed[to].contains(slot)) revert AlreadyClaimed(); - - bytes32 node = keccak256( - abi.encodePacked( - to, - tokenId, - amount, - slot, - burnAuth, - verifier, - data - ) - ); - if (!proof.verifyCalldata(merkelRoot, node)) revert Unauthorized(to); - - _issue(_slotIssuers[slot], to, tokenId, slot, burnAuth, verifier); - _issue(_slotIssuers[slot], tokenId, amount); - - _claimed[to].add(slot); - } - - function isClaimed( - address to, - uint256 slot - ) external view virtual returns (bool) { - return _claimed[to].contains(slot); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC5727) returns (bool) { - return - interfaceId == type(IERC5727Claimable).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5727/ERC5727Delegate.sol b/assets/eip-5727/ERC5727Delegate.sol deleted file mode 100644 index 91fbea375a5de2..00000000000000 --- a/assets/eip-5727/ERC5727Delegate.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./interfaces/IERC5727Delegate.sol"; -import "./ERC5727.sol"; - -abstract contract ERC5727Delegate is IERC5727Delegate, ERC5727 { - function delegate( - address operator, - uint256 slot - ) external virtual override onlyAdmin { - if (operator == address(0) || slot == 0) revert NullValue(); - if (isOperatorFor(operator, slot)) - revert RoleAlreadyGranted(operator, "minter"); - - _minterRole[slot][operator] = true; - emit Delegate(operator, slot); - } - - function undelegate( - address operator, - uint256 slot - ) external virtual override onlyAdmin { - if (operator == address(0) || slot == 0) revert NullValue(); - if (!isOperatorFor(operator, slot)) - revert RoleNotGranted(operator, "minter"); - - _minterRole[slot][operator] = false; - emit UnDelegate(operator, slot); - } - - function isOperatorFor( - address operator, - uint256 slot - ) public view virtual override returns (bool) { - if (operator == address(0) || slot == 0) revert NullValue(); - - return _minterRole[slot][operator]; - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC5727) returns (bool) { - return - interfaceId == type(IERC5727Delegate).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5727/ERC5727Enumerable.sol b/assets/eip-5727/ERC5727Enumerable.sol deleted file mode 100644 index 218b3e15cab638..00000000000000 --- a/assets/eip-5727/ERC5727Enumerable.sol +++ /dev/null @@ -1,152 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./ERC5727.sol"; -import "./interfaces/IERC5727Enumerable.sol"; -import "../ERC3525/ERC3525SlotEnumerable.sol"; - -abstract contract ERC5727Enumerable is - IERC5727Enumerable, - ERC5727, - ERC3525SlotEnumerable -{ - using EnumerableSet for EnumerableSet.UintSet; - using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableMap for EnumerableMap.AddressToUintMap; - - mapping(address => EnumerableSet.UintSet) private _slotsOfOwner; - - mapping(uint256 => EnumerableMap.AddressToUintMap) - private _ownerBalanceInSlot; - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC3525, ERC5727) returns (bool) { - return - interfaceId == type(IERC5727Enumerable).interfaceId || - super.supportsInterface(interfaceId); - } - - function slotCountOfOwner( - address owner - ) external view override returns (uint256) { - if (owner == address(0)) revert NullValue(); - - return _slotsOfOwner[owner].length(); - } - - function slotOfOwnerByIndex( - address owner, - uint256 index - ) external view override returns (uint256) { - if (owner == address(0)) revert NullValue(); - uint256 slotCountByOwner = _slotsOfOwner[owner].length(); - if (index >= slotCountByOwner) - revert IndexOutOfBounds(index, slotCountByOwner); - - return _slotsOfOwner[owner].at(index); - } - - function ownerBalanceInSlot( - address owner, - uint256 slot - ) external view returns (uint256) { - if (owner == address(0)) revert NullValue(); - if (!_slotExists(slot)) revert NotFound(slot); - - return _ownerBalanceInSlot[slot].get(owner); - } - - function _incrementOwnerBalanceInSlot( - address owner, - uint256 slot - ) internal virtual { - if (owner == address(0)) revert NullValue(); - - (, uint256 balanceInSlot) = _ownerBalanceInSlot[slot].tryGet(owner); - unchecked { - _ownerBalanceInSlot[slot].set(owner, balanceInSlot + 1); - } - } - - function _decrementOwnerBalanceInSlot( - address owner, - uint256 slot - ) internal virtual { - if (owner == address(0)) revert NullValue(); - - uint256 balanceInSlot = _ownerBalanceInSlot[slot].get(owner); - unchecked { - _ownerBalanceInSlot[slot].set(owner, balanceInSlot - 1); - } - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 firstTokenId, - uint256 batchSize - ) internal virtual override(ERC721Enumerable, ERC5727) { - super._beforeTokenTransfer(from, to, firstTokenId, batchSize); - } - - function _burn( - uint256 tokenId - ) internal virtual override(ERC3525, ERC5727) { - ERC5727._burn(tokenId); - } - - function _beforeValueTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 slot, - uint256 value - ) internal virtual override(ERC3525SlotEnumerable, ERC5727) { - super._beforeValueTransfer( - from, - to, - fromTokenId, - toTokenId, - slot, - value - ); - - if (from == address(0) && fromTokenId == 0) { - _incrementOwnerBalanceInSlot(to, slot); - - _slotsOfOwner[to].add(slot); - } - - value; - } - - function _afterValueTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 slot, - uint256 value - ) internal virtual override(ERC3525SlotEnumerable, ERC3525) { - super._afterValueTransfer( - from, - to, - fromTokenId, - toTokenId, - slot, - value - ); - - if (to == address(0) && toTokenId == 0) { - _decrementOwnerBalanceInSlot(from, slot); - - if (_ownerBalanceInSlot[slot].get(from) == 0) { - _slotsOfOwner[from].remove(slot); - } - } - - value; - } -} diff --git a/assets/eip-5727/ERC5727Expirable.sol b/assets/eip-5727/ERC5727Expirable.sol deleted file mode 100644 index 12925af15fd668..00000000000000 --- a/assets/eip-5727/ERC5727Expirable.sol +++ /dev/null @@ -1,92 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./ERC5727.sol"; -import "./interfaces/IERC5727Expirable.sol"; - -abstract contract ERC5727Expirable is IERC5727Expirable, ERC5727 { - mapping(uint256 => uint64) private _expiryDate; - mapping(uint256 => bool) private _isRenewable; - - mapping(uint256 => uint64) private _slotExpiryDate; - mapping(uint256 => bool) private _slotIsRenewable; - - modifier onlyManager(uint256 tokenId) { - if ( - _msgSender() != _issuers[tokenId] && - _msgSender() != ownerOf(tokenId) - ) revert Unauthorized(_msgSender()); - _; - } - - function setExpiration( - uint256 tokenId, - uint64 expiration, - bool renewable - ) public virtual override onlyIssuer(tokenId) { - if (!_exists(tokenId)) revert NotFound(tokenId); - if (expiration == 0) revert NullValue(); - if (_expiryDate[tokenId] > 0) revert Conflict(tokenId); - // solhint-disable-next-line not-rely-on-time - if (expiration < block.timestamp) revert PastDate(); - - _expiryDate[tokenId] = expiration; - _isRenewable[tokenId] = renewable; - - emit SubscriptionUpdate(tokenId, expiration); - } - - function renewSubscription( - uint256 tokenId, - uint64 duration - ) external payable virtual override onlyManager(tokenId) { - if (!_exists(tokenId)) revert NotFound(tokenId); - if (duration == 0) revert NullValue(); - if (!_isRenewable[tokenId]) revert NotRenewable(tokenId); - // solhint-disable-next-line not-rely-on-time - if (_expiryDate[tokenId] < block.timestamp) revert Expired(tokenId); - - unchecked { - _expiryDate[tokenId] += duration; - } - - emit SubscriptionUpdate(tokenId, _expiryDate[tokenId]); - } - - function cancelSubscription( - uint256 tokenId - ) external payable virtual override onlyManager(tokenId) { - if (!_exists(tokenId)) revert NotFound(tokenId); - if (!_isRenewable[tokenId]) revert NotRenewable(tokenId); - // solhint-disable-next-line not-rely-on-time - if (_expiryDate[tokenId] < block.timestamp) revert Expired(tokenId); - - delete _expiryDate[tokenId]; - delete _isRenewable[tokenId]; - - emit SubscriptionUpdate(tokenId, 0); - } - - function isRenewable( - uint256 tokenId - ) public view virtual override returns (bool) { - if (!_exists(tokenId)) revert NotFound(tokenId); - return _isRenewable[tokenId]; - } - - function expiresAt( - uint256 tokenId - ) public view virtual override returns (uint64) { - if (!_exists(tokenId)) revert NotFound(tokenId); - if (_expiryDate[tokenId] == 0) revert NoExpiration(tokenId); - return _expiryDate[tokenId]; - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC5727) returns (bool) { - return - interfaceId == type(IERC5727Expirable).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5727/ERC5727Governance.sol b/assets/eip-5727/ERC5727Governance.sol deleted file mode 100644 index 6fd4eadc880c74..00000000000000 --- a/assets/eip-5727/ERC5727Governance.sol +++ /dev/null @@ -1,209 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./ERC5727.sol"; -import "./interfaces/IERC5727Governance.sol"; - -abstract contract ERC5727Governance is IERC5727Governance, ERC5727 { - using EnumerableSet for EnumerableSet.AddressSet; - using EnumerableSet for EnumerableSet.UintSet; - using Counters for Counters.Counter; - using Strings for uint256; - - modifier onlyVoter() { - if (!isVoter(_msgSender())) revert MethodNotAllowed(_msgSender()); - _; - } - - struct IssueApproval { - address creator; - address to; - uint256 tokenId; - uint256 amount; - uint256 slot; - uint256 votersApproved; - uint256 votersRejected; - ApprovalStatus approvalStatus; - BurnAuth burnAuth; - address verifier; - } - - bytes32 public constant VOTER_ROLE = bytes32(uint256(0x04)); - - EnumerableSet.AddressSet private _voters; - - Counters.Counter private _approvalRequestCount; - mapping(uint256 => IssueApproval) private _approvals; - mapping(address => bool) private _voterRole; - - constructor( - string memory name_, - string memory symbol_, - address admin_, - string memory version_ - ) ERC5727(name_, symbol_, admin_, version_) { - _voters.add(admin_); - _voterRole[admin_] = true; - } - - function requestApproval( - address to, - uint256 tokenId, - uint256 amount, - uint256 slot, - BurnAuth burnAuth, - address verifier, - bytes calldata data - ) external virtual override onlyVoter { - if (to == address(0) || tokenId == 0 || slot == 0) revert NullValue(); - - uint256 approvalId = _approvalRequestCount.current(); - _approvals[approvalId] = IssueApproval( - _msgSender(), - to, - tokenId, - amount, - slot, - 0, - 0, - ApprovalStatus.Pending, - burnAuth, - verifier - ); - - _approvalRequestCount.increment(); - - emit ApprovalUpdate(approvalId, _msgSender(), ApprovalStatus.Pending); - - data; - } - - function removeApprovalRequest( - uint256 approvalId - ) external virtual override { - if (_approvals[approvalId].creator == address(0)) - revert NotFound(approvalId); - if (_msgSender() != _approvals[approvalId].creator) - revert Unauthorized(_msgSender()); - if (_approvals[approvalId].approvalStatus != ApprovalStatus.Pending) - revert Forbidden(); - - _approvals[approvalId].approvalStatus = ApprovalStatus.Removed; - - emit ApprovalUpdate(approvalId, address(0), ApprovalStatus.Removed); - } - - function addVoter(address newVoter) public virtual onlyAdmin { - if (newVoter == address(0)) revert NullValue(); - if (_voterRole[newVoter]) revert RoleAlreadyGranted(newVoter, "voter"); - - _voters.add(newVoter); - _voterRole[newVoter] = true; - } - - function removeVoter(address voter) public virtual onlyAdmin { - if (voter == address(0)) revert NullValue(); - if (!_voters.contains(voter)) revert RoleNotGranted(voter, "voter"); - - _voterRole[voter] = false; - _voters.remove(voter); - } - - function voterCount() public view virtual returns (uint256) { - return _voters.length(); - } - - function voterByIndex(uint256 index) public view virtual returns (address) { - if (index >= voterCount()) revert IndexOutOfBounds(index, voterCount()); - - return _voters.at(index); - } - - function isVoter(address voter) public view virtual returns (bool) { - return _voterRole[voter]; - } - - function voteApproval( - uint256 approvalId, - bool approve, - bytes calldata data - ) external virtual override onlyVoter { - IssueApproval storage approval = _approvals[approvalId]; - if (approval.creator == address(0)) revert NotFound(approvalId); - - ApprovalStatus approvalStatus = approval.approvalStatus; - if (approvalStatus != ApprovalStatus.Pending) revert Forbidden(); - - if (approve) { - approval.votersApproved++; - } else { - approval.votersRejected++; - } - - if (approval.votersApproved > voterCount() / 2) { - approval.approvalStatus = ApprovalStatus.Approved; - _issue( - _msgSender(), - approval.to, - approval.tokenId, - approval.slot, - approval.burnAuth, - approval.verifier - ); - _issue(_msgSender(), approval.tokenId, approval.amount); - - emit ApprovalUpdate( - approvalId, - _msgSender(), - ApprovalStatus.Approved - ); - } - if (approval.votersRejected > voterCount() / 2) { - approval.approvalStatus = ApprovalStatus.Rejected; - - emit ApprovalUpdate( - approvalId, - _msgSender(), - ApprovalStatus.Rejected - ); - } - - data; - } - - function getApproval( - uint256 approvalId - ) public view virtual returns (IssueApproval memory) { - if (_approvals[approvalId].creator == address(0)) - revert NotFound(approvalId); - - return _approvals[approvalId]; - } - - function approvalURI( - uint256 approvalId - ) public view virtual override returns (string memory) { - if (_approvals[approvalId].creator == address(0)) - revert NotFound(approvalId); - - string memory contractUri = contractURI(); - return - bytes(contractUri).length > 0 - ? string( - abi.encodePacked( - contractUri, - "/approvals/", - approvalId.toString() - ) - ) - : ""; - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC5727) returns (bool) { - return - interfaceId == type(IERC5727Governance).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5727/ERC5727Recovery.sol b/assets/eip-5727/ERC5727Recovery.sol deleted file mode 100644 index 3075df9a927076..00000000000000 --- a/assets/eip-5727/ERC5727Recovery.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -import "./ERC5727Enumerable.sol"; -import "./interfaces/IERC5727Recovery.sol"; - -abstract contract ERC5727Recovery is IERC5727Recovery, ERC5727Enumerable { - using ECDSA for bytes32; - - bytes32 private constant _RECOVERY_TYPEHASH = - keccak256("Recovery(address from,address recipient)"); - - function recover( - address from, - bytes memory signature - ) public virtual override { - if (from == address(0)) revert NullValue(); - address recipient = _msgSender(); - if (from == recipient) revert MethodNotAllowed(recipient); - - bytes32 digest = _hashTypedDataV4( - keccak256(abi.encode(_RECOVERY_TYPEHASH, from, recipient)) - ); - if (digest.recover(signature) != from) revert Forbidden(); - - uint256 balance = balanceOf(from); - for (uint256 i = 0; i < balance; ) { - uint256 tokenId = tokenOfOwnerByIndex(from, i); - - _unlocked[tokenId] = true; - _transfer(from, recipient, tokenId); - _unlocked[tokenId] = false; - - unchecked { - i++; - } - } - - emit Recovered(from, recipient); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC5727Enumerable) returns (bool) { - return - interfaceId == type(IERC5727Recovery).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-5727/interfaces/IERC5727.sol b/assets/eip-5727/interfaces/IERC5727.sol deleted file mode 100644 index ee0f901676c54d..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727.sol +++ /dev/null @@ -1,111 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "../../ERC3525/interfaces/IERC3525.sol"; -import "../../ERC5192/interfaces/IERC5192.sol"; -import "../../ERC5484/interfaces/IERC5484.sol"; -import "../../ERC4906/interfaces/IERC4906.sol"; - -/** - * @title ERC5727 Soulbound Token Interface - * @dev The core interface of the ERC5727 standard. - */ -interface IERC5727 is IERC3525, IERC5192, IERC5484, IERC4906 { - /** - * @dev MUST emit when a token is revoked. - * @param from The address of the owner - * @param tokenId The token id - */ - event Revoked(address indexed from, uint256 indexed tokenId); - - /** - * @dev MUST emit when a token is verified. - * @param by The address that initiated the verification - * @param tokenId The token id - * @param result The result of the verification - */ - event Verified(address indexed by, uint256 indexed tokenId, bool result); - - /** - * @notice Get the verifier of a token. - * @dev MUST revert if the `tokenId` does not exist - * @param tokenId the token for which to query the verifier - * @return The address of the verifier of `tokenId` - */ - function verifierOf(uint256 tokenId) external view returns (address); - - /** - * @notice Get the issuer of a token. - * @dev MUST revert if the `tokenId` does not exist - * @param tokenId the token for which to query the issuer - * @return The address of the issuer of `tokenId` - */ - function issuerOf(uint256 tokenId) external view returns (address); - - /** - * @notice Issue a token in a specified slot to an address. - * @dev MUST revert if the `to` address is the zero address. - * MUST revert if the `verifier` address is the zero address. - * @param to The address to issue the token to - * @param tokenId The token id - * @param slot The slot to issue the token in - * @param burnAuth The burn authorization of the token - * @param verifier The address of the verifier - * @param data Additional data used to issue the token - */ - function issue( - address to, - uint256 tokenId, - uint256 slot, - BurnAuth burnAuth, - address verifier, - bytes calldata data - ) external payable; - - /** - * @notice Issue credit to a token. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param amount The amount of the credit - * @param data The additional data used to issue the credit - */ - function issue( - uint256 tokenId, - uint256 amount, - bytes calldata data - ) external payable; - - /** - * @notice Revoke a token from an address. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param data The additional data used to revoke the token - */ - function revoke(uint256 tokenId, bytes calldata data) external payable; - - /** - * @notice Revoke credit from a token. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param amount The amount of the credit - * @param data The additional data used to revoke the credit - */ - function revoke( - uint256 tokenId, - uint256 amount, - bytes calldata data - ) external payable; - - /** - * @notice Verify if a token is valid. - * @dev MUST revert if the `tokenId` does not exist. - * @param tokenId The token id - * @param data The additional data used to verify the token - * @return A boolean indicating whether the token is successfully verified - */ - function verify( - uint256 tokenId, - bytes calldata data - ) external returns (bool); -} diff --git a/assets/eip-5727/interfaces/IERC5727Claimable.sol b/assets/eip-5727/interfaces/IERC5727Claimable.sol deleted file mode 100644 index 2cb29f35e5c15c..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727Claimable.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./IERC5727.sol"; - -interface IERC5727Claimable is IERC5727 { - /** - * @notice Emitted when a token is claimed by `to`. - * @param to The new owner of the token - * @param tokenId The token claimed - * @param amount The amount of the token claimed - */ - event Claimed(address indexed to, uint256 indexed tokenId, uint256 amount); - - /** - * @notice Claim the token with `tokenId` and `signature`. - * @dev MUST revert if the signature is invalid. - * @param to The new owner of the token - * @param tokenId The token claimed - * @param amount The amount of the token claimed - * @param slot The slot to claim the token in - * @param burnAuth The burn authorization of the token - * @param data The additional data used to claim the token - * @param proof The proof to claim the token - */ - function claim( - address to, - uint256 tokenId, - uint256 amount, - uint256 slot, - BurnAuth burnAuth, - address verifier, - bytes calldata data, - bytes32[] calldata proof - ) external; -} diff --git a/assets/eip-5727/interfaces/IERC5727Delegate.sol b/assets/eip-5727/interfaces/IERC5727Delegate.sol deleted file mode 100644 index dc8a8595e83c92..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727Delegate.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./IERC5727.sol"; - -/** - * @title ERC5727 Soulbound Token Delegate Interface - * @dev This extension allows delegation of issuing and revocation of tokens to an operator. - */ -interface IERC5727Delegate is IERC5727 { - /** - * @notice Emitted when a token issuance is delegated to an operator. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - event Delegate(address indexed operator, uint256 indexed slot); - - /** - * @notice Emitted when a token issuance is revoked from an operator. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - event UnDelegate(address indexed operator, uint256 indexed slot); - - /** - * @notice Delegate rights to `operator` for a slot. - * @dev MUST revert if the caller does not have the right to delegate. - * MUST revert if the `operator` address is the zero address. - * MUST revert if the `slot` is not a valid slot. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - function delegate(address operator, uint256 slot) external; - - /** - * @notice Revoke rights from `operator` for a slot. - * @dev MUST revert if the caller does not have the right to delegate. - * MUST revert if the `operator` address is the zero address. - * MUST revert if the `slot` is not a valid slot. - * @param operator The owner to which the issuing right is delegated - * @param slot The slot to issue the token in - */ - - function undelegate(address operator, uint256 slot) external; - - /** - * @notice Check if an operator has the permission to issue or revoke tokens in a slot. - * @param operator The operator to check - * @param slot The slot to check - */ - function isOperatorFor( - address operator, - uint256 slot - ) external view returns (bool); -} diff --git a/assets/eip-5727/interfaces/IERC5727Enumerable.sol b/assets/eip-5727/interfaces/IERC5727Enumerable.sol deleted file mode 100644 index 1f2f4cbe7f61a8..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727Enumerable.sol +++ /dev/null @@ -1,44 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; - -import "../../ERC3525/interfaces/IERC3525SlotEnumerable.sol"; -import "./IERC5727.sol"; - -/** - * @title ERC5727 Soulbound Token Enumerable Interface - * @dev This extension allows querying the tokens of a owner. - */ -interface IERC5727Enumerable is IERC3525SlotEnumerable, IERC5727 { - /** - * @notice Get the number of slots of a owner. - * @param owner The owner whose number of slots is queried for - * @return The number of slots of the `owner` - */ - function slotCountOfOwner(address owner) external view returns (uint256); - - /** - * @notice Get the slot with `index` of the `owner`. - * @dev MUST revert if the `index` exceed the number of slots of the `owner`. - * @param owner The owner whose slot is queried for. - * @param index The index of the slot queried for - * @return The slot is queried for - */ - function slotOfOwnerByIndex( - address owner, - uint256 index - ) external view returns (uint256); - - /** - * @notice Get the balance of a owner in a slot. - * @dev MUST revert if the slot does not exist. - * @param owner The owner whose balance is queried for - * @param slot The slot whose balance is queried for - * @return The balance of the `owner` in the `slot` - */ - function ownerBalanceInSlot( - address owner, - uint256 slot - ) external view returns (uint256); -} diff --git a/assets/eip-5727/interfaces/IERC5727Expirable.sol b/assets/eip-5727/interfaces/IERC5727Expirable.sol deleted file mode 100644 index 975d86e64d7d9d..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727Expirable.sol +++ /dev/null @@ -1,25 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./IERC5727.sol"; -import "../../ERC5643/interfaces/IERC5643.sol"; - -/** - * @title ERC5727 Soulbound Token Expirable Interface - * @dev This extension allows soulbound tokens to be expirable and renewable. - */ -interface IERC5727Expirable is IERC5727, IERC5643 { - /** - * @notice Set the expiry date of a token. - * @dev MUST revert if the `tokenId` token does not exist. - * MUST revert if the `date` is in the past. - * @param tokenId The token whose expiry date is set - * @param expiration The expire date to set - * @param isRenewable Whether the token is renewable - */ - function setExpiration( - uint256 tokenId, - uint64 expiration, - bool isRenewable - ) external; -} diff --git a/assets/eip-5727/interfaces/IERC5727Governance.sol b/assets/eip-5727/interfaces/IERC5727Governance.sol deleted file mode 100644 index 1520f4dd89ce58..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727Governance.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./IERC5727.sol"; - -/** - * @title ERC5727 Soulbound Token Governance Interface - * @dev This extension allows issuing of tokens by community voting. - */ -interface IERC5727Governance is IERC5727 { - enum ApprovalStatus { - Pending, - Approved, - Rejected, - Removed - } - - /** - * @notice Emitted when a token issuance approval is changed. - * @param approvalId The id of the approval - * @param creator The creator of the approval, zero address if the approval is removed - * @param status The status of the approval - */ - event ApprovalUpdate( - uint256 indexed approvalId, - address indexed creator, - ApprovalStatus status - ); - - /** - * @notice Emitted when a voter approves an approval. - * @param voter The voter who approves the approval - * @param approvalId The id of the approval - */ - event Approve( - address indexed voter, - uint256 indexed approvalId, - bool approve - ); - - /** - * @notice Create an approval of issuing a token. - * @dev MUST revert if the caller is not a voter. - * MUST revert if the `to` address is the zero address. - * @param to The owner which the token to mint to - * @param tokenId The id of the token to mint - * @param amount The amount of the token to mint - * @param slot The slot of the token to mint - * @param burnAuth The burn authorization of the token to mint - * @param data The additional data used to mint the token - */ - function requestApproval( - address to, - uint256 tokenId, - uint256 amount, - uint256 slot, - BurnAuth burnAuth, - address verifier, - bytes calldata data - ) external; - - /** - * @notice Remove `approvalId` approval request. - * @dev MUST revert if the caller is not the creator of the approval request. - * MUST revert if the approval request is already approved or rejected or non-existent. - * @param approvalId The approval to remove - */ - function removeApprovalRequest(uint256 approvalId) external; - - /** - * @notice Approve `approvalId` approval request. - * @dev MUST revert if the caller is not a voter. - * MUST revert if the approval request is already approved or rejected or non-existent. - * @param approvalId The approval to approve - * @param approve True if the approval is approved, false if the approval is rejected - * @param data The additional data used to approve the approval (e.g. the signature, voting power) - */ - function voteApproval( - uint256 approvalId, - bool approve, - bytes calldata data - ) external; - - /** - * @notice Get the URI of the approval. - * @dev MUST revert if the `approvalId` does not exist. - * @param approvalId The approval whose URI is queried for - * @return The URI of the approval - */ - function approvalURI( - uint256 approvalId - ) external view returns (string memory); -} diff --git a/assets/eip-5727/interfaces/IERC5727Metadata.sol b/assets/eip-5727/interfaces/IERC5727Metadata.sol deleted file mode 100644 index 02125923dd9d79..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727Metadata.sol +++ /dev/null @@ -1,13 +0,0 @@ -//SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "../../ERC3525/interfaces/IERC3525Metadata.sol"; -import "./IERC5727.sol"; - -/** - * @title ERC5727 Soulbound Token Metadata Interface - * @dev This extension allows querying the metadata of soulbound tokens. - */ -interface IERC5727Metadata is IERC3525Metadata, IERC5727 { - -} diff --git a/assets/eip-5727/interfaces/IERC5727Recovery.sol b/assets/eip-5727/interfaces/IERC5727Recovery.sol deleted file mode 100644 index f9c987b5716e23..00000000000000 --- a/assets/eip-5727/interfaces/IERC5727Recovery.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.0; - -import "./IERC5727.sol"; - -/** - * @title ERC5727 Soulbound Token Recovery Interface - * @dev This extension allows recovering soulbound tokens from an address provided its signature. - */ -interface IERC5727Recovery is IERC5727 { - /** - * @notice Emitted when the tokens of `owner` are recovered. - * @param from The owner whose tokens are recovered - * @param to The new owner of the tokens - */ - event Recovered(address indexed from, address indexed to); - - /** - * @notice Recover the tokens of `owner` with `signature`. - * @dev MUST revert if the signature is invalid. - * @param owner The owner whose tokens are recovered - * @param signature The signature signed by the `owner` - */ - function recover(address owner, bytes memory signature) external; -} diff --git a/assets/eip-5773/contracts/IERC5773.sol b/assets/eip-5773/contracts/IERC5773.sol deleted file mode 100644 index d864585d33d3ef..00000000000000 --- a/assets/eip-5773/contracts/IERC5773.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5773 { - event AssetSet(uint64 assetId); - - event AssetAddedToTokens( - uint256[] tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetAccepted( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetRejected(uint256 indexed tokenId, uint64 indexed assetId); - - event AssetPrioritySet(uint256 indexed tokenId); - - event ApprovalForAssets( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - event ApprovalForAllForAssets( - address indexed owner, - address indexed operator, - bool approved - ); - - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAllAssets(uint256 tokenId, uint256 maxRejections) external; - - function setPriority( - uint256 tokenId, - uint64[] calldata priorities - ) external; - - function getActiveAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getPendingAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getActiveAssetPriorities( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) external view returns (uint64); - - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) external view returns (string memory); - - function approveForAssets(address to, uint256 tokenId) external; - - function getApprovedForAssets( - uint256 tokenId - ) external view returns (address); - - function setApprovalForAllForAssets( - address operator, - bool approved - ) external; - - function isApprovedForAllForAssets( - address owner, - address operator - ) external view returns (bool); -} diff --git a/assets/eip-5773/contracts/MultiAssetToken.sol b/assets/eip-5773/contracts/MultiAssetToken.sol deleted file mode 100644 index 089fc834e35cba..00000000000000 --- a/assets/eip-5773/contracts/MultiAssetToken.sol +++ /dev/null @@ -1,684 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -import "./IERC5773.sol"; -import "./library/MultiAssetLib.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; - -contract MultiAssetToken is Context, IERC721, IERC5773 { - using MultiAssetLib for uint256; - using MultiAssetLib for uint64[]; - using MultiAssetLib for uint128[]; - using Address for address; - using Strings for uint256; - - // Token name - string private _name; - - // Token symbol - string private _symbol; - - // Mapping from token ID to owner address - mapping(uint256 => address) private _owners; - - // Mapping owner address to token count - mapping(address => uint256) private _balances; - - // Mapping from token ID to approved address - mapping(uint256 => address) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // Mapping from token ID to approved address for assets - mapping(uint256 => address) internal _tokenApprovalsForAssets; - - // Mapping from owner to operator approvals for assets - mapping(address => mapping(address => bool)) - internal _operatorApprovalsForAssets; - - //mapping of uint64 Ids to asset object - mapping(uint64 => string) internal _assets; - - //mapping of tokenId to new asset, to asset to be replaced - mapping(uint256 => mapping(uint64 => uint64)) private _assetReplacements; - - //mapping of tokenId to all assets - mapping(uint256 => uint64[]) internal _activeAssets; - - //mapping of tokenId to an array of asset priorities - mapping(uint256 => uint64[]) internal _activeAssetPriorities; - - //Double mapping of tokenId to active assets - mapping(uint256 => mapping(uint64 => bool)) private _tokenAssets; - - //mapping of tokenId to all assets by priority - mapping(uint256 => uint64[]) internal _pendingAssets; - - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - } - - //////////////////////////////////////// - // ERC-721 COMPLIANCE - //////////////////////////////////////// - - function supportsInterface(bytes4 interfaceId) public view returns (bool) { - return - interfaceId == type(IERC5773).interfaceId || - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC165).interfaceId; - } - - function balanceOf( - address owner - ) public view virtual override returns (uint256) { - require( - owner != address(0), - "ERC721: address zero is not a valid owner" - ); - return _balances[owner]; - } - - function ownerOf( - uint256 tokenId - ) public view virtual override returns (address) { - address owner = _owners[tokenId]; - require( - owner != address(0), - "ERC721: owner query for nonexistent token" - ); - return owner; - } - - function name() public view virtual returns (string memory) { - return _name; - } - - function symbol() public view virtual returns (string memory) { - return _symbol; - } - - function approve(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - require(to != owner, "MultiAsset: approval to current owner"); - require( - _msgSender() == owner || isApprovedForAll(owner, _msgSender()), - "MultiAsset: approve caller is not owner nor approved for all" - ); - - _approve(to, tokenId); - } - - function approveForAssets(address to, uint256 tokenId) external virtual { - address owner = ownerOf(tokenId); - require(to != owner, "MultiAsset: approval to current owner"); - require( - _msgSender() == owner || - isApprovedForAllForAssets(owner, _msgSender()), - "MultiAsset: approve caller is not owner nor approved for all" - ); - _approveForAssets(to, tokenId); - } - - function getApproved( - uint256 tokenId - ) public view virtual override returns (address) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - - return _tokenApprovals[tokenId]; - } - - function getApprovedForAssets( - uint256 tokenId - ) public view virtual returns (address) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - return _tokenApprovalsForAssets[tokenId]; - } - - function setApprovalForAll( - address operator, - bool approved - ) public virtual override { - _setApprovalForAll(_msgSender(), operator, approved); - } - - function isApprovedForAll( - address owner, - address operator - ) public view virtual override returns (bool) { - return _operatorApprovals[owner][operator]; - } - - function setApprovalForAllForAssets( - address operator, - bool approved - ) public virtual override { - _setApprovalForAllForAssets(_msgSender(), operator, approved); - } - - function isApprovedForAllForAssets( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovalsForAssets[owner][operator]; - } - - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - //solhint-disable-next-line max-line-length - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "MultiAsset: transfer caller is not owner nor approved" - ); - - _transfer(from, to, tokenId); - } - - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual override { - safeTransferFrom(from, to, tokenId, ""); - } - - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual override { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "MultiAsset: transfer caller is not owner nor approved" - ); - _safeTransfer(from, to, tokenId, data); - } - - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _transfer(from, to, tokenId); - require( - _checkOnERC721Received(from, to, tokenId, data), - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - } - - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _owners[tokenId] != address(0); - } - - function _isApprovedOrOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - address owner = ownerOf(tokenId); - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - function _isApprovedForAssetsOrOwner( - address user, - uint256 tokenId - ) internal view virtual returns (bool) { - require( - _exists(tokenId), - "MultiAsset: approved query for nonexistent token" - ); - address owner = ownerOf(tokenId); - return (user == owner || - isApprovedForAllForAssets(owner, user) || - getApprovedForAssets(tokenId) == user); - } - - function _safeMint(address to, uint256 tokenId) internal virtual { - _safeMint(to, tokenId, ""); - } - - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _mint(to, tokenId); - require( - _checkOnERC721Received(address(0), to, tokenId, data), - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - } - - function _mint(address to, uint256 tokenId) internal virtual { - require(to != address(0), "MultiAsset: mint to the zero address"); - require(!_exists(tokenId), "MultiAsset: token already minted"); - - _beforeTokenTransfer(address(0), to, tokenId); - - _balances[to] += 1; - _owners[tokenId] = to; - - emit Transfer(address(0), to, tokenId); - - _afterTokenTransfer(address(0), to, tokenId); - } - - function _burn(uint256 tokenId) internal virtual { - // WARNING: If you intend to allow the reminting of a burned token, you - // might want to clean the assets for the token, that is: - // _pendingAssets, _activeAssets, _assetReplacements - // _activeAssetPriorities and _tokenAssets. - address owner = ownerOf(tokenId); - - _beforeTokenTransfer(owner, address(0), tokenId); - - // Clear approvals - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - - _balances[owner] -= 1; - delete _owners[tokenId]; - - emit Transfer(owner, address(0), tokenId); - - _afterTokenTransfer(owner, address(0), tokenId); - } - - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual { - require( - ownerOf(tokenId) == from, - "MultiAsset: transfer from incorrect owner" - ); - require(to != address(0), "MultiAsset: transfer to the zero address"); - - _beforeTokenTransfer(from, to, tokenId); - - // Clear approvals from the previous owner - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - - _balances[from] -= 1; - _balances[to] += 1; - _owners[tokenId] = to; - - emit Transfer(from, to, tokenId); - - _afterTokenTransfer(from, to, tokenId); - } - - function _approve(address to, uint256 tokenId) internal virtual { - _tokenApprovals[tokenId] = to; - emit Approval(ownerOf(tokenId), to, tokenId); - } - - function _approveForAssets(address to, uint256 tokenId) internal virtual { - _tokenApprovalsForAssets[tokenId] = to; - emit ApprovalForAssets(ownerOf(tokenId), to, tokenId); - } - - function _setApprovalForAll( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "MultiAsset: approve to caller"); - _operatorApprovals[owner][operator] = approved; - emit ApprovalForAll(owner, operator, approved); - } - - function _setApprovalForAllForAssets( - address owner, - address operator, - bool approved - ) internal virtual { - require(owner != operator, "MultiAsset: approve to caller"); - _operatorApprovalsForAssets[owner][operator] = approved; - emit ApprovalForAllForAssets(owner, operator, approved); - } - - function _checkOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory data - ) private returns (bool) { - if (to.isContract()) { - try - IERC721Receiver(to).onERC721Received( - _msgSender(), - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert( - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - //////////////////////////////////////// - // ASSETS - //////////////////////////////////////// - - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external virtual { - require( - index < _pendingAssets[tokenId].length, - "MultiAsset: index out of bounds" - ); - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - require( - assetId == _pendingAssets[tokenId][index], - "MultiAsset: Unexpected asset" - ); - - _beforeAcceptAsset(tokenId, index, assetId); - uint64 replacesId = _assetReplacements[tokenId][assetId]; - uint256 replaceIndex; - bool replacefound; - if (replacesId != uint64(0)) - (replaceIndex, replacefound) = _activeAssets[tokenId].indexOf( - replacesId - ); - - if (replacefound) { - // We don't want to remove and then push a new asset. - // This way we also keep the priority of the original resource - _activeAssets[tokenId][replaceIndex] = assetId; - delete _tokenAssets[tokenId][replacesId]; - } else { - // We use the current size as next priority, by default priorities would be [0,1,2...] - _activeAssetPriorities[tokenId].push( - uint64(_activeAssets[tokenId].length) - ); - _activeAssets[tokenId].push(assetId); - replacesId = uint64(0); - } - _pendingAssets[tokenId].removeItemByIndex(index); - delete _assetReplacements[tokenId][assetId]; - - emit AssetAccepted(tokenId, assetId, replacesId); - _afterAcceptAsset(tokenId, index, assetId); - } - - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external virtual { - require( - index < _pendingAssets[tokenId].length, - "MultiAsset: index out of bounds" - ); - require( - _pendingAssets[tokenId].length > index, - "MultiAsset: Pending asset index out of range" - ); - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - - _beforeRejectAsset(tokenId, index, assetId); - _pendingAssets[tokenId].removeItemByIndex(index); - delete _tokenAssets[tokenId][assetId]; - delete _assetReplacements[tokenId][assetId]; - - emit AssetRejected(tokenId, assetId); - _afterRejectAsset(tokenId, index, assetId); - } - - function rejectAllAssets( - uint256 tokenId, - uint256 maxRejections - ) external virtual { - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - - uint256 len = _pendingAssets[tokenId].length; - if (len > maxRejections) revert("Unexpected number of assets"); - - _beforeRejectAllAssets(tokenId); - for (uint256 i; i < len; ) { - uint64 assetId = _pendingAssets[tokenId][i]; - delete _assetReplacements[tokenId][assetId]; - unchecked { - ++i; - } - } - delete (_pendingAssets[tokenId]); - - emit AssetRejected(tokenId, uint64(0)); - _afterRejectAllAssets(tokenId); - } - - function setPriority( - uint256 tokenId, - uint64[] memory priorities - ) external virtual { - uint256 length = priorities.length; - require( - length == _activeAssets[tokenId].length, - "MultiAsset: Bad priority list length" - ); - require( - _isApprovedForAssetsOrOwner(_msgSender(), tokenId), - "MultiAsset: not owner or approved" - ); - - _beforeSetPriority(tokenId, priorities); - _activeAssetPriorities[tokenId] = priorities; - - emit AssetPrioritySet(tokenId); - _afterSetPriority(tokenId, priorities); - } - - function getActiveAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _activeAssets[tokenId]; - } - - function getPendingAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _pendingAssets[tokenId]; - } - - function getActiveAssetPriorities( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _activeAssetPriorities[tokenId]; - } - - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) public view virtual returns (uint64) { - return _assetReplacements[tokenId][newAssetId]; - } - - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) public view virtual returns (string memory) { - if (!_tokenAssets[tokenId][assetId]) - revert("MultiAsset: Token does not have asset"); - return _assets[assetId]; - } - - function tokenURI( - uint256 tokenId - ) public view virtual returns (string memory) { - return ""; - } - - // To be implemented with custom guards - - function _addAssetEntry(uint64 id, string memory metadataURI) internal { - require(id != uint64(0), "RMRK: Write to zero"); - require(bytes(_assets[id]).length == 0, "RMRK: asset already exists"); - - _beforeAddAsset(id, metadataURI); - _assets[id] = metadataURI; - - emit AssetSet(id); - _afterAddAsset(id, metadataURI); - } - - function _addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal { - require( - !_tokenAssets[tokenId][assetId], - "MultiAsset: Asset already exists on token" - ); - - require( - bytes(_assets[assetId]).length != 0, - "MultiAsset: Asset not found in storage" - ); - - require( - _pendingAssets[tokenId].length < 128, - "MultiAsset: Max pending assets reached" - ); - - _beforeAddAssetToToken(tokenId, assetId, replacesAssetWithId); - _tokenAssets[tokenId][assetId] = true; - _pendingAssets[tokenId].push(assetId); - - if (replacesAssetWithId != uint64(0)) { - _assetReplacements[tokenId][assetId] = replacesAssetWithId; - } - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - emit AssetAddedToTokens(tokenIds, assetId, replacesAssetWithId); - _afterAddAssetToToken(tokenId, assetId, replacesAssetWithId); - } - - // HOOKS - - function _beforeAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - function _afterAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - function _beforeAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - function _afterAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - function _beforeAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _afterAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _beforeRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _afterRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - function _beforeRejectAllAssets(uint256 tokenId) internal virtual {} - - function _afterRejectAllAssets(uint256 tokenId) internal virtual {} - - function _beforeSetPriority( - uint256 tokenId, - uint64[] memory priorities - ) internal virtual {} - - function _afterSetPriority( - uint256 tokenId, - uint64[] memory priorities - ) internal virtual {} -} diff --git a/assets/eip-5773/contracts/library/MultiAssetLib.sol b/assets/eip-5773/contracts/library/MultiAssetLib.sol deleted file mode 100644 index 24a9cc0200f735..00000000000000 --- a/assets/eip-5773/contracts/library/MultiAssetLib.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -library MultiAssetLib { - function indexOf( - uint64[] memory A, - uint64 a - ) internal pure returns (uint256, bool) { - uint256 length = A.length; - for (uint256 i; i < length; ) { - if (A[i] == a) { - return (i, true); - } - unchecked { - ++i; - } - } - return (0, false); - } - - //For reasource storage array - function removeItemByIndex(uint64[] storage array, uint256 index) internal { - //Check to see if this is already gated by require in all calls - require(index < array.length); - array[index] = array[array.length - 1]; - array.pop(); - } -} diff --git a/assets/eip-5773/contracts/mocks/ERC721ReceiverMock.sol b/assets/eip-5773/contracts/mocks/ERC721ReceiverMock.sol deleted file mode 100644 index 1f0665b8c304ca..00000000000000 --- a/assets/eip-5773/contracts/mocks/ERC721ReceiverMock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract ERC721ReceiverMock { - bytes4 constant ERC721_RECEIVED = 0x150b7a02; - - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) public returns (bytes4) { - return ERC721_RECEIVED; - } -} diff --git a/assets/eip-5773/contracts/mocks/MultiAssetTokenMock.sol b/assets/eip-5773/contracts/mocks/MultiAssetTokenMock.sol deleted file mode 100644 index 0ceafe7dcca23e..00000000000000 --- a/assets/eip-5773/contracts/mocks/MultiAssetTokenMock.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -import "../MultiAssetToken.sol"; - -contract MultiAssetTokenMock is MultiAssetToken { - address private _issuer; - - constructor( - string memory name, - string memory symbol - ) MultiAssetToken(name, symbol) { - _setIssuer(_msgSender()); - } - - modifier onlyIssuer() { - require(_msgSender() == _issuer, "RMRK: Only issuer"); - _; - } - - function setIssuer(address issuer) external onlyIssuer { - _setIssuer(issuer); - } - - function getIssuer() external view returns (address) { - return _issuer; - } - - function mint(address to, uint256 tokenId) external onlyIssuer { - _mint(to, tokenId); - } - - function transfer(address to, uint256 tokenId) external { - _transfer(msg.sender, to, tokenId); - } - - function burn(uint256 tokenId) external { - _burn(tokenId); - } - - function addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 overwrites - ) external onlyIssuer { - _addAssetToToken(tokenId, assetId, overwrites); - } - - function addAssetEntry( - uint64 id, - string memory metadataURI - ) external onlyIssuer { - _addAssetEntry(id, metadataURI); - } - - function _setIssuer(address issuer) private { - _issuer = issuer; - } -} diff --git a/assets/eip-5773/contracts/mocks/NonReceiverMock.sol b/assets/eip-5773/contracts/mocks/NonReceiverMock.sol deleted file mode 100644 index e9d2d6ed3ecb2e..00000000000000 --- a/assets/eip-5773/contracts/mocks/NonReceiverMock.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract NonReceiverMock { - function dummy() external {} -} diff --git a/assets/eip-5773/contracts/utils/MultiAssetRenderUtils.sol b/assets/eip-5773/contracts/utils/MultiAssetRenderUtils.sol deleted file mode 100644 index 4a1ccbc51fecd9..00000000000000 --- a/assets/eip-5773/contracts/utils/MultiAssetRenderUtils.sol +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -import "../IERC5773.sol"; - -pragma solidity ^0.8.15; - -/** - * @dev Extra utility functions for composing RMRK assets. - */ - -contract MultiAssetRenderUtils { - uint64 private constant _LOWEST_POSSIBLE_PRIORITY = 2 ** 16 - 1; - - struct ActiveAsset { - uint64 id; - uint64 priority; - string metadata; - } - - struct PendingAsset { - uint64 id; - uint128 acceptRejectIndex; - uint64 overwritesAssetWithId; - string metadata; - } - - function getActiveAssets( - address target, - uint256 tokenId - ) public view virtual returns (ActiveAsset[] memory) { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint64[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert("Token has no assets"); - } - - ActiveAsset[] memory activeAssets = new ActiveAsset[](len); - string memory metadata; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - activeAssets[i] = ActiveAsset({ - id: assets[i], - priority: priorities[i], - metadata: metadata - }); - unchecked { - ++i; - } - } - return activeAssets; - } - - function getPendingAssets( - address target, - uint256 tokenId - ) public view virtual returns (PendingAsset[] memory) { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getPendingAssets(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert("Token has no assets"); - } - - PendingAsset[] memory pendingAssets = new PendingAsset[](len); - string memory metadata; - uint64 overwritesAssetWithId; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - overwritesAssetWithId = target_.getAssetReplacements( - tokenId, - assets[i] - ); - pendingAssets[i] = PendingAsset({ - id: assets[i], - acceptRejectIndex: uint128(i), - overwritesAssetWithId: overwritesAssetWithId, - metadata: metadata - }); - unchecked { - ++i; - } - } - return pendingAssets; - } - - /** - * @notice Returns asset metadata strings for the given ids - * - * Requirements: - * - * - `assetIds` must exist. - */ - function getAssetsById( - address target, - uint256 tokenId, - uint64[] calldata assetIds - ) public view virtual returns (string[] memory) { - IERC5773 target_ = IERC5773(target); - uint256 len = assetIds.length; - string[] memory assets = new string[](len); - for (uint256 i; i < len; ) { - assets[i] = target_.getAssetMetadata(tokenId, assetIds[i]); - unchecked { - ++i; - } - } - return assets; - } - - /** - * @notice Returns the asset metadata with the highest priority for the given token - */ - function getTopAssetMetaForToken( - address target, - uint256 tokenId - ) external view returns (string memory) { - IERC5773 target_ = IERC5773(target); - uint64[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint256 len = priorities.length; - if (len == 0) { - revert("Token has no assets"); - } - - uint64 maxPriority = _LOWEST_POSSIBLE_PRIORITY; - uint64 maxPriorityAsset; - for (uint64 i; i < len; ) { - uint64 currentPrio = priorities[i]; - if (currentPrio < maxPriority) { - maxPriority = currentPrio; - maxPriorityAsset = assets[i]; - } - unchecked { - ++i; - } - } - return target_.getAssetMetadata(tokenId, maxPriorityAsset); - } -} diff --git a/assets/eip-5773/hardhat.config.ts b/assets/eip-5773/hardhat.config.ts deleted file mode 100644 index 1c14d979c72879..00000000000000 --- a/assets/eip-5773/hardhat.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@nomiclabs/hardhat-etherscan"; -import "@typechain/hardhat"; -import "hardhat-contract-sizer"; -import "hardhat-gas-reporter"; -import "solidity-coverage"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.15", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-5773/package.json b/assets/eip-5773/package.json deleted file mode 100644 index e6f2346786f858..00000000000000 --- a/assets/eip-5773/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "eip-5773", - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/test-helpers": "^0.5.15", - "@primitivefi/hardhat-dodoc": "^0.2.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.3", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "chai": "^4.3.6", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.0", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "hardhat-contract-sizer": "^2.6.1", - "hardhat-gas-reporter": "^1.0.8", - "prettier": "2.7.1", - "prettier-plugin-solidity": "^1.0.0-beta.20", - "solc": "^0.8.9", - "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "walk-sync": "^3.0.0" - } -} diff --git a/assets/eip-5773/test/multiasset.ts b/assets/eip-5773/test/multiasset.ts deleted file mode 100644 index e623e4e090fe8d..00000000000000 --- a/assets/eip-5773/test/multiasset.ts +++ /dev/null @@ -1,761 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { - ERC721ReceiverMock, - MultiAssetReceiverMock, - MultiAssetTokenMock, - NonReceiverMock, - MultiAssetRenderUtils, -} from "../typechain-types"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { BigNumber } from "ethers"; - -describe("MultiAsset", async () => { - let token: MultiAssetTokenMock; - let renderUtils: MultiAssetRenderUtils; - let nonReceiver: NonReceiverMock; - let receiver721: ERC721ReceiverMock; - - let owner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - const name = "RmrkTest"; - const symbol = "RMRKTST"; - - const metaURIDefault = "metaURI"; - - beforeEach(async () => { - const [signersOwner, ...signersAddr] = await ethers.getSigners(); - owner = signersOwner; - addrs = signersAddr; - - const multiassetFactory = await ethers.getContractFactory( - "MultiAssetTokenMock" - ); - token = await multiassetFactory.deploy(name, symbol); - await token.deployed(); - - const renderFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - renderUtils = await renderFactory.deploy(); - await renderUtils.deployed(); - }); - - describe("Init", async function () { - it("Name", async function () { - expect(await token.name()).to.equal(name); - }); - - it("Symbol", async function () { - expect(await token.symbol()).to.equal(symbol); - }); - }); - - describe("ERC165 check", async function () { - it("can support IERC165", async function () { - expect(await token.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("can support IERC721", async function () { - expect(await token.supportsInterface("0x80ac58cd")).to.equal(true); - }); - - it("can support IERC5773", async function () { - expect(await token.supportsInterface("0x06b4329a")).to.equal(true); - }); - - it("cannot support other interfaceId", async function () { - expect(await token.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("Check OnReceived ERC721 and Multiasset", async function () { - it("Revert on transfer to non onERC721/onMultiasset implementer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const NonReceiver = await ethers.getContractFactory("NonReceiverMock"); - nonReceiver = await NonReceiver.deploy(); - await nonReceiver.deployed(); - - await expect( - token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - nonReceiver.address, - 1 - ) - ).to.be.revertedWith( - "MultiAsset: transfer to non ERC721 Receiver implementer" - ); - }); - - it("onERC721Received callback on transfer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const ERC721Receiver = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - receiver721 = await ERC721Receiver.deploy(); - await receiver721.deployed(); - - await token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - receiver721.address, - 1 - ); - expect(await token.ownerOf(1)).to.equal(receiver721.address); - }); - }); - - describe("Asset storage", async function () { - it("can add asset", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - }); - - it("cannot get non existing asset", async function () { - const tokenId = 1; - const resId = 10; - await token.mint(owner.address, tokenId); - await expect(token.getAssetMetadata(tokenId, resId)).to.be.revertedWith( - "MultiAsset: Token does not have asset" - ); - }); - - it("cannot add asset entry if not issuer", async function () { - const id = 10; - await expect( - token.connect(addrs[1]).addAssetEntry(id, metaURIDefault) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("can set and get issuer", async function () { - const newIssuerAddr = addrs[1].address; - expect(await token.getIssuer()).to.equal(owner.address); - - await token.setIssuer(newIssuerAddr); - expect(await token.getIssuer()).to.equal(newIssuerAddr); - }); - - it("cannot set issuer if not issuer", async function () { - const newIssuer = addrs[1]; - await expect( - token.connect(newIssuer).setIssuer(newIssuer.address) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("cannot overwrite asset", async function () { - const id = 10; - - await token.addAssetEntry(id, metaURIDefault); - await expect(token.addAssetEntry(id, metaURIDefault)).to.be.revertedWith( - "RMRK: asset already exists" - ); - }); - - it("cannot add asset with id 0", async function () { - const id = ethers.utils.hexZeroPad("0x0", 8); - - await expect(token.addAssetEntry(id, metaURIDefault)).to.be.revertedWith( - "RMRK: Write to zero" - ); - }); - - it("cannot add same asset twice", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - - await expect(token.addAssetEntry(id, metaURIDefault)).to.be.revertedWith( - "RMRK: asset already exists" - ); - }); - }); - - describe("Adding assets", async function () { - it("can add asset to token", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId2, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - - const pendingIds = await token.getPendingAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, pendingIds) - ).to.be.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot add non existing asset to token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.be.revertedWith( - "MultiAsset: Asset not found in storage" - ); - }); - - it("can add asset to non existing token and it is pending when minted", async function () { - const resId = 1; - const tokenId = 1; - await addAssets([resId]); - - await token.addAssetToToken(tokenId, resId, 0); - await token.mint(owner.address, tokenId); - expect(await token.getPendingAssets(tokenId)).to.eql([ - ethers.BigNumber.from(resId), - ]); - }); - - it("cannot add asset twice to the same token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.addAssetToToken(tokenId, ethers.BigNumber.from(resId), 0) - ).to.be.revertedWith("MultiAsset: Asset already exists on token"); - }); - - it("cannot add too many assets to the same token", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - for (let i = 1; i <= 128; i++) { - await addAssets([i]); - await token.addAssetToToken(tokenId, i, 0); - } - - // Now it's full, next should fail - const resId = 129; - await addAssets([resId]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.be.revertedWith( - "MultiAsset: Max pending assets reached" - ); - }); - - it("can add same asset to 2 different tokens", async function () { - const resId = 1; - const tokenId1 = 1; - const tokenId2 = 2; - - await token.mint(owner.address, tokenId1); - await token.mint(owner.address, tokenId2); - await addAssets([resId]); - await token.addAssetToToken(tokenId1, resId, 0); - await token.addAssetToToken(tokenId2, resId, 0); - }); - }); - - describe("Accepting assets", async function () { - it("can accept asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept multiple assets", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 1, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot accept asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - }); - - it("cannot accept asset if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.connect(addrs[1]).acceptAsset(tokenId, 0, resId) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - }); - - it("cannot accept non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect(token.acceptAsset(tokenId, 0, 1)).to.be.revertedWith( - "MultiAsset: index out of bounds" - ); - }); - }); - - describe("Overwriting assets", async function () { - it("can add asset to token overwritting an existing one", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Add new asset to overwrite the first, and accept - const activeAssets = await token.getActiveAssets(tokenId); - await expect(token.addAssetToToken(tokenId, resId2, activeAssets[0])) - .to.emit(token, "AssetAddedToTokens") - .withArgs([tokenId], resId2, resId); - const pendingAssets = await token.getPendingAssets(tokenId); - - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(activeAssets[0]); - await expect(token.acceptAsset(tokenId, 0, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - // Overwrite should be gone - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(ethers.BigNumber.from(0)); - }); - - it("can overwrite non existing asset to token, it could have been deleted", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken( - tokenId, - resId, - ethers.utils.hexZeroPad("0x1", 8) - ); - await token.acceptAsset(tokenId, 0, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - }); - - it("can overwrite an existing asset after 3 have been added and 1 accepted", async function () { - const resId = 1; - const resId2 = 2; - const resId3 = 3; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2, resId3]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId2, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId3, resId2)) - .to.emit(token, "AssetAddedToTokens") - .withArgs([tokenId], resId3, resId2); - - const pendingIds = await token.getPendingAssets(tokenId); - - expect( - await renderUtils.getAssetsById(token.address, tokenId, pendingIds) - ).to.be.eql([metaURIDefault, metaURIDefault, metaURIDefault]); - - await expect(token.acceptAsset(tokenId, 1, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, 0); - - await expect(token.acceptAsset(tokenId, 1, resId3)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId3, 2); - }); - }); - - describe("Rejecting assets", async function () { - it("can reject asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject all assets if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject asset and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject it - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAsset(tokenId, 0, resId2); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all assets and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject all - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAllAssets(tokenId, 1); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all pending assets at max capacity", async function () { - const tokenId = 1; - const resArr = []; - - for (let i = 1; i < 128; i++) { - resArr.push(i); - } - - await token.mint(owner.address, tokenId); - await addAssets(resArr); - - for (let i = 1; i < 128; i++) { - await token.addAssetToToken(tokenId, i, 1); - } - await token.rejectAllAssets(tokenId, 128); - - expect(await token.getAssetReplacements(1, 2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("cannot reject asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.rejectAsset(tokenId, 0, resId); - }); - - it("cannot reject asset nor reject all if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(addrs[1]).rejectAsset(tokenId, 0, resId) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - await expect( - token.connect(addrs[1]).rejectAllAssets(tokenId, 1) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - }); - - it("cannot reject non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect(token.rejectAsset(tokenId, 0, 1)).to.be.revertedWith( - "MultiAsset: index out of bounds" - ); - }); - }); - - describe("Priorities", async function () { - it("can set and get priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([BigNumber.from(0), BigNumber.from(1)]); - await expect(token.setPriority(tokenId, [2, 1])) - .to.emit(token, "AssetPrioritySet") - .withArgs(tokenId); - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([BigNumber.from(2), BigNumber.from(1)]); - }); - - it("cannot set priorities for non owned token", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [2, 1]) - ).to.be.revertedWith("MultiAsset: not owner or approved"); - }); - - it("cannot set different number of priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [1]) - ).to.be.revertedWith("MultiAsset: Bad priority list length"); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [2, 1, 3]) - ).to.be.revertedWith("MultiAsset: Bad priority list length"); - }); - - it("cannot set priorities for non existing token", async function () { - const tokenId = 1; - await expect( - token.connect(addrs[1]).setPriority(tokenId, []) - ).to.be.revertedWith("MultiAsset: approved query for nonexistent token"); - }); - }); - - describe("Approval Cleaning", async function () { - it("cleans token and assets approvals on transfer", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const newOwner = addrs[2]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner).transfer(newOwner.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql( - ethers.constants.AddressZero - ); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - ethers.constants.AddressZero - ); - }); - - it("cleans token and assets approvals on burn", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner).burn(tokenId); - - await expect(token.getApproved(tokenId)).to.be.revertedWith( - "MultiAsset: approved query for nonexistent token" - ); - await expect(token.getApprovedForAssets(tokenId)).to.be.revertedWith( - "MultiAsset: approved query for nonexistent token" - ); - }); - }); - - async function mintSampleToken(): Promise<{ - tokenOwner: SignerWithAddress; - tokenId: number; - }> { - const tokenOwner = owner; - const tokenId = 1; - await token.mint(tokenOwner.address, tokenId); - - return { tokenOwner, tokenId }; - } - - async function addAssets(ids: number[]): Promise { - ids.forEach(async (resId) => { - await token.addAssetEntry(resId, metaURIDefault); - }); - } - - async function addAssetsToToken(tokenId: number): Promise { - const resId = 1; - const resId2 = 2; - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await token.acceptAsset(tokenId, 0, resId); - await token.acceptAsset(tokenId, 0, resId2); - } - - async function checkAcceptFromAddress( - accepter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect(token.connect(accepter).acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - } - - async function checkRejectFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(rejecter).rejectAsset(tokenId, 0, resId) - ).to.emit(token, "AssetRejected"); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } - - async function checkRejectAllFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - const resId2 = 2; - - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - - await expect(token.connect(rejecter).rejectAllAssets(tokenId, 2)).to.emit( - token, - "AssetRejected" - ); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } -}); diff --git a/assets/eip-5773/test/renderUtils.ts b/assets/eip-5773/test/renderUtils.ts deleted file mode 100644 index 78bc785c2a74fe..00000000000000 --- a/assets/eip-5773/test/renderUtils.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { MultiAssetTokenMock, MultiAssetRenderUtils } from "../typechain-types"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -async function assetsFixture() { - const multiassetFactory = await ethers.getContractFactory( - "MultiAssetTokenMock" - ); - const renderUtilsFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - - const multiasset = await multiassetFactory.deploy("Chunky", "CHNK"); - await multiasset.deployed(); - - const renderUtils = await renderUtilsFactory.deploy(); - await renderUtils.deployed(); - - return { multiasset, renderUtils }; -} - -describe("Render Utils", async function () { - let owner: SignerWithAddress; - let multiasset: MultiAssetTokenMock; - let renderUtils: MultiAssetRenderUtils; - let tokenId: number; - - const resId = bn(1); - const resId2 = bn(2); - const resId3 = bn(3); - const resId4 = bn(4); - - before(async function () { - ({ multiasset, renderUtils } = await loadFixture(assetsFixture)); - - const signers = await ethers.getSigners(); - owner = signers[0]; - - tokenId = 1; - await multiasset.mint(owner.address, tokenId); - await multiasset.addAssetEntry(resId, "ipfs://res1.jpg"); - await multiasset.addAssetEntry(resId2, "ipfs://res2.jpg"); - await multiasset.addAssetEntry(resId3, "ipfs://res3.jpg"); - await multiasset.addAssetEntry(resId4, "ipfs://res4.jpg"); - await multiasset.addAssetToToken(tokenId, resId, 0); - await multiasset.addAssetToToken(tokenId, resId2, 0); - await multiasset.addAssetToToken(tokenId, resId3, resId); - await multiasset.addAssetToToken(tokenId, resId4, 0); - - await multiasset.acceptAsset(tokenId, 0, resId); - await multiasset.acceptAsset(tokenId, 1, resId2); - await multiasset.setPriority(tokenId, [10, 5]); - }); - - describe("Render Utils MultiAsset", async function () { - it("can get active assets", async function () { - expect( - await renderUtils.getActiveAssets(multiasset.address, tokenId) - ).to.eql([ - [resId, BigNumber.from(10), "ipfs://res1.jpg"], - [resId2, BigNumber.from(5), "ipfs://res2.jpg"], - ]); - }); - it("can get pending assets", async function () { - expect( - await renderUtils.getPendingAssets(multiasset.address, tokenId) - ).to.eql([ - [resId4, bn(0), bn(0), "ipfs://res4.jpg"], - [resId3, bn(1), resId, "ipfs://res3.jpg"], - ]); - }); - - it("can get top asset by priority", async function () { - expect( - await renderUtils.getTopAssetMetaForToken(multiasset.address, tokenId) - ).to.eql("ipfs://res2.jpg"); - }); - - it("cannot get top asset if token has no assets", async function () { - const otherTokenId = 2; - await multiasset.mint(owner.address, otherTokenId); - await expect( - renderUtils.getTopAssetMetaForToken(multiasset.address, otherTokenId) - ).to.be.revertedWith("Token has no assets"); - }); - }); -}); diff --git a/assets/eip-5827/ERC5827.sol b/assets/eip-5827/ERC5827.sol deleted file mode 100644 index 1d70d4804f3ed3..00000000000000 --- a/assets/eip-5827/ERC5827.sol +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.17; - -import "openzeppelin-contracts/token/ERC20/ERC20.sol"; -import "./IERC5827.sol"; - -contract ERC5827 is ERC20, IERC5827 { - struct RenewableAllowance { - uint256 amount; - uint192 recoveryRate; - uint64 lastUpdated; - } - - // owner => spender => renewableAllowance - mapping(address => mapping(address => RenewableAllowance)) - private rAllowance; - - constructor( - string memory name_, - string memory symbol_ - ) ERC20(name_, symbol_) {} - - function approve( - address _spender, - uint256 _value - ) public override(ERC20, IERC5827) returns (bool success) { - address owner = _msgSender(); - _approve(owner, _spender, _value, 0); - return true; - } - - function approveRenewable( - address _spender, - uint256 _value, - uint256 _recoveryRate - ) public override returns (bool success) { - address owner = _msgSender(); - _approve(owner, _spender, _value, _recoveryRate); - return true; - } - - function _approve( - address _owner, - address _spender, - uint256 _value, - uint256 _recoveryRate - ) internal virtual { - require( - _recoveryRate <= _value, - "recoveryRate must be less than or equal to value" - ); - - rAllowance[_owner][_spender] = RenewableAllowance({ - amount: _value, - recoveryRate: uint192(_recoveryRate), - lastUpdated: uint64(block.timestamp) - }); - - _approve(_owner, _spender, _value); - emit RenewableApproval(_owner, _spender, _value, _recoveryRate); - } - - /// @notice fetch amounts spendable by _spender - /// @return remaining allowance at the current point in time - function allowance( - address _owner, - address _spender - ) public view override(ERC20, IERC5827) returns (uint256 remaining) { - return _remainingAllowance(_owner, _spender); - } - - /// @dev returns the sum of two uint256 values, saturating at 2**256 - 1 - function saturatingAdd( - uint256 a, - uint256 b - ) internal pure returns (uint256) { - unchecked { - uint256 c = a + b; - if (c < a) return type(uint256).max; - return c; - } - } - - function _remainingAllowance( - address _owner, - address _spender - ) private view returns (uint256) { - RenewableAllowance memory a = rAllowance[_owner][_spender]; - uint256 remaining = super.allowance(_owner, _spender); - - uint256 recovered = uint256(a.recoveryRate) * - uint64(block.timestamp - a.lastUpdated); - uint256 remainingAllowance = saturatingAdd(remaining, recovered); - return remainingAllowance > a.amount ? a.amount : remainingAllowance; - } - - /// @notice fetch approved max amount and recovery rate - /// @return amount initial and maximum allowance given to spender - /// @return recoveryRate recovery amount per second - function renewableAllowance( - address _owner, - address _spender - ) public view returns (uint256 amount, uint256 recoveryRate) { - RenewableAllowance memory a = rAllowance[_owner][_spender]; - return (a.amount, uint256(a.recoveryRate)); - } - - /// @notice transfers base token with renewable allowance logic applied - /// @param from owner of base token - /// @param to recipient of base token - /// @param amount amount to transfer - function transferFrom( - address from, - address to, - uint256 amount - ) public override(ERC20, IERC5827) returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual override { - (uint256 maxAllowance, ) = renewableAllowance(owner, spender); - if (maxAllowance != type(uint256).max) { - uint256 currentAllowance = _remainingAllowance(owner, spender); - if (currentAllowance < amount) { - revert InsufficientRenewableAllowance({ - available: currentAllowance - }); - } - - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - rAllowance[owner][spender].lastUpdated = uint64(block.timestamp); - } - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return interfaceId == type(IERC5827).interfaceId; - } -} diff --git a/assets/eip-5827/IERC5827.sol b/assets/eip-5827/IERC5827.sol deleted file mode 100644 index 2f45588f13d464..00000000000000 --- a/assets/eip-5827/IERC5827.sol +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import "openzeppelin-contracts/interfaces/IERC20.sol"; -import "openzeppelin-contracts/interfaces/IERC165.sol"; - -/// @title Interface for IERC5827 contracts -/// @notice Please see https://eips.ethereum.org/EIPS/eip-5827 for more details on the goals of this interface -/// @author Zac (zlace0x), zhongfu (zhongfu), Edison (edison0xyz) -interface IERC5827 is IERC20, IERC165 { - /// Note: the ERC-165 identifier for this interface is 0x93cd7af6. - /// 0x93cd7af6 === - /// bytes4(keccak256('approveRenewable(address,uint256,uint256)')) ^ - /// bytes4(keccak256('renewableAllowance(address,address)')) ^ - /// bytes4(keccak256('approve(address,uint256)') ^ - /// bytes4(keccak256('transferFrom(address,address,uint256)') ^ - /// bytes4(keccak256('allowance(address,address)') ^ - - /// @dev Thrown when there available allowance is lesser than transfer amount - /// @param available Allowance available, 0 if unset - error InsufficientRenewableAllowance(uint256 available); - - /// @notice Emitted when a new renewable allowance is set. - /// @param _owner owner of token - /// @param _spender allowed spender of token - /// @param _value initial and maximum allowance given to spender - /// @param _recoveryRate recovery amount per second - event RenewableApproval( - address indexed _owner, - address indexed _spender, - uint256 _value, - uint256 _recoveryRate - ); - - /// @notice Grants an allowance of `_value` to `_spender` initially, which recovers over time based on `_recoveryRate` up to a limit of `_value`. - /// SHOULD throw when `_recoveryRate` is larger than `_value`. - /// MUST emit `RenewableApproval` event. - /// @param _spender allowed spender of token - /// @param _value initial and maximum allowance given to spender - /// @param _recoveryRate recovery amount per second - function approveRenewable( - address _spender, - uint256 _value, - uint256 _recoveryRate - ) external returns (bool success); - - /// @notice Returns approved max amount and recovery rate. - /// @return amount initial and maximum allowance given to spender - /// @return recoveryRate recovery amount per second - function renewableAllowance( - address _owner, - address _spender - ) external view returns (uint256 amount, uint256 recoveryRate); - - /// Overridden EIP-20 functions - - /// @notice Grants a (non-increasing) allowance of _value to _spender. - /// MUST clear set _recoveryRate to 0 on the corresponding renewable allowance, if any. - /// @param _spender allowed spender of token - /// @param _value allowance given to spender - function approve( - address _spender, - uint256 _value - ) external returns (bool success); - - /// @notice Moves `amount` tokens from `from` to `to` using the - /// allowance mechanism. `amount` is then deducted from the caller's - /// allowance factoring in recovery rate logic. - /// SHOULD throw when there is insufficient allowance - /// @param from token owner address - /// @param to token recipient - /// @param amount amount of token to transfer - /// @return success True if the function is successful, false if otherwise - function transferFrom( - address from, - address to, - uint256 amount - ) external returns (bool success); - - /// @notice Returns amounts spendable by `_spender`. - /// @param _owner Address of the owner - /// @param _spender spender of token - /// @return remaining allowance at the current point in time - function allowance( - address _owner, - address _spender - ) external view returns (uint256 remaining); -} diff --git a/assets/eip-5851/contracts/ERC5851Issuer.sol b/assets/eip-5851/contracts/ERC5851Issuer.sol deleted file mode 100644 index f6e91ded89560e..00000000000000 --- a/assets/eip-5851/contracts/ERC5851Issuer.sol +++ /dev/null @@ -1,46 +0,0 @@ - -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./interfaces/IERC5851.sol"; - - -abstract contract ERC5851Issuer is IERC5851{ - mapping(uint256 => IERC5851.Claim[]) private _claimMetadata; - mapping(address => mapping(uint256 => bool)) private _SBTVerified; - address public admin; - - constructor() { - admin = msg.sender; - - } - - function ifVerified(address claimmer, uint256 SBTID) public override view returns (bool){ - return(_SBTVerified[claimmer][SBTID]); - } - - function standardClaim(uint256 SBTID) public override view returns (Claim[] memory){ - return(_claimMetadata[SBTID]); - } - - function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) public override returns (bool){ - require(msg.sender == admin); - _claimMetadata[SBTID] = _claims; - emit StandardChanged(SBTID, _claims); - return(true); - } - - function certify(address claimer, uint256 SBTID) public override returns (bool){ - require(msg.sender == admin); - _SBTVerified[claimer][SBTID] = true; - emit Certified(claimer, SBTID); - return(true); - } - - function revoke(address claimer, uint256 SBTID) external override returns (bool){ - require(msg.sender == admin); - _SBTVerified[claimer][SBTID] = false; - emit Revoked(claimer, SBTID); - return(true); - } - -} diff --git a/assets/eip-5851/contracts/ERC5851Verifier.sol b/assets/eip-5851/contracts/ERC5851Verifier.sol deleted file mode 100644 index 75c663379e4a32..00000000000000 --- a/assets/eip-5851/contracts/ERC5851Verifier.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./interfaces/IERC5851.sol"; - -abstract contract ERC5851Verifier is IERC5851 { - address private _issuer; - - constructor(address issuer) { - _issuer = issuer; - } - - modifier KYCApproved(address claimer, uint256 SBTID) { - IERC5851(_issuer).ifVerified(claimer, SBTID); - _; - } - -} diff --git a/assets/eip-5851/contracts/interfaces/IERC5851.sol b/assets/eip-5851/contracts/interfaces/IERC5851.sol deleted file mode 100644 index a045e9cae718d5..00000000000000 --- a/assets/eip-5851/contracts/interfaces/IERC5851.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5851{ - - /** Metadata - * - * @param title defines the name of the claim field - * @param kind is the nature of the data (bool,string,address,bytes,..) - * @param description additional information about claim details. - */ - struct Metadata { - string title; - string kind; - string description; - } - - /** Values - * - * @dev Values here can be read and wrote by smartcontract and front-end, cited from [EIP-3475](./eip-3475.md) - */ - struct Values { - string stringValue; - uint uintValue; - address addressValue; - bool boolValue; - } - - /** Claim - * - * Claims structure consist of the conditions and value that holder claims to associate and verifier has to validate them. - * @notice the below given parameters are for reference purposes only, developers can optimize the fields that are needed to be represented on-chain by using schemes like TLV, encoding into base64 etc. - * @dev structure that DeFines the parameters for specific claims of the SBT certificate - * @notice this structure is used for the verification process, it contains the metadata, logic and expectation - * @logic given here MUST be one of ("⊄", "⊂", "<", "<=", "==", "!=", ">=",">") - */ - struct Claim { - Metadata metadata; - string logic; - Values expectation; - } - - //Verifier - /// @notice getter function to validate if the address `claimer` is the holder of the claim Defined by the tokenId `SBTID` - /// @dev it MUST be Defining the logic to fetch the result of the ZK verification (either from). - /// @dev logic given here MUST be one of ("⊄", "⊂", "<", "<=", "==", "!=", ">=", ">") - /// @param claimer is the EOA address that wants to validate the SBT issued to it by the KYC. - /// @param SBTID is the Id of the SBT that user is the claimer. - /// @return true if the assertion is valid, else false - /** - example ifVerified(0xfoo, 1) => true will mean that 0xfoo is the holder of the SBT identity token DeFined by tokenId of the given collection. - */ - function ifVerified(address claimer, uint256 SBTID) external view returns (bool); - - //Issuer - /// @notice getter function to fetch the on-chain identification logic for the given identity holder. - /// @dev it MUST not be defined for address(0). - /// @param SBTID is the Id of the SBT that the user is the claimer. - /// @return the struct array of all the descriptions of condition metadata that is defined by the administrator for the given KYC provider. - /** - ex: standardClaim(1) --> { - { "title":"age", - "kind": "uint", - "description": "age of the person based on the birth date on the legal document", - }, - "logic": ">=", - "value":"18" - } - Defines the condition encoded for the identity index 1, DeFining the identity condition that holder must be equal or more than 18 years old. - **/ - function standardClaim(uint256 SBTID) external view returns (Claim[] memory); - - /// @notice function for setting the claim requirement logic (defined by Claims metadata) details for the given identity token defined by SBTID. - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims. - /// @param `claims` is the struct array of all the descriptions of condition metadata that is defined by the administrator. check metadata section for more information. - /** - example: changeStandardClaim(1, { "title":"age", - "kind": "uint", - "description": "age of the person based on the birth date on the legal document", - }, - "logic": ">=", - "value":"18" - }); - will correspond to the functionality that admin needs to adjust the standard claim for the identification SBT with tokenId = 1, based on the conditions described in the Claims array struct details. - **/ - function changeStandardClaim(uint256 SBTID, Claim[] memory _claims) external returns (bool); - - /// @notice function which uses the ZKProof protocol to validate the identity based on the given - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which admin wants to define the Claims. - /// @param claimer is the address that needs to be proven as the owner of the SBT defined by the tokenID. - /** - example: certify(0xA....., 10) means that admin assigns the DID badge with id 10 to the address defined by the `0xA....` wallet. - */ - function certify(address claimer, uint256 SBTID) external returns (bool); - - /// @notice function which uses the ZKProof protocol to validate the identity based on the given - /// @dev it should only be called by the admin address. - /// @param SBTID is the Id of the SBT-based identity certificate for which the admin wants to define the Claims. - /// @param certifying is the address that needs to be proven as the owner of the SBT defined by the tokenID. - // eg: revoke(0xfoo,1): means that KYC admin revokes the SBT certificate number 1 for the address '0xfoo'. - function revoke(address certifying, uint256 SBTID) external returns (bool); - - -// Events - /** - * standardChanged - * @notice standardChanged MUST be triggered when claims are changed by the admin. - * @dev standardChanged MUST also be triggered for the creation of a new SBTID. - e.g : emit StandardChanged(1, Claims(Metadata('age', 'uint', 'age of the person based on the birth date on the legal document'), ">=", "18"); - is emitted when the Claim condition is changed which allows the certificate holder to call the functions with the modifier, claims that the holder must be equal or more than 18 years old. - */ - event StandardChanged(uint256 SBTID, Claim[] _claims); - - /** - * certified - * @notice certified MUST be triggered when the SBT certificate is given to the certifying address. - * eg: Certified(0xfoo,2); means that wallet holder address 0xfoo is certified to hold a certificate issued with id 2, and thus can satisfy all the conditions defined by the required interface. - */ - event Certified(address claimer, uint256 SBTID); - - /** - * revoked - * @notice revoked MUST be triggered when the SBT certificate is revoked. - * eg: Revoked( 0xfoo,1); means that entity user 0xfoo has been revoked to all the function access defined by the SBT ID 1. - */ - event Revoked(address claimer, uint256 SBTID); -} diff --git a/assets/eip-5851/contracts/test.sol b/assets/eip-5851/contracts/test.sol deleted file mode 100644 index a299dbb65685d8..00000000000000 --- a/assets/eip-5851/contracts/test.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -import "./ERC5851Verifier.sol"; - -abstract contract Token is ERC5851Verifier { - uint public test; - uint public SBTID; - function mint(address to, uint256 amount) public KYCApproved(to, SBTID){ - _mint(to, amount); - } - - function _mint(address account, uint256 amount) internal { - require(account != address(0), "ERC20: mint to the zero address"); - test = amount; - } - -} diff --git a/assets/eip-5851/script/offchainOperations.js b/assets/eip-5851/script/offchainOperations.js deleted file mode 100644 index e2636d32d830d6..00000000000000 --- a/assets/eip-5851/script/offchainOperations.js +++ /dev/null @@ -1,69 +0,0 @@ -// CC0 license. -// taken from (credits): https://soliditydeveloper.com/merkle-tree -const keccak256 = require("keccak256"); -const { MerkleTree } = require("merkletreejs"); - -const Web3 = require("web3"); - -const web3 = new Web3(); - - -/** - * generates the proof offchain using the PII information of the user and associated it with the other parameter details. - * - */ - -async function generateProof() { - -// consider the given information that is verified privately off-chain (storing with wallet, name, age, personal ID, jurisdiction) -// they will be considered as leaves for the application. -const personalIdentifiedInfo = ["0x00000a86986129038908a9808098-toto-18-99123456-France", "0x00000a86986129038908a9808098-john-20-1276546-England"].map(x => keccak256(x)); -const tree = new MerkleTree(leaves,keccak256); - -} - - -/** - * this checks the ownership of the information from requirement (stored onchain) and then verify whether the keccak256 representation is a member of the given proof. - * we follow the checkProof - */ -async function verifyRequirement(verifyingAddress,leafNodes) { -const buf2hex = x => '0x'+x.toString('hex') -const leaf = keccak256('0x00000a86986129038908a9808098-toto-18-99123456-France') - -const leafInfo = buf2hex(leaf); - -const hexproof = tree.getProof(leaf).map(x => buf2hex(x.data)); - -const positions = tree.getProof(leaf).map(x => x.position === 'right' ? 1 : 0) - - - -//TODO: fetch the -const SBTCertification = await web3.eth.Contract(); - -const verifiedOnchain = await SBTCertification.ifVerified(verifyingAddress, leafNodes); - -assert.equal(MerkleTree.verify(proof,leaf,root), verifiedOnchain); - -} - - - -const root = tree.getRoot(); - - -// this is the root generated by claim verifier, by doing the operations offchain. -const hexroot = buf2hex(root); - -const merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true }); - -console.log("---------"); -console.log("Merke Tree"); -console.log("---------"); -console.log(merkleTree.toString()); -console.log("---------"); -console.log("Merkle Root: " + merkleTree.getHexRoot()); - -console.log("Proof 1: " + merkleTree.getHexProof(leafNodes[0])); -console.log("Proof 2: " + merkleTree.getHexProof(leafNodes[1])); diff --git a/assets/eip-6059/contracts/IERC6059.sol b/assets/eip-6059/contracts/IERC6059.sol deleted file mode 100644 index 49e64548096ff2..00000000000000 --- a/assets/eip-6059/contracts/IERC6059.sol +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - - -interface IERC6059 { - struct DirectOwner { - uint256 tokenId; - address ownerAddress; - } - - event NestTransfer( - address indexed from, - address indexed to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 indexed tokenId - ); - - event ChildProposed( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event ChildAccepted( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event AllChildrenRejected(uint256 indexed tokenId); - - event ChildTransferred( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId, - bool fromPending - ); - - struct Child { - uint256 tokenId; - address contractAddress; - } - - function ownerOf(uint256 tokenId) external view returns (address owner); - - function directOwnerOf( - uint256 tokenId - ) external view returns (address, uint256, bool); - - function burn( - uint256 tokenId, - uint256 maxRecursiveBurns - ) external returns (uint256); - - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) external; - - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) external; - - function rejectAllChildren(uint256 parentId, uint256 maxRejections) - external; - - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) external; - - function childrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function pendingChildrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function childOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function pendingChildOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) external; -} diff --git a/assets/eip-6059/contracts/NestableToken.sol b/assets/eip-6059/contracts/NestableToken.sol deleted file mode 100644 index b29f81a94061de..00000000000000 --- a/assets/eip-6059/contracts/NestableToken.sol +++ /dev/null @@ -1,1462 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//Generally all interactions should propagate downstream - -pragma solidity ^0.8.16; - -import "./IERC6059.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -error ChildAlreadyExists(); -error ChildIndexOutOfRange(); -error ERC721AddressZeroIsNotaValidOwner(); -error ERC721ApprovalToCurrentOwner(); -error ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); -error ERC721ApproveToCaller(); -error ERC721InvalidTokenId(); -error ERC721MintToTheZeroAddress(); -error ERC721NotApprovedOrOwner(); -error ERC721TokenAlreadyMinted(); -error ERC721TransferFromIncorrectOwner(); -error ERC721TransferToNonReceiverImplementer(); -error ERC721TransferToTheZeroAddress(); -error IdZeroForbidden(); -error IsNotContract(); -error MaxPendingChildrenReached(); -error MaxRecursiveBurnsReached(address childContract, uint256 childId); -error MintToNonNestableImplementer(); -error NestableTooDeep(); -error NestableTransferToDescendant(); -error NestableTransferToNonNestableImplementer(); -error NestableTransferToSelf(); -error NotApprovedOrDirectOwner(); -error PendingChildIndexOutOfRange(); -error UnexpectedChildId(); -error UnexpectedNumberOfChildren(); - -/** - * @title NestableToken - * @author RMRK team - * @notice Smart contract of the Nestable module. - * @dev This contract is hierarchy agnostic and can support an arbitrary number of nested levels up and down, as long as - * gas limits allow it. - */ -contract NestableToken is Context, IERC165, IERC721, IERC6059 { - using Address for address; - - uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100; - - // Mapping owner address to token count - mapping(address => uint256) private _balances; - - // Mapping from token ID to approver address to approved address - // The approver is necessary so approvals are invalidated for nested children on transfer - // WARNING: If a child NFT returns to a previous root owner, old permissions would be active again - mapping(uint256 => mapping(address => address)) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // ------------------- NESTABLE -------------- - - // Mapping from token ID to DirectOwner struct - mapping(uint256 => DirectOwner) private _directOwners; - - // Mapping of tokenId to array of active children structs - mapping(uint256 => Child[]) private _activeChildren; - - // Mapping of tokenId to array of pending children structs - mapping(uint256 => Child[]) private _pendingChildren; - - // Mapping of child token address to child token ID to whether they are pending or active on any token - // We might have a first extra mapping from token ID, but since the same child cannot be nested into multiple tokens - // we can strip it for size/gas savings. - mapping(address => mapping(uint256 => uint256)) internal _childIsInActive; - - // -------------------------- MODIFIERS ---------------------------- - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @dev If the caller is not the owner of the token or approved to manage it by its owner, the execution will be - * reverted. - * @param tokenId ID of the token to check - */ - function _onlyApprovedOrOwner(uint256 tokenId) private view { - if (!_isApprovedOrOwner(_msgSender(), tokenId)) - revert ERC721NotApprovedOrOwner(); - } - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrOwner(uint256 tokenId) { - _onlyApprovedOrOwner(tokenId); - _; - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or it its direct owner. - * @dev This does not delegate to ownerOf, which returns the root owner, but rater uses an owner from DirectOwner - * struct. - * @dev The execution is reverted if the caller is not immediate owner or approved to manage the given token. - * @dev Used for parent-scoped transfers. - * @param tokenId ID of the token to check. - */ - function _onlyApprovedOrDirectOwner(uint256 tokenId) private view { - if (!_isApprovedOrDirectOwner(_msgSender(), tokenId)) - revert NotApprovedOrDirectOwner(); - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or is its direct owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrDirectOwner(uint256 tokenId) { - _onlyApprovedOrDirectOwner(tokenId); - _; - } - - // ------------------------------- ERC721 --------------------------------- - /** - * @inheritdoc IERC165 - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC165).interfaceId || - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC721Metadata).interfaceId || - interfaceId == type(IERC6059).interfaceId; - } - - /** - * @inheritdoc IERC721 - */ - function balanceOf(address owner) public view virtual returns (uint256) { - if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner(); - return _balances[owner]; - } - - //////////////////////////////////////// - // TRANSFERS - //////////////////////////////////////// - - /** - * @inheritdoc IERC721 - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _transfer(from, to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual { - safeTransferFrom(from, to, tokenId, ""); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _safeTransfer(from, to, tokenId, data); - } - - /** - * @notice Used to transfer the token into another token. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @param from Address of the direct owner of the token to be transferred - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token being transferred - * @param destinationId ID of the token to receive the token being transferred - */ - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _nestTransfer(from, to, tokenId, destinationId, data); - } - - /** - * @notice Used to safely transfer the token form `from` to `to`. - * @dev The function checks that contract recipients are aware of the ERC721 protocol to prevent tokens from being - * forever locked. - * @dev This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. implement alternative - * mechanisms to perform token transfer, such as signature-based. - * @dev Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - * @param data Additional data with no specified format, sent in call to `to` - */ - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _transfer(from, to, tokenId); - if (!_checkOnERC721Received(from, to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to transfer the token from `from` to `to`. - * @dev As opposed to {transferFrom}, this imposes no restrictions on msg.sender. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - */ - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, 0, to); - _balances[to] += 1; - - emit Transfer(from, to, tokenId); - emit NestTransfer(immediateOwner, to, parentId, 0, tokenId); - - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - } - - /** - * @notice Used to transfer a token into another token. - * @dev Attempting to nest a token into `0x0` address will result in reverted transaction. - * @dev Attempting to nest a token into itself will result in reverted transaction. - * @param from Address of the account currently owning the given token - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token to transfer - * @param destinationId ID of the token receiving the given token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestTransfer( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - if (to == address(this) && tokenId == destinationId) - revert NestableTransferToSelf(); - - // Destination contract checks: - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert NestableTransferToNonNestableImplementer(); - _checkForInheritanceLoop(tokenId, to, destinationId); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - to, - parentId, - destinationId, - tokenId - ); - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, destinationId, to); - _balances[to] += 1; - - // Sending to NFT: - _sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data); - } - - /** - * @notice Used to send a token to another token. - * @dev If the token being sent is currently owned by an externally owned account, the `parentId` should equal `0`. - * @dev Emits {Transfer} event. - * @dev Emits {NestTransfer} event. - * @param from Address from which the token is being sent - * @param to Address of the collection smart contract of the token to receive the given token - * @param parentId ID of the current parent token of the token being sent - * @param destinationId ID of the tokento receive the token being sent - * @param tokenId ID of the token being sent - * @param data Additional data with no specified format, sent in the addChild call - */ - function _sendToNFT( - address from, - address to, - uint256 parentId, - uint256 destinationId, - uint256 tokenId, - bytes memory data - ) private { - IERC6059 destContract = IERC6059(to); - destContract.addChild(destinationId, tokenId, data); - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(from, to, parentId, destinationId, tokenId); - - emit Transfer(from, to, tokenId); - emit NestTransfer(from, to, parentId, destinationId, tokenId); - } - - /** - * @notice Used to check if nesting a given token into a specified token would create an inheritance loop. - * @dev If a loop would occur, the tokens would be unmanageable, so the execution is reverted if one is detected. - * @dev The check for inheritance loop is bounded to guard against too much gas being consumed. - * @param currentId ID of the token that would be nested - * @param targetContract Address of the collection smart contract of the token into which the given token would be - * nested - * @param targetId ID of the token into which the given token would be nested - */ - function _checkForInheritanceLoop( - uint256 currentId, - address targetContract, - uint256 targetId - ) private view { - for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) { - ( - address nextOwner, - uint256 nextOwnerTokenId, - bool isNft - ) = IERC6059(targetContract).directOwnerOf(targetId); - // If there's a final address, we're good. There's no loop. - if (!isNft) { - return; - } - // Ff the current nft is an ancestor at some point, there is an inheritance loop - if (nextOwner == address(this) && nextOwnerTokenId == currentId) { - revert NestableTransferToDescendant(); - } - // We reuse the parameters to save some contract size - targetContract = nextOwner; - targetId = nextOwnerTokenId; - unchecked { - ++i; - } - } - revert NestableTooDeep(); - } - - //////////////////////////////////////// - // MINTING - //////////////////////////////////////// - - /** - * @notice Used to safely mint a token to a specified address. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param to Address to which to safely mint the gven token - * @param tokenId ID of the token to mint to the specified address - */ - function _safeMint(address to, uint256 tokenId) internal virtual { - _safeMint(to, tokenId, ""); - } - - /** - * @notice Used to safely mint the token to the specified address while passing the additional data to contract - * recipients. - * @param to Address to which to mint the token - * @param tokenId ID of the token to mint - * @param data Additional data to send with the tokens - */ - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _mint(to, tokenId); - if (!_checkOnERC721Received(address(0), to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to mint a specified token to a given address. - * @dev WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - `to` cannot be the zero address. - * @dev Emits a {Transfer} event. - * @param to Address to mint the token to - * @param tokenId ID of the token to mint - */ - function _mint(address to, uint256 tokenId) internal virtual { - _innerMint(to, tokenId, 0); - - emit Transfer(address(0), to, tokenId); - emit NestTransfer(address(0), to, 0, 0, tokenId); - - _afterTokenTransfer(address(0), to, tokenId); - _afterNestedTokenTransfer(address(0), to, 0, 0, tokenId); - } - - /** - * @notice Used to mint a child token to a given parent token. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new child token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestMint( - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert MintToNonNestableImplementer(); - - _innerMint(to, tokenId, destinationId); - _sendToNFT(address(0), to, 0, destinationId, tokenId, data); - } - - /** - * @notice Used to mint a child token into a given parent token. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` must not exist. - * - `tokenId` must not be `0`. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new token - */ - function _innerMint( - address to, - uint256 tokenId, - uint256 destinationId - ) private { - if (to == address(0)) revert ERC721MintToTheZeroAddress(); - if (_exists(tokenId)) revert ERC721TokenAlreadyMinted(); - if (tokenId == 0) revert IdZeroForbidden(); - - _beforeTokenTransfer(address(0), to, tokenId); - _beforeNestedTokenTransfer(address(0), to, 0, destinationId, tokenId); - - _balances[to] += 1; - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId - }); - } - - //////////////////////////////////////// - // Ownership - //////////////////////////////////////// - - /** - * @notice Used to retrieve the root owner of the given token. - * @dev Root owner is always the externally owned account. - * @dev If the given token is owned by another token, it will recursively query the parent tokens until reaching the - * root owner. - * @param tokenId ID of the token for which the root owner is being retrieved - * @return address Address of the root owner of the given token - */ - function ownerOf( - uint256 tokenId - ) public view virtual override(IERC6059, IERC721) returns (address) { - (address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf( - tokenId - ); - if (isNft) { - owner = IERC6059(owner).ownerOf(ownerTokenId); - } - return owner; - } - - /** - * @notice Used to retrieve the immediate owner of the given token. - * @dev In the event the NFT is owned by an externally owned account, `tokenId` will be `0`. - * @param tokenId ID of the token for which the immediate owner is being retrieved - * @return address Address of the immediate owner. If the token is owned by an externally owned account, its address - * will be returned. If the token is owned by another token, the parent token's collection smart contract address - * is returned - * @return uint256 Token ID of the immediate owner. If the immediate owner is an externally owned account, the value - * should be `0` - * @return bool A boolean value signifying whether the immediate owner is a token (`true`) or not (`false`) - */ - function directOwnerOf( - uint256 tokenId - ) public view virtual returns (address, uint256, bool) { - DirectOwner memory owner = _directOwners[tokenId]; - if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId(); - - return (owner.ownerAddress, owner.tokenId, owner.tokenId != 0); - } - - //////////////////////////////////////// - // BURNING - //////////////////////////////////////// - - /** - * @notice Used to burn a given token. - * @param tokenId ID of the token to burn - */ - function burn(uint256 tokenId) public virtual { - burn(tokenId, 0); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) public virtual onlyApprovedOrDirectOwner(tokenId) returns (uint256) { - return _burn(tokenId, maxChildrenBurns); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @dev Emits a {NestTransfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function _burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) internal virtual returns (uint256) { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - address owner = ownerOf(tokenId); - _balances[immediateOwner] -= 1; - - _beforeTokenTransfer(owner, address(0), tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - - _approve(address(0), tokenId); - _cleanApprovals(tokenId); - - Child[] memory children = childrenOf(tokenId); - - delete _activeChildren[tokenId]; - delete _pendingChildren[tokenId]; - delete _tokenApprovals[tokenId][owner]; - - uint256 pendingRecursiveBurns; - uint256 totalChildBurns; - - uint256 length = children.length; //gas savings - for (uint256 i; i < length; ) { - if (totalChildBurns >= maxChildrenBurns) - revert MaxRecursiveBurnsReached( - children[i].contractAddress, - children[i].tokenId - ); - delete _childIsInActive[children[i].contractAddress][ - children[i].tokenId - ]; - unchecked { - // At this point we know pendingRecursiveBurns must be at least 1 - pendingRecursiveBurns = maxChildrenBurns - totalChildBurns; - } - // We substract one to the next level to count for the token being burned, then add it again on returns - // This is to allow the behavior of 0 recursive burns meaning only the current token is deleted. - totalChildBurns += - IERC6059(children[i].contractAddress).burn( - children[i].tokenId, - pendingRecursiveBurns - 1 - ) + - 1; - unchecked { - ++i; - } - } - // Can't remove before burning child since child will call back to get root owner - delete _directOwners[tokenId]; - - _afterTokenTransfer(owner, address(0), tokenId); - _afterNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - emit Transfer(owner, address(0), tokenId); - emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId); - - return totalChildBurns; - } - - //////////////////////////////////////// - // APPROVALS - //////////////////////////////////////// - - /** - * @inheritdoc IERC721 - */ - function approve(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - if (to == owner) revert ERC721ApprovalToCurrentOwner(); - - if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) - revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); - - _approve(to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function getApproved( - uint256 tokenId - ) public view virtual returns (address) { - _requireMinted(tokenId); - - return _tokenApprovals[tokenId][ownerOf(tokenId)]; - } - - /** - * @inheritdoc IERC721 - */ - function setApprovalForAll(address operator, bool approved) public virtual { - if (_msgSender() == operator) revert ERC721ApproveToCaller(); - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @inheritdoc IERC721 - */ - function isApprovedForAll( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovals[owner][operator]; - } - - /** - * @notice Used to grant an approval to manage a given token. - * @dev Emits an {Approval} event. - * @param to Address to which the approval is being granted - * @param tokenId ID of the token for which the approval is being granted - */ - function _approve(address to, uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - _tokenApprovals[tokenId][owner] = to; - emit Approval(owner, to, tokenId); - } - - /** - * @notice Used to update the owner of the token and clear the approvals associated with the previous owner. - * @dev The `destinationId` should equal `0` if the new owner is an externally owned account. - * @param tokenId ID of the token being updated - * @param destinationId ID of the token to receive the given token - * @param to Address of account to receive the token - */ - function _updateOwnerAndClearApprovals( - uint256 tokenId, - uint256 destinationId, - address to - ) internal { - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId - }); - - // Clear approvals from the previous owner - _approve(address(0), tokenId); - _cleanApprovals(tokenId); - } - - /** - * @notice Used to remove approvals for the current owner of the given token. - * @param tokenId ID of the token to clear the approvals for - */ - function _cleanApprovals(uint256 tokenId) internal virtual {} - - //////////////////////////////////////// - // UTILS - //////////////////////////////////////// - - /** - * @notice Used to check whether the given account is allowed to manage the given token. - * @dev Requirements: - * - * - `tokenId` must exist. - * @param spender Address that is being checked for approval - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token - */ - function _isApprovedOrOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to check whether the account is approved to manage the token or its direct owner. - * @param spender Address that is being checked for approval or direct ownership - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token or its - * direct owner - */ - function _isApprovedOrDirectOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - (address owner, uint256 parentId, ) = directOwnerOf(tokenId); - // When the parent is an NFT, only it can do operations - if (parentId != 0) { - return (spender == owner); - } - // Otherwise, the owner or approved address can - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to enforce that the given token has been minted. - * @dev Reverts if the `tokenId` has not been minted yet. - * @dev The validation checks whether the owner of a given token is a `0x0` address and considers it not minted if - * it is. This means that both tokens that haven't been minted yet as well as the ones that have already been - * burned will cause the transaction to be reverted. - * @param tokenId ID of the token to check - */ - function _requireMinted(uint256 tokenId) internal view virtual { - if (!_exists(tokenId)) revert ERC721InvalidTokenId(); - } - - /** - * @notice Used to check whether the given token exists. - * @dev Tokens start existing when they are minted (`_mint`) and stop existing when they are burned (`_burn`). - * @param tokenId ID of the token being checked - * @return bool The boolean value signifying whether the token exists - */ - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _directOwners[tokenId].ownerAddress != address(0); - } - - /** - * @notice Used to invoke {IERC721Receiver-onERC721Received} on a target address. - * @dev The call is not executed if the target address is not a contract. - * @param from Address representing the previous owner of the given token - * @param to Yarget address that will receive the tokens - * @param tokenId ID of the token to be transferred - * @param data Optional data to send along with the call - * @return bool Boolean value signifying whether the call correctly returned the expected magic value - */ - function _checkOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory data - ) private returns (bool) { - if (to.isContract()) { - try - IERC721Receiver(to).onERC721Received( - _msgSender(), - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert ERC721TransferToNonReceiverImplementer(); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - //////////////////////////////////////// - // CHILD MANAGEMENT PUBLIC - //////////////////////////////////////// - - /** - * @notice Used to add a child token to a given parent token. - * @dev This adds the iichild token into the given parent token's pending child tokens array. - * @dev You MUST NOT call this method directly. To add a a child to an NFT you must use either - * `nestTransfer`, `nestMint` or `transferChild` to the NFT. - * @dev Requirements: - * - * - `ownerOf` on the child contract must resolve to the called contract. - * - The pending array of the parent contract must not be full. - * @param parentId ID of the parent token to receive the new child token - * @param childId ID of the new proposed child token - * @param data Additional data with no specified format - */ - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) public virtual { - _requireMinted(parentId); - - address childAddress = _msgSender(); - if (!childAddress.isContract()) revert IsNotContract(); - - Child memory child = Child({ - contractAddress: childAddress, - tokenId: childId - }); - - _beforeAddChild(parentId, childAddress, childId); - - uint256 length = pendingChildrenOf(parentId).length; - - if (length < 128) { - _pendingChildren[parentId].push(child); - } else { - revert MaxPendingChildrenReached(); - } - - // Previous length matches the index for the new child - emit ChildProposed(parentId, length, childAddress, childId); - - _afterAddChild(parentId, childAddress, childId); - } - - /** - * @notice @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) public virtual onlyApprovedOrOwner(parentId) { - _acceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @dev Requirements: - * - * - `tokenId` must exist - * - `index` must be in range of the pending children array - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual { - if (pendingChildrenOf(parentId).length <= childIndex) - revert PendingChildIndexOutOfRange(); - - Child memory child = pendingChildOf(parentId, childIndex); - _checkExpectedChild(child, childAddress, childId); - if (_childIsInActive[childAddress][childId] != 0) - revert ChildAlreadyExists(); - - _beforeAcceptChild(parentId, childIndex, childAddress, childId); - - // Remove from pending: - _removeChildByIndex(_pendingChildren[parentId], childIndex); - - // Add to active: - _activeChildren[parentId].push(child); - _childIsInActive[childAddress][childId] = 1; // We use 1 as true - - emit ChildAccepted(parentId, childIndex, childAddress, childId); - - _afterAcceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @param tokenId ID of the parent token for which to reject all of the pending tokens - */ - function rejectAllChildren(uint256 tokenId, uint256 maxRejections) public virtual onlyApprovedOrOwner(tokenId) { - _rejectAllChildren(tokenId, maxRejections); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @dev Requirements: - * - * - `tokenId` must exist - * @param tokenId ID of the parent token for which to reject all of the pending tokens. - * @param maxRejections Maximum number of expected children to reject, used to prevent from - * rejecting children which arrive just before this operation. - */ - function _rejectAllChildren(uint256 tokenId, uint256 maxRejections) - internal - virtual - { - if (_pendingChildren[tokenId].length > maxRejections) - revert UnexpectedNumberOfChildren(); - - _beforeRejectAllChildren(tokenId); - delete _pendingChildren[tokenId]; - emit AllChildrenRejected(tokenId); - _afterRejectAllChildren(tokenId); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) public virtual onlyApprovedOrOwner(tokenId) { - _transferChild( - tokenId, - to, - destinationId, - childIndex, - childAddress, - childId, - isPending, - data - ); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @dev When transferring a child token, the owner of the token is set to `to`, or is not updated in the event of `to` - * being the `0x0` address. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits {ChildTransferred} event. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function _transferChild( - uint256 tokenId, - address to, - uint256 destinationId, // newParentId - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) internal virtual { - Child memory child; - if (isPending) { - child = pendingChildOf(tokenId, childIndex); - } else { - child = childOf(tokenId, childIndex); - } - _checkExpectedChild(child, childAddress, childId); - - _beforeTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - - if (isPending) { - _removeChildByIndex(_pendingChildren[tokenId], childIndex); - } else { - delete _childIsInActive[childAddress][childId]; - _removeChildByIndex(_activeChildren[tokenId], childIndex); - } - - if (to != address(0)) { - if (destinationId == 0) { - IERC721(childAddress).safeTransferFrom( - address(this), - to, - childId, - data - ); - } else { - // Destination is an NFT - IERC6059(child.contractAddress).nestTransferFrom( - address(this), - to, - child.tokenId, - destinationId, - data - ); - } - } - - emit ChildTransferred( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - _afterTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - } - - function _checkExpectedChild( - Child memory child, - address expectedAddress, - uint256 expectedId - ) private pure { - if ( - expectedAddress != child.contractAddress || - expectedId != child.tokenId - ) revert UnexpectedChildId(); - } - - //////////////////////////////////////// - // CHILD MANAGEMENT GETTERS - //////////////////////////////////////// - - /** - * @notice Used to retrieve the active child tokens of a given parent token. - * @dev Returns array of Child structs existing for parent token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the active child tokens - * @return struct[] An array of Child structs containing the parent token's active child tokens - */ - - function childrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory children = _activeChildren[parentId]; - return children; - } - - /** - * @notice Used to retrieve the pending child tokens of a given parent token. - * @dev Returns array of pending Child structs existing for given parent. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the pending child tokens - * @return struct[] An array of Child structs containing the parent token's pending child tokens - */ - - function pendingChildrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory pendingChildren = _pendingChildren[parentId]; - return pendingChildren; - } - - /** - * @notice Used to retrieve a specific active child token for a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the child is being retrieved - * @param index Index of the child token in the parent token's active child tokens array - * @return struct A Child struct containing data about the specified child - */ - function childOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (childrenOf(parentId).length <= index) revert ChildIndexOutOfRange(); - Child memory child = _activeChildren[parentId][index]; - return child; - } - - /** - * @notice Used to retrieve a specific pending child token from a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the pending child token is being retrieved - * @param index Index of the child token in the parent token's pending child tokens array - * @return struct A Child struct containting data about the specified child - */ - function pendingChildOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (pendingChildrenOf(parentId).length <= index) - revert PendingChildIndexOutOfRange(); - Child memory child = _pendingChildren[parentId][index]; - return child; - } - - /** - * @notice Used to verify that the given child tokwn is included in an active array of a token. - * @param childAddress Address of the given token's collection smart contract - * @param childId ID of the child token being checked - * @return bool A boolean value signifying whether the given child token is included in an active child tokens array - * of a token (`true`) or not (`false`) - */ - function childIsInActive( - address childAddress, - uint256 childId - ) public view virtual returns (bool) { - return _childIsInActive[childAddress][childId] != 0; - } - - // HOOKS - - /** - * @notice Hook that is called before any token transfer. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be transferred to `to`. - * - When `from` is zero, `tokenId` will be minted to `to`. - * - When `to` is zero, ``from``'s `tokenId` will be burned. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after any transfer of tokens. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token has been transferred - * @param to Address to which the token has been transferred - * @param tokenId ID of the token that has been transferred - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param fromTokenId ID of the token from which the given token is being transferred - * @param toTokenId ID of the token to which the given token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token was transferred - * @param to Address to which the token was transferred - * @param fromTokenId ID of the token from which the given token was transferred - * @param toTokenId ID of the token to which the given token was transferred - * @param tokenId ID of the token that was transferred - */ - function _afterNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will receive a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has received a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _afterAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that will accept a pending child token - * @param childIndex Index of the child token to accept in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that has accepted a pending child token - * @param childIndex Index of the child token that was accpeted in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's pending children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's pending children array - */ - function _afterAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will transfer a child token - * @param childIndex Index of the child token that will be transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that is expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that is expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token is being transferred from the pending child - * tokens array (`true`) or from the active child tokens array (`false`) - */ - function _beforeTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called after a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has transferred a child token - * @param childIndex Index of the child token that was transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token was transferred from the pending child tokens - * array (`true`) or from the active child tokens array (`false`) - */ - function _afterTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called before a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will reject all of the pending child tokens - */ - function _beforeRejectAllChildren(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called after a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has rejected all of the pending child tokens - */ - function _afterRejectAllChildren(uint256 tokenId) internal virtual {} - - // HELPERS - - /** - * @notice Used to remove a specified child token form an array using its index within said array. - * @dev The caller must ensure that the length of the array is valid compared to the index passed. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param array An array od Child struct containing info about the child tokens in a given child tokens array - * @param index An index of the child token to remove in the accompanying array - */ - function _removeChildByIndex(Child[] storage array, uint256 index) private { - array[index] = array[array.length - 1]; - array.pop(); - } -} diff --git a/assets/eip-6059/contracts/mocks/ERC721Mock.sol b/assets/eip-6059/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index b31caeb545ac9e..00000000000000 --- a/assets/eip-6059/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests with non RMRK implementer - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} -} diff --git a/assets/eip-6059/contracts/mocks/NestableTokenMock.sol b/assets/eip-6059/contracts/mocks/NestableTokenMock.sol deleted file mode 100644 index 28861737fc1aac..00000000000000 --- a/assets/eip-6059/contracts/mocks/NestableTokenMock.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../NestableToken.sol"; - -//Minimal public implementation of IRMRKNestable for testing. -contract NestableTokenMock is NestableToken { - constructor() NestableToken() {} - - function mint(address to, uint256 tokenId) external { - _mint(to, tokenId); - } - - function nestMint( - address to, - uint256 tokenId, - uint256 destinationId - ) external { - _nestMint(to, tokenId, destinationId, ""); - } - - // Utility transfers: - - function transfer(address to, uint256 tokenId) public virtual { - transferFrom(_msgSender(), to, tokenId); - } - - function nestTransfer( - address to, - uint256 tokenId, - uint256 destinationId - ) public virtual { - nestTransferFrom(_msgSender(), to, tokenId, destinationId, ""); - } -} diff --git a/assets/eip-6059/hardhat.config.ts b/assets/eip-6059/hardhat.config.ts deleted file mode 100644 index 4289f15e8be78d..00000000000000 --- a/assets/eip-6059/hardhat.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HardhatUserConfig } from 'hardhat/config'; -import '@nomicfoundation/hardhat-chai-matchers'; -import '@nomiclabs/hardhat-etherscan'; -import '@typechain/hardhat'; -import 'hardhat-contract-sizer'; -import 'hardhat-gas-reporter'; -import 'solidity-coverage'; - -const config: HardhatUserConfig = { - solidity: { - version: '0.8.16', - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6059/img/eip-6059-abandon-child.png b/assets/eip-6059/img/eip-6059-abandon-child.png deleted file mode 100644 index e97465dbca45f1..00000000000000 Binary files a/assets/eip-6059/img/eip-6059-abandon-child.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-nestable-tokens.png b/assets/eip-6059/img/eip-6059-nestable-tokens.png deleted file mode 100644 index cfc15eca08d4bc..00000000000000 Binary files a/assets/eip-6059/img/eip-6059-nestable-tokens.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-reject-child.png b/assets/eip-6059/img/eip-6059-reject-child.png deleted file mode 100644 index 2dc8979906f210..00000000000000 Binary files a/assets/eip-6059/img/eip-6059-reject-child.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-transfer-child-to-eoa.png b/assets/eip-6059/img/eip-6059-transfer-child-to-eoa.png deleted file mode 100644 index e623cbc0346492..00000000000000 Binary files a/assets/eip-6059/img/eip-6059-transfer-child-to-eoa.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-transfer-child-to-token.png b/assets/eip-6059/img/eip-6059-transfer-child-to-token.png deleted file mode 100644 index ee94447fcaef98..00000000000000 Binary files a/assets/eip-6059/img/eip-6059-transfer-child-to-token.png and /dev/null differ diff --git a/assets/eip-6059/img/eip-6059-unnest-child.png b/assets/eip-6059/img/eip-6059-unnest-child.png deleted file mode 100644 index f675c9c28d59ab..00000000000000 Binary files a/assets/eip-6059/img/eip-6059-unnest-child.png and /dev/null differ diff --git a/assets/eip-6059/package.json b/assets/eip-6059/package.json deleted file mode 100644 index 4dd735215bd834..00000000000000 --- a/assets/eip-6059/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "erc-6059", - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/test-helpers": "^0.5.15", - "@primitivefi/hardhat-dodoc": "^0.2.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.3", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "chai": "^4.3.6", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.0", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "hardhat-contract-sizer": "^2.6.1", - "hardhat-gas-reporter": "^1.0.8", - "prettier": "2.7.1", - "prettier-plugin-solidity": "^1.0.0-beta.20", - "solc": "^0.8.9", - "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "walk-sync": "^3.0.0" - } -} diff --git a/assets/eip-6059/test/nestable.ts b/assets/eip-6059/test/nestable.ts deleted file mode 100644 index 96048212d7e25f..00000000000000 --- a/assets/eip-6059/test/nestable.ts +++ /dev/null @@ -1,1158 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { BigNumber, constants } from 'ethers'; -import { NestableTokenMock } from '../typechain-types'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -const ADDRESS_ZERO = constants.AddressZero; - -async function parentChildFixture(): Promise<{ - parent: NestableTokenMock; - child: NestableTokenMock; -}> { - const factory = await ethers.getContractFactory('NestableTokenMock'); - - const parent = await factory.deploy(); - await parent.deployed(); - const child = await factory.deploy(); - await child.deployed(); - return { parent, child }; -} - -describe('NestableToken', function () { - let parent: NestableTokenMock; - let child: NestableTokenMock; - let owner: SignerWithAddress; - let tokenOwner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [owner, tokenOwner, ...addrs] = await ethers.getSigners(); - ({ parent, child } = await loadFixture(parentChildFixture)); - }); - - describe('Minting', async function () { - it('cannot mint id 0', async function () { - const tokenId1 = 0; - await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError( - child, - 'IdZeroForbidden', - ); - }); - - it('cannot nest mint id 0', async function () { - const parentId = 1; - await child.mint(owner.address, parentId); - const childId1 = 0; - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'IdZeroForbidden'); - }); - - it('cannot mint already minted token', async function () { - const tokenId1 = 1; - await child.mint(owner.address, tokenId1); - await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError( - child, - 'ERC721TokenAlreadyMinted', - ); - }); - - it('cannot nest mint already minted token', async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted'); - }); - - it('cannot nest mint already minted token', async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted'); - }); - - it('can mint with no destination', async function () { - const tokenId1 = 1; - await child.mint(tokenOwner.address, tokenId1); - expect(await child.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await child.directOwnerOf(tokenId1)).to.eql([tokenOwner.address, bn(0), false]); - }); - - it('has right owners', async function () { - const otherOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(tokenOwner.address, tokenId1); - const tokenId2 = 2; - await parent.mint(otherOwner.address, tokenId2); - const tokenId3 = 3; - await parent.mint(otherOwner.address, tokenId3); - - expect(await parent.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await parent.ownerOf(tokenId2)).to.equal(otherOwner.address); - expect(await parent.ownerOf(tokenId3)).to.equal(otherOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(2); - - await expect(parent.ownerOf(9999)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - }); - - it('cannot mint to zero address', async function () { - await expect(child.mint(ADDRESS_ZERO, 1)).to.be.revertedWithCustomError( - child, - 'ERC721MintToTheZeroAddress', - ); - }); - - it('cannot nest mint to a non-contract destination', async function () { - await expect(child.nestMint(tokenOwner.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'IsNotContract', - ); - }); - - it('cannot nest mint to non nestable receiver', async function () { - const ERC721 = await ethers.getContractFactory('ERC721Mock'); - const nonReceiver = await ERC721.deploy('Non receiver', 'NR'); - await nonReceiver.deployed(); - - await expect(child.nestMint(nonReceiver.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'MintToNonNestableImplementer', - ); - }); - - it('cannot nest mint to a non-existent token', async function () { - await expect(child.nestMint(parent.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - }); - - it('cannot nest mint to zero address', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(child.nestMint(ADDRESS_ZERO, parentId, 1)).to.be.revertedWithCustomError( - child, - 'IsNotContract', - ); - }); - - it('can mint to contract and owners are ok', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // owner is the same adress - expect(await parent.ownerOf(parentId)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - }); - - it('can mint to contract and direct owners are ok', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Direct owner is an address for the parent - expect(await parent.directOwnerOf(parentId)).to.eql([tokenOwner.address, bn(0), false]); - // Direct owner is a contract for the child - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - }); - - it("can mint to contract and parent's children are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - const children = await parent.childrenOf(parentId); - expect(children).to.eql([]); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([[bn(childId1), child.address]]); - expect(await parent.pendingChildOf(parentId, 0)).to.eql([bn(childId1), child.address]); - }); - - it('cannot get child out of index', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.childOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - 'ChildIndexOutOfRange', - ); - }); - - it('cannot get pending child out of index', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.pendingChildOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - 'PendingChildIndexOutOfRange', - ); - }); - - it('can mint multiple children', async function () { - const parentId = 1; - const childId1 = 99; - const childId2 = 100; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId2)).to.equal(tokenOwner.address); - - expect(await child.balanceOf(parent.address)).to.equal(2); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([ - [bn(childId1), child.address], - [bn(childId2), child.address], - ]); - }); - - it('can mint child into child', async function () { - const parentId = 1; - const childId1 = 99; - const granchildId = 999; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - - // Check balances -- yes, technically the counted balance indicates `child` owns an instance of itself - // and this is a little counterintuitive, but the root owner is the EOA. - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - const pendingChildrenOfChunky10 = await parent.pendingChildrenOf(parentId); - const pendingChildrenOfMonkey1 = await child.pendingChildrenOf(childId1); - - expect(pendingChildrenOfChunky10).to.eql([[bn(childId1), child.address]]); - expect(pendingChildrenOfMonkey1).to.eql([[bn(granchildId), child.address]]); - - expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]); - - expect(await child.ownerOf(granchildId)).to.eql(tokenOwner.address); - }); - - it('cannot have too many pending children', async () => { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - - // First 128 should be fine. - for (let i = 1; i <= 128; i++) { - await child.nestMint(parent.address, i, parentId); - } - - await expect(child.nestMint(parent.address, 129, parentId)).to.be.revertedWithCustomError( - child, - 'MaxPendingChildrenReached', - ); - }); - }); - - describe('Interface support', async function () { - it('can support IERC165', async function () { - expect(await parent.supportsInterface('0x01ffc9a7')).to.equal(true); - }); - - it('can support IERC721', async function () { - expect(await parent.supportsInterface('0x80ac58cd')).to.equal(true); - }); - - it('can support IERC6059', async function () { - expect(await parent.supportsInterface('0x42b0e56f')).to.equal(true); - }); - - it('cannot support other interfaceId', async function () { - expect(await parent.supportsInterface('0xffffffff')).to.equal(false); - }); - }); - - describe('Adding child', async function () { - it('cannot add child from user address', async function () { - const tokenOwner1 = addrs[0]; - const tokenOwner2 = addrs[1]; - const parentId = 1; - await parent.mint(tokenOwner1.address, parentId); - const childId1 = 99; - await child.mint(tokenOwner2.address, childId1); - await expect(parent.addChild(parentId, childId1, '0x')).to.be.revertedWithCustomError( - parent, - 'IsNotContract', - ); - }); - }); - - describe('Accept child', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it('can accept child', async function () { - await expect(parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1)) - .to.emit(parent, 'ChildAccepted') - .withArgs(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('can accept child if approved', async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved).acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('can accept child if approved for all', async function () { - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator).acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('cannot accept not owned child', async function () { - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).acceptChild(parentId, 0, child.address, childId1), - ).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot accept child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, otherChildId), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 0, otherAddress, childId1), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('cannot accept children for non existing index', async () => { - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 1, child.address, childId1), - ).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange'); - }); - - async function checkChildWasAccepted() { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - } - }); - - describe('Rejecting children', async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, 99, parentId); - }); - - it('can reject all pending children', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect(parent.connect(tokenOwner).rejectAllChildren(parentId, 3)) - .to.emit(parent, 'AllChildrenRejected') - .withArgs(parentId); - await checkNoChildrenNorPending(parentId); - - // They are still on the child - expect(await child.balanceOf(parent.address)).to.equal(3); - }); - - it('cannot reject all pending children if there are more than expected', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect( - parent.connect(tokenOwner).rejectAllChildren(parentId, 1), - ).to.be.revertedWithCustomError(parent, 'UnexpectedNumberOfChildren'); - }); - - it('can reject all pending children if approved', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const rejecter = addrs[1]; - await parent.connect(tokenOwner).approve(rejecter.address, parentId); - await parent.connect(rejecter).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it('can reject all pending children if approved for all', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it('cannot reject all pending children for not owned pending child', async function () { - const notOwner = addrs[3]; - - await expect( - parent.connect(notOwner).rejectAllChildren(parentId, 2), - ).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner'); - }); - }); - - describe('Burning', async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - }); - - it('can burn token', async function () { - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - await parent.connect(tokenOwner)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can burn token if approved', async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can burn token if approved for all', async function () { - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can recursively burn nested token', async function () { - const childId1 = 99; - const granchildId = 999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, granchildId); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - expect(await child.childrenOf(childId1)).to.eql([[bn(granchildId), child.address]]); - expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]); - - // Sets recursive burns to 2 - await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(0); - expect(await child.balanceOf(parent.address)).to.equal(0); - expect(await child.balanceOf(child.address)).to.equal(0); - - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - await expect(parent.directOwnerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - - await expect(child.ownerOf(childId1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - await expect(child.directOwnerOf(childId1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - - await expect(parent.ownerOf(granchildId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - await expect(parent.directOwnerOf(granchildId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - }); - - it('can recursively burn nested token with the right number of recursive burns', async function () { - // Parent - // -> Child1 - // -> GrandChild1 - // -> GrandChild2 - // -> GreatGrandChild1 - // -> Child2 - // Total tree 5 (4 recursive burns) - const childId1 = 99; - const childId2 = 100; - const grandChild1 = 999; - const grandChild2 = 1000; - const greatGrandChild1 = 9999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - await child.nestMint(child.address, grandChild1, childId1); - await child.nestMint(child.address, grandChild2, childId1); - await child.nestMint(child.address, greatGrandChild1, grandChild2); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId2); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild1); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild2); - await child.connect(tokenOwner).acceptChild(grandChild2, 0, child.address, greatGrandChild1); - - // 0 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 0)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, childId1); - // 1 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 1)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, grandChild1); - // 2 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, grandChild2); - // 3 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 3)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, greatGrandChild1); - // 4 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 4)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, childId2); - // 5 is just enough - await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 5); - }); - - async function checkBurntParent() { - expect(await parent.balanceOf(addrs[1].address)).to.equal(0); - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - } - }); - - describe('Transferring Active Children', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - childId1 = 99; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - }); - - it('can transfer child with to as root owner', async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, false, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(); - }); - - it('can transfer child to another address', async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, false, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it('can transfer child to another NFT', async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - false, - '0x', - ), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot transfer child out of index', async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'ChildIndexOutOfRange'); - }); - - it('cannot transfer child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('can transfer child if approved', async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child if approved for all', async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child with grandchild and children are ok', async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]); - }); - - it('cannot transfer child if not child root owner', async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot transfer child from not existing parent', async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild(badChildId, toOwner, 0, 0, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId'); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe('Transferring Pending Children', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it('can transfer child with to as root owner', async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, true, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(); - }); - - it('can transfer child to another address', async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, true, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it('can transfer child to another NFT', async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - true, - '0x', - ), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot transfer child out of index', async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange'); - }); - - it('cannot transfer child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('can transfer child if approved', async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild(parentId, toOwner, 0, 0,child.address, childId1, true, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child if approved for all', async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child with grandchild and children are ok', async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]); - }); - - it('cannot transfer child if not child root owner', async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot transfer child from not existing parent', async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild(badChildId, toOwner, 0, 0, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId'); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe('Transfer', async function () { - it('can transfer token', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - // Balances and ownership are updated - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - }); - - it('cannot transfer not owned token', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(newOwner).transfer(newOwner.address, tokenId1), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot transfer to address zero', async function () { - const firstOwner = addrs[1]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(firstOwner).transfer(ADDRESS_ZERO, tokenId1), - ).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress'); - }); - - it('can transfer token from approved address (not owner)', async function () { - const firstOwner = addrs[1]; - const approved = addrs[2]; - const newOwner = addrs[3]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - - await parent.connect(firstOwner).approve(approved.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - }); - - it('can transfer not nested token with child to address and owners/children are ok', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await parent.connect(firstOwner).transfer(newOwner.address, parentId); - - // Balances and ownership are updated - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - - expect(await parent.ownerOf(parentId)).to.eql(newOwner.address); - expect(await parent.directOwnerOf(parentId)).to.eql([newOwner.address, bn(0), false]); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(newOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - - // Parent still has its children - expect(await parent.pendingChildrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot directly transfer nested child', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.connect(firstOwner).transfer(newOwner.address, childId1), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('can transfer parent token to token with same owner, family tree is ok', async function () { - const firstOwner = addrs[1]; - const grandParentId = 999; - await parent.mint(firstOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(firstOwner.address)).to.equal(2); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Transfers token parentId to (parent.address, token grandParentId) - await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId); - - // Balances unchanged since root owner is the same - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - - it('can transfer parent token to token with different owner, family tree is ok', async function () { - const firstOwner = addrs[1]; - const otherOwner = addrs[2]; - const grandParentId = 999; - await parent.mint(otherOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // firstOwner calls parent to transfer parent token parent - await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId); - - // Balances update - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - }); - - describe('Nest Transfer', async function () { - let firstOwner: SignerWithAddress; - let parentId: number; - let childId1: number; - - beforeEach(async function () { - firstOwner = addrs[1]; - parentId = 1; - childId1 = 99; - await parent.mint(firstOwner.address, parentId); - await child.mint(firstOwner.address, childId1); - }); - - it('cannot nest tranfer from non immediate owner (owner of parent)', async function () { - const otherParentId = 2; - await parent.mint(firstOwner.address, otherParentId); - // We send it to the parent first - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child.connect(firstOwner).nestTransfer(parent.address, childId1, otherParentId), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot nest tranfer to same NFT', async function () { - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child.connect(firstOwner).nestTransfer(child.address, childId1, childId1), - ).to.be.revertedWithCustomError(child, 'NestableTransferToSelf'); - }); - - it('cannot nest tranfer a descendant same NFT', async function () { - // We can no longer nest transfer it, even if we are the root owner: - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - const grandChildId = 999; - await child.nestMint(child.address, grandChildId, childId1); - // Ownership is now parent->child->granChild - // Cannot send parent to grandChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, grandChildId), - ).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant'); - // Cannot send parent to child - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, childId1), - ).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant'); - }); - - it('cannot nest tranfer if ancestors tree is too deep', async function () { - let lastId = childId1; - for (let i = 101; i <= 200; i++) { - await child.nestMint(child.address, i, lastId); - lastId = i; - } - // Ownership is now parent->child->child->child->child...->lastChild - // Cannot send parent to lastChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, lastId), - ).to.be.revertedWithCustomError(child, 'NestableTooDeep'); - }); - - it('cannot nest tranfer if not owner', async function () { - const notOwner = addrs[3]; - await expect( - child.connect(notOwner).nestTransfer(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot nest tranfer to address 0', async function () { - await expect( - child.connect(firstOwner).nestTransfer(ADDRESS_ZERO, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress'); - }); - - it('cannot nest tranfer to a non contract', async function () { - const newOwner = addrs[2]; - await expect( - child.connect(firstOwner).nestTransfer(newOwner.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'IsNotContract'); - }); - - it('cannot nest tranfer to contract if it does implement IERC6059', async function () { - const ERC721 = await ethers.getContractFactory('ERC721Mock'); - const nonNestable = await ERC721.deploy('Non receiver', 'NR'); - await nonNestable.deployed(); - await expect( - child.connect(firstOwner).nestTransfer(nonNestable.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'NestableTransferToNonNestableImplementer'); - }); - - it('can nest tranfer to IERC6059 contract', async function () { - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - expect(await child.ownerOf(childId1)).to.eql(firstOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - }); - - it('cannot nest tranfer to non existing parent token', async function () { - const notExistingParentId = 9999; - await expect( - child.connect(firstOwner).nestTransfer(parent.address, childId1, notExistingParentId), - ).to.be.revertedWithCustomError(parent, 'ERC721InvalidTokenId'); - }); - }); - - async function checkNoChildrenNorPending(parentId: number): Promise { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([]); - } - - async function checkAcceptedAndPendingChildren( - contract: NestableTokenMock, - tokenId1: number, - expectedAccepted: any[], - expectedPending: any[], - ) { - const accepted = await contract.childrenOf(tokenId1); - expect(accepted).to.eql(expectedAccepted); - - const pending = await contract.pendingChildrenOf(tokenId1); - expect(pending).to.eql(expectedPending); - } -}); diff --git a/assets/eip-6065/Implementation.sol b/assets/eip-6065/Implementation.sol deleted file mode 100644 index 1849d503e5c5d8..00000000000000 --- a/assets/eip-6065/Implementation.sol +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -/* -NOTE: WORK IN PROGRESS, NOT READY FOR PRODUCTION - -This is a sample interface contract by Klasma Labs Inc. for demonstration purposes for EIP-6065. The technical implementation includes the -following additional components for reference, this implementation is not required. - -Summary of Klasma Inc. ERC-6065 implementation: --- NFT burn and mint function --- Immutable NFT data (unique identifiers and operating agreement hash) --- Debt tracking by Administrator --- Blocklist function to freeze asset held by fraudulent addresses (NOTE: to be implemented in the future) --- Foreclosure logic initiated by Administrator --- managerOf function implementation -*/ - -import "solmate/tokens/ERC721.sol"; -import "forge-std/interfaces/IERC20.sol"; -import "forge-std/interfaces/IERC165.sol"; -import "openzeppelin-contracts/utils/Strings.sol"; - -contract ERC6065 is ERC721 { - using Strings for uint256; - - address public ADMINISTRATOR; - uint256 public COUNTER; - string public URIBASE; - - struct EIP6065Immutable { - string legal_description_of_property; - string street_address; - string geo_json; - string parcel_id; - string legal_owner; - bytes32 operating_agreement_hash; - } - - struct EIP6065Mutable { - address debt_token; - int256 debt_amt; - bool foreclosed; - } - - mapping (uint256 => EIP6065Immutable) EIP6065ImmutableMetadata; - mapping (uint256 => EIP6065Mutable) EIP6065MutableMetadata; - - event DebtTokenChanged(uint256 id, address newToken); - event DebtBalanceChanged(uint256 id, int256 changedBy, int256 newAmt); - event DebtPaid(uint256 id, uint256 paidAmt, int256 remainingAmt); - event CreditClaimed(uint256 id, uint256 claimedAmt); - event Foreclosed(uint256 id); - - constructor(string memory _name, string memory _symbol, string memory _uriBase) ERC721(_name, _symbol) { - ADMINISTRATOR = msg.sender; - URIBASE = _uriBase; - } - - modifier onlyAdmin() { - require(msg.sender == ADMINISTRATOR, "NOT_ADMIN"); - _; - } - - modifier tokenExists(uint256 _id) { - require(ownerOf(_id) != address(0), "NOT_MINTED"); - _; - } - - ///// SETTERS ///// - - function setAdmin(address _new) external onlyAdmin { - ADMINISTRATOR = _new; - } - - function setUriBase(string calldata _new) external onlyAdmin { - URIBASE = _new; - } - - ///// GETTERS ///// - - function tokenURI(uint256 _id) public view override tokenExists(_id) returns (string memory){ - return string(abi.encodePacked(URIBASE, _id.toString())); - } - - function legalDescriptionOf(uint256 _id) external view tokenExists(_id) returns (string memory){ - return EIP6065ImmutableMetadata[_id].legal_description_of_property; - } - - function addressOf(uint256 _id) external view tokenExists(_id) returns (string memory){ - return EIP6065ImmutableMetadata[_id].street_address; - } - - function geoJsonOf(uint256 _id) external view tokenExists(_id) returns (string memory){ - return EIP6065ImmutableMetadata[_id].geo_json; - } - - function parcelIdOf(uint256 _id) external view tokenExists(_id) returns (string memory){ - return EIP6065ImmutableMetadata[_id].parcel_id; - } - - function legalOwnerOf(uint256 _id) external view tokenExists(_id) returns (string memory){ - return EIP6065ImmutableMetadata[_id].legal_owner; - } - - function operatingAgreementHashOf(uint256 _id) external view tokenExists(_id) returns (bytes32){ - return EIP6065ImmutableMetadata[_id].operating_agreement_hash; - } - - function debtOf(uint256 _id) external view tokenExists(_id) returns (address, int256, bool){ - EIP6065Mutable memory _data = EIP6065MutableMetadata[_id]; - return (_data.debt_token, _data.debt_amt, _data.foreclosed); - } - - // EIP6065 get manager of the NFT. Smart contracts can optionally implement managerOf() to chain this call to some underlying contract or EOA - // Otherwise, default to the ownerOf(NFT) being managerOf(NFT) - function managerOf(uint256 _id) external view returns (address){ // note: modifier removed, implement in code for efficiency tokenExists(_id) - address _owner = ownerOf(_id); - require(_owner != address(0), "NOT_MINTED"); - - uint256 _codeSize; - assembly { - _codeSize := extcodesize(_owner) - } - if (_codeSize == 0){ - return _owner; - } - else { - try IERC165(_owner).supportsInterface(0x01ffc9a7) returns (bool _check1) { // 0x01ffc9a7 is EIP165 interface - if (_check1){ - try IERC165(_owner).supportsInterface(0xffffffff) returns (bool _check2){ // 0xffffffff is required false check, see: https://eips.ethereum.org/EIPS/eip-165 - if (!_check2){ - bool _check3 = IERC165(_owner).supportsInterface(0x9325945c); // 0x9325945c is bytes4(keccak256(bytes("managerOf(address,uint256)"))) ie: IManager interface - if (_check3){ - return IManager(_owner).managerOf(address(this), _id); // chain managerOf(nftContract, id) call if smart contract supports this interface - } else { - return _owner; - } - } else { - return _owner; - } - } catch { - return _owner; - } - } else { - return _owner; - } - } catch { - return _owner; - } - } - } - - ///// MANAGEMENT OF NFT STATE / MUTABLE VARS ///// - - // change debt token, requires no existing debt or credit on the token - function changeDebtToken(uint256 _id, address _new) public onlyAdmin tokenExists(_id) { - require(EIP6065MutableMetadata[_id].debt_amt == 0, "DEBT_AMT_NOT_ZERO"); - EIP6065MutableMetadata[_id].debt_token = _new; - emit DebtTokenChanged(_id, _new); - } - - function balanceChange(uint256 _id, int256 _amt) public onlyAdmin tokenExists(_id) { - require(_amt != 0, "NO_AMT"); - EIP6065Mutable memory _data = EIP6065MutableMetadata[_id]; - int256 _oldAmt = _data.debt_amt; - _data.debt_amt += _amt; - - if (_amt > 0){ // debt added to token - if (_oldAmt < 0){ - if (_data.debt_amt < 0){ - IERC20(_data.debt_token).transfer(ADMINISTRATOR, uint256(_amt)); // return entire _amt as it's all prior credit - } - else { - IERC20(_data.debt_token).transfer(ADMINISTRATOR, uint256(-1 * _oldAmt)); // return _oldAmt, as this credit has been zeroed, and debt added - } - } - } - else { // (_amt < 0) ie: credit added to token - if (_data.debt_amt < 0){ - if (_oldAmt < 0){ - IERC20(_data.debt_token).transferFrom(ADMINISTRATOR, address(this), uint256(-1 * _amt)); // admin owes entire amt as credit - } - else { - IERC20(_data.debt_token).transferFrom(ADMINISTRATOR, address(this), uint256(-1 * _data.debt_amt)); - } - } - } - - EIP6065MutableMetadata[_id].debt_amt = _data.debt_amt; - emit DebtBalanceChanged(_id, _amt, _data.debt_amt); - } - - function payDebt(uint256 _id, uint256 _amt) public tokenExists(_id) { - EIP6065Mutable memory _data = EIP6065MutableMetadata[_id]; - require(_data.debt_amt > 0, "NO_DEBT"); - require(!_data.foreclosed, "FORECLOSED"); - if (_amt > uint256(_data.debt_amt)) _amt = uint256(_data.debt_amt); - - IERC20(_data.debt_token).transferFrom(msg.sender, ADMINISTRATOR, _amt); - - _data.debt_amt -= int256(_amt); - - EIP6065MutableMetadata[_id].debt_amt = _data.debt_amt; - emit DebtPaid(_id, _amt, _data.debt_amt); - } - - function claimCredit(uint256 _id) public { - require(msg.sender == ownerOf(_id), "NOT_OWNER"); - - EIP6065Mutable memory _data = EIP6065MutableMetadata[_id]; - require(_data.debt_amt < 0, "NO_CREDIT"); - - EIP6065MutableMetadata[_id].debt_amt = 0; - uint256 _transferAmt = uint256(-1 * _data.debt_amt); - IERC20(_data.debt_token).transfer(msg.sender, _transferAmt); - emit CreditClaimed(_id, _transferAmt); - } - - function foreclose(uint256 _id) public onlyAdmin tokenExists(_id) { - require(!EIP6065MutableMetadata[_id].foreclosed, "FORECLOSED"); - EIP6065MutableMetadata[_id].foreclosed = true; - emit Foreclosed(_id); - } - - ///// MINT & BURN LOGIC ///// - - function mint(address _to, EIP6065Immutable calldata _immutableData, EIP6065Mutable calldata _mutableData) public onlyAdmin { - uint256 _counter = COUNTER; - _mint(_to, _counter); - - EIP6065ImmutableMetadata[_counter] = _immutableData; - EIP6065MutableMetadata[_counter] = _mutableData; - - // not reasonable to overflow - unchecked { - COUNTER = _counter + 1; - } - } - - function burn(uint256 _id) public onlyAdmin { - require(ownerOf(_id) == ADMINISTRATOR, "ADMIN_NOT_OWNER"); - - // recommend to clear data and info about NFT here too - delete EIP6065ImmutableMetadata[_id]; - delete EIP6065MutableMetadata[_id]; - - _burn(_id); - } -} - -// simple manager interface to chain calls of the initial managerOf() function call -interface IManager is IERC165 { - function managerOf(address _nftContract, uint256 _id) external view returns (address); -} \ No newline at end of file diff --git a/assets/eip-6065/corporate-structure.png b/assets/eip-6065/corporate-structure.png deleted file mode 100644 index 75c442293345ce..00000000000000 Binary files a/assets/eip-6065/corporate-structure.png and /dev/null differ diff --git a/assets/eip-6123/README.md b/assets/eip-6123/README.md deleted file mode 100644 index 2bf456a68bda04..00000000000000 --- a/assets/eip-6123/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# SDC Solidity implementation - -## Description -This sdc implementation aims to implement process logic in a very lean way using an integrative solidity implementation and according unit tests - -### Provided Contracts and Tests -- `contracts/ISDC.sol` - Interface contract -- `contracts/SDC.sol` - SDC reference implementation contract -- `contracts/SDCToken.sol` - Mintable token contract for unit tests -- `test/SDC.js` - Unit tests for livecycle of sdc implementation - -### Used javascript based testing libraries for solidity -- `ethereum-waffle`: Waffle is a Solidity testing library. It allows you to write tests for your contracts with JavaScript. -- `chai`: Chai is an assertion library and provides functions like expect. -- `ethers`: This is a popular Ethereum client library. It allows you to interface with blockchains that implement the Ethereum API. -- `solidity-coverage`: This library gives you coverage reports on unit tests with the help of Istanbul. - -### Compile and run tests with hardhat -We provide the essential steps to compile the contracts and run provided unit tests -Check that you have the latest version of npm and node via `npm -version` (should be better than 8.5.0) and `node -v` (should be better than 16.14.2). - -1. Check out project -2. Go to folder and initialise a new npm project: `npm init -y`. A basic `package.json` file should occur -3. Install Hardhat as local solidity dev environment: `npx hardhat` -4. Select following option: Create an empty hardhat.config.js -5. Install Hardhat as a development dependency: `npm install --save-dev hardhat` -6. Install further testing dependencies: -`npm install --save-dev @nomiclabs/hardhat-waffle @nomiclabs/hardhat-ethers ethereum-waffle chai ethers solidity-coverage` -7. Install open zeppelin contracts: `npm install @openzeppelin/contracts` -8. add plugins to hardhat.config.ts: -``` -require("@nomiclabs/hardhat-waffle"); -require('solidity-coverage'); -``` - -9. Adding commands to `package.json`: -``` -"scripts": { - "build": "hardhat compile", - "test:light": "hardhat test", - "test": "hardhat coverage" - }, -``` -9. run `npm run build` -10. run `npm run test` - - diff --git a/assets/eip-6123/contracts/ISDC.sol b/assets/eip-6123/contracts/ISDC.sol deleted file mode 100644 index 45059fa2176a6f..00000000000000 --- a/assets/eip-6123/contracts/ISDC.sol +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.7.0 <0.9.0; - -/*------------------------------------------- DESCRIPTION ---------------------------------------------------------------------------------------*/ - -/** - * @title ERC6123 Smart Derivative Contract - * @dev Interface specification for a Smart Derivative Contract, which specifies the post-trade live cycle of an OTC financial derivative in a completely deterministic way. - * Counterparty Risk is removed by construction. - * - * A Smart Derivative Contract is a deterministic settlement protocol which has economically the same behaviour as a collateralized OTC financial derivative. - * It aims is to remove many inefficiencies in collateralized OTC transactions and remove counterparty credit risk by construction. - * - * In contrast to a collateralized derivative contract based and collateral flows are netted. As result, the smart derivative contract generates a stream of - * reflecting the settlement of a referenced underlying. The settlement cash flows may be daily (which is the standard frequency in traditional markets) - * or at higher frequencies. - * With each settlement flow the change is the (discounting adjusted) net present value of the underlying contract is exchanged and the value of the contract is reset to zero. - * - * To automatically process settlement, counterparties need to provide sufficient prefunded margin amounts and termination fees at the - * beginning of each settlement cycle. Through a settlement cycle the margin amounts are locked. Simplified, the contract reverts the classical scheme of - * 1) underlying valuation, then 2) funding of a margin call to - * 1) pre-funding of a margin buffer (a token), then 2) settlement. - * - * A SDC automatically terminates the derivatives contract if there is insufficient pre-funding or if the settlement amount exceeds a - * prefunded margin balance. Beyond mutual termination is also intended by the function specification. - * - * Events and Functionality specify the entire live cycle: TradeInception, TradeConfirmation, TradeTermination, Margin-Account-Mechanics, Valuation and Settlement. - * - * The process can be described by time points and time-intervals which are associated with well defined states: - *
    - *
  1. t < T* (befrore incept). - *
  2. - *
  3. - * The process runs in cycles. Let i = 0,1,2,... denote the index of the cycle. Within each cycle there are times - * T_{i,0}, T_{i,1}, T_{i,2}, T_{i,3} with T_{i,1} = pre-funding of the Smart Contract, T_{i,2} = request valuation from oracle, T_{i,3} = perform settlement on given valuation, T_{i+1,0} = T_{i,3}. - *
  4. - *
  5. - * Given this time discretization the states are assigned to time points and time intervalls: - *
    - *
    Idle
    - *
    Before incept or after terminate
    - * - *
    Initiation
    - *
    T* < t < T_{0}, where T* is time of incept and T_{0} = T_{0,0}
    - * - *
    AwaitingFunding
    - *
    T_{i,0} < t < T_{i,1}
    - * - *
    Funding
    - *
    t = T_{i,1}
    - * - *
    AwaitingSettlement
    - *
    T_{i,1} < t < T_{i,2}
    - * - *
    ValuationAndSettlement
    - *
    T_{i,2} < t < T_{i,3}
    - * - *
    Settled
    - *
    t = T_{i,3}
    - *
    - *
  6. - *
- */ - -interface ISDC { - /*------------------------------------------- EVENTS ---------------------------------------------------------------------------------------*/ - /** - * @dev Emitted when a new trade is incepted from a eligible counterparty - * @param initiator is the address from which trade was incepted - * @param tradeId is the trade ID (e.g. generated internally) - * @param tradeData holding the trade parameters - */ - event TradeIncepted(address initiator, string tradeId, string tradeData); - - /** - * @dev Emitted when an incepted trade is confirmed by the opposite counterparty - * @param confirmer the confirming party - * @param tradeId the trade identifier - */ - event TradeConfirmed(address confirmer, string tradeId); - - /** - * @dev Emitted when a confirmed trade is set to active - e.g. when termination fee amounts are provided - * @param tradeId the trade identifier of the activated trade - */ - event TradeActivated(string tradeId); - - /** - * @dev Emitted when an active trade is terminated - * @param cause string holding the cause of the termination - */ - event TradeTerminated(string cause); - - /** - * @dev Emitted when funding phase is initiated - */ - event ProcessAwaitingFunding(); - - /** - * @dev Emitted when margin balance was updated and sufficient funding is provided - */ - event ProcessFunded(); - - /** - * @dev Emitted when a valuation and settlement is requested - * @param tradeData holding the stored trade data - * @param lastSettlementData holding the settlementdata from previous settlement (next settlement will be the increment of next valuation compared to former valuation) - */ - event ProcessSettlementRequest(string tradeData, string lastSettlementData); - - /** - * @dev Emitted when a settlement was processed succesfully - */ - event ProcessSettled(); - - /** - * @dev Emitted when a counterparty proactively requests an early termination of the underlying trade - * @param cpAddress the address of the requesting party - * @param tradeId the trade identifier which is supposed to be terminated - */ - event TradeTerminationRequest(address cpAddress, string tradeId); - - /** - * @dev Emitted when early termination request is confirmed by the opposite party - * @param cpAddress the party which confirms the trade termination - * @param tradeId the trade identifier which is supposed to be terminated - */ - event TradeTerminationConfirmed(address cpAddress, string tradeId); - - /*------------------------------------------- FUNCTIONALITY ---------------------------------------------------------------------------------------*/ - - /// Trade Inception - - /** - * @notice Handles trade inception, stores trade data - * @dev emits a {TradeIncepted} event - * @param _tradeData a description of the trade specification e.g. in xml format, suggested structure - see assets/eip-6123/doc/sample-tradedata-filestructure.xml - * @param _initialSettlementData the initial settlement data (e.g. initial market data at which trade was incepted) - * @param _upfrontPayment provides an initial payment amount upfront - */ - function inceptTrade(string memory _tradeData, string memory _initialSettlementData, int256 _upfrontPayment) external; - - /** - * @notice Performs a matching of provided trade data and settlement data - * @dev emits a {TradeConfirmed} event if trade data match - * @param _tradeData a description of the trade in sdc.xml, e.g. in xml format, suggested structure - see assets/eip-6123/doc/sample-tradedata-filestructure.xml - * @param _initialSettlementData the initial settlement data (e.g. initial market data at which trade was incepted) - */ - function confirmTrade(string memory _tradeData, string memory _initialSettlementData) external; - - /// Settlement Cycle: Prefunding - - /** - * @notice Called from outside to check and secure pre-funding. Terminate the trade if prefunding fails. - * @dev emits a {ProcessFunded} event if prefunding check is successful or a {TradeTerminated} event if prefunding check fails - */ - function initiatePrefunding() external; - - /// Settlement Cycle: Settlement - - /** - * @notice Called to trigger a (maybe external) valuation of the underlying contract and afterwards the according settlement process - * @dev emits a {ProcessSettlementRequest} - */ - function initiateSettlement() external; - - /** - * @notice Called from outside to trigger according settlement on chain-balances callback for initiateSettlement() event handler - * @dev emits a {ProcessSettled} if settlement is successful or {TradeTerminated} if settlement fails - * @param settlementAmount the settlement amount. If settlementAmount > 0 then receivingParty receives this amount from other party. If settlementAmount < 0 then other party receives -settlementAmount from receivingParty. - * @param settlementData. the tripple (product, previousSettlementData, settlementData) determines the settlementAmount. - */ - function performSettlement(int256 settlementAmount, string memory settlementData) external; - - /// Trade termination - - /** - * @notice Called from a counterparty to request a mutual termination - * @dev emits a {TradeTerminationRequest} - * @param tradeId the trade identifier which is supposed to be terminated - */ - function requestTradeTermination(string memory tradeId) external; - - /** - * @notice Called from a counterparty to confirm a termination, which will triggers a final settlement before trade gets inactive - * @dev emits a {TradeTerminationConfirmed} - * @param tradeId the trade identifier of the trade which is supposed to be terminated - */ - function confirmTradeTermination(string memory tradeId) external; -} - diff --git a/assets/eip-6123/contracts/SDC.sol b/assets/eip-6123/contracts/SDC.sol deleted file mode 100644 index 6865c519be6c71..00000000000000 --- a/assets/eip-6123/contracts/SDC.sol +++ /dev/null @@ -1,449 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity >=0.8.0 <0.9.0; - -import "./ISDC.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - - -/** - * @title Reference Implementation of ERC6123 - Smart Derivative Contract - * @notice This reference implementation is based on a finite state machine with predefined trade and process states (see enums below) - * Some comments on the implementation: - * - trade and process states are used in modifiers to check which function is able to be called at which state - * - trade data are stored in the contract - * - trade data matching is done in incept and confirm routine (comparing the hash of the provided data) - * - ERC-20 token is used for three participants: counterparty1 and counterparty2 and sdc - * - when prefunding is done sdc contract will hold agreed amounts and perform settlement on those - * - sdc also keeps track on internal balances for each counterparty - * - during prefunding sdc will transfer required amounts to its own balance - therefore sufficient approval is needed - * - upon termination all remaining 'locked' amounts will be transferred back to the counterparties -*/ - -contract SDC is ISDC { - /* - * Trade States - */ - enum TradeState { - - /* - * State before the trade is incepted. - */ - Inactive, - - /* - * Incepted: Trade data submitted by one party. Market data for initial valuation is set. - */ - Incepted, - - /* - * Confirmed: Trade data accepted by other party. - */ - Confirmed, - - /* - * Active (Confirmend + Prefunded Termination Fees). Will cycle through process states. - */ - Active, - - /* - * Terminated. - */ - Terminated - } - - /* - * Process States. t < T* (vor incept). The process runs in cycles. Let i = 0,1,2,... denote the index of the cycle. Within each cycle there are times - * T_{i,0}, T_{i,1}, T_{i,2}, T_{i,3} with T_{i,1} = pre-funding of the Smart Contract, T_{i,2} = request valuation from oracle, T_{i,3} = perform settlement on given valuation, T_{i+1,0} = T_{i,3}. - * Given this time discretization the states are assigned to time points and time intervalls: - * Idle: Before incept or after terminate - * Initiation: T* < t < T_{0}, where T* is time of incept and T_{0} = T_{0,0} - * AwaitingFunding: T_{i,0} < t < T_{i,1} - * Funding: t = T_{i,1} - * AwaitingSettlement: T_{i,1} < t < T_{i,2} - * ValuationAndSettlement: T_{i,2} < t < T_{i,3} - * Settled: t = T_{i,3} - */ - enum ProcessState { - /** - * @dev The process has not yet started or is terminated - */ - Idle, - /* - * @dev The process is initiated (incepted, but not yet completed confimation). Next: AwaitingFunding - */ - Initiation, - /* - * @dev Awaiiting preparation for funding the smart contract. Next: Funding - */ - AwaitingFunding, - /* - * @dev Prefunding the smart contract. Next: AwaitingSettlement - */ - Funding, - /* - * @dev The smart contract is completely funded and awaits settlement. Next: ValuationAndSettlement - */ - Funded, - /* - * @dev The settlement process is initiated. Next: Settled or InTermination - */ - ValuationAndSettlement, - /* - * @dev Termination started. - */ - InTermination - } - - struct MarginRequirement { - int256 buffer; - int256 terminationFee; - } - - /* - * Modifiers serve as guards whether at a specific process state a specific function can be called - */ - - modifier onlyCounterparty() { - require(msg.sender == party1 || msg.sender == party2, "You are not a counterparty."); _; - } - modifier onlyWhenTradeInactive() { - require(tradeState == TradeState.Inactive, "Trade state is not 'Inactive'."); _; - } - modifier onlyWhenTradeIncepted() { - require(tradeState == TradeState.Incepted, "Trade state is not 'Incepted'."); _; - } - modifier onlyWhenProcessAwaitingFunding() { - require(processState == ProcessState.AwaitingFunding, "Process state is not 'AwaitingFunding'."); _; - } - modifier onlyWhenProcessFundedAndTradeActive() { - require(processState == ProcessState.Funded && tradeState == TradeState.Active, "Process state is not 'Funded' or Trade is not 'Active'."); _; - } - modifier onlyWhenProcessValuationAndSettlement() { - require(processState == ProcessState.ValuationAndSettlement, "Process state is not 'ValuationAndSettlement'."); _; - } - TradeState private tradeState; - ProcessState private processState; - - address public party1; - address public party2; - address private immutable receivingPartyAddress; // Determine the receiver: Positive values are consider to be received by receivingPartyAddress. Negative values are received by the other counterparty. - - /* - * liquidityToken holds: - * - funding account of party1 - * - funding account of party2 - * - account for SDC (sum - this is split among parties by sdcBalances) - */ - IERC20 private liquidityToken; - - string private tradeID; - string private tradeData; - string private lastSettlementData; - - mapping(address => MarginRequirement) private marginRequirements; // Storage of M and P per counterparty address - mapping(uint256 => address) private pendingRequests; // Stores open request hashes for several requests: initiation, update and termination - - mapping(address => int256) private sdcBalances; // internal book-keeping: needed to track what part of the gross token balance is held for each party - - - bool private mutuallyTerminated = false; - - constructor( - address counterparty1, - address counterparty2, - address receivingParty, - address tokenAddress, - uint256 initialMarginRequirement, - uint256 initalTerminationFee - ) { - party1 = counterparty1; - party2 = counterparty2; - receivingPartyAddress = receivingParty; - liquidityToken = IERC20(tokenAddress); - tradeState = TradeState.Inactive; - processState = ProcessState.Idle; - marginRequirements[party1] = MarginRequirement(int256(initialMarginRequirement), int256(initalTerminationFee)); - marginRequirements[party2] = MarginRequirement(int256(initialMarginRequirement), int256(initalTerminationFee)); - sdcBalances[party1] = 0; - sdcBalances[party2] = 0; - } - - /* - * generates a hash from tradeData and generates a map entry in openRequests - * emits a TradeIncepted - * can be called only when TradeState = Incepted - */ - function inceptTrade(string memory _tradeData, string memory _initialSettlementData, int256 _upfrontPayment) external override onlyCounterparty onlyWhenTradeInactive - { - processState = ProcessState.Initiation; - tradeState = TradeState.Incepted; // Set TradeState to Incepted - - _upfrontPayment; // To silence warning... TODO: Implement new feature. - - uint256 hash = uint256(keccak256(abi.encode(_tradeData, _initialSettlementData))); - pendingRequests[hash] = msg.sender; - tradeID = Strings.toString(hash); - tradeData = _tradeData; // Set trade data to enable querying already in inception state - lastSettlementData = _initialSettlementData; // Store settlement data to make them available for confirming party - - emit TradeIncepted(msg.sender, tradeID, _tradeData); - } - - /* - * generates a hash from tradeData and checks whether an open request can be found by the opposite party - * if so, data are stored and open request is deleted - * emits a TradeConfirmed - * can be called only when TradeState = Incepted - */ - function confirmTrade(string memory _tradeData, string memory _initialSettlementData) external override onlyCounterparty onlyWhenTradeIncepted - { - address pendingRequestParty = msg.sender == party1 ? party2 : party1; - uint256 tradeIDConf = uint256(keccak256(abi.encode(_tradeData, _initialSettlementData))); - require(pendingRequests[tradeIDConf] == pendingRequestParty, "Confirmation fails due to inconsistent trade data or wrong party address"); - delete pendingRequests[tradeIDConf]; // Delete Pending Request - - tradeState = TradeState.Confirmed; - emit TradeConfirmed(msg.sender, tradeID); - - // Pre-Conditions - if(_lockTerminationFees()) { - tradeState = TradeState.Active; - emit TradeActivated(tradeID); - - processState = ProcessState.AwaitingFunding; - emit ProcessAwaitingFunding(); - } - } - - /** - * Check sufficient balances and lock Termination Fees otherwise trade does not get activated - */ - function _lockTerminationFees() internal returns(bool) { - bool isAvailableParty1 = (liquidityToken.balanceOf(party1) >= uint(marginRequirements[party1].terminationFee)) && (liquidityToken.allowance(party1,address(this)) >= uint(marginRequirements[party1].terminationFee)); - bool isAvailableParty2 = (liquidityToken.balanceOf(party2) >= uint(marginRequirements[party2].terminationFee)) && (liquidityToken.allowance(party2,address(this)) >= uint(marginRequirements[party2].terminationFee)); - if (isAvailableParty1 && isAvailableParty2){ - liquidityToken.transferFrom(party1, address(this), uint(marginRequirements[party1].terminationFee)); // transfer termination fee party1 to sdc - liquidityToken.transferFrom(party2, address(this), uint(marginRequirements[party2].terminationFee)); // transfer termination fee party2 to sdc - adjustSDCBalances(marginRequirements[party1].terminationFee, marginRequirements[party2].terminationFee); // Update internal balances - return true; - } - else{ - tradeState == TradeState.Inactive; - processState = ProcessState.Idle; - emit TradeTerminated("Termination Fee could not be locked."); - return false; - } - } - - /* - * Failsafe: Free up accounts upon termination - */ - function _processTermination() internal { - liquidityToken.transfer(party1, uint256(sdcBalances[party1])); - liquidityToken.transfer(party2, uint256(sdcBalances[party2])); - - processState = ProcessState.Idle; - tradeState = TradeState.Inactive; - } - - /* - * Settlement Cycle - */ - - /* - * Send an Lock Request Event only when Process State = Funding - * Puts Process state to Margin Account Check - * can be called only when ProcessState = AwaitingFunding - */ - function initiatePrefunding() external override onlyWhenProcessAwaitingFunding { - processState = ProcessState.Funding; - - uint256 balanceParty1 = liquidityToken.balanceOf(party1); - uint256 balanceParty2 = liquidityToken.balanceOf(party2); - - /* Calculate gap amount for each party, i.e. residual between buffer and termination fee and actual balance */ - // max(M+P - sdcBalance,0) - uint gapAmountParty1 = marginRequirements[party1].buffer + marginRequirements[party1].terminationFee - sdcBalances[party1] > 0 ? uint(marginRequirements[party1].buffer + marginRequirements[party1].terminationFee - sdcBalances[party1]) : 0; - uint gapAmountParty2 = marginRequirements[party2].buffer + marginRequirements[party2].terminationFee - sdcBalances[party2] > 0 ? uint(marginRequirements[party2].buffer + marginRequirements[party2].terminationFee - sdcBalances[party2]) : 0; - - /* Good case: Balances are sufficient and token has enough approval */ - if ( (balanceParty1 >= gapAmountParty1 && liquidityToken.allowance(party1,address(this)) >= gapAmountParty1) && - (balanceParty2 >= gapAmountParty2 && liquidityToken.allowance(party2,address(this)) >= gapAmountParty2) ) { - liquidityToken.transferFrom(party1, address(this), gapAmountParty1); // Transfer of GapAmount to sdc contract - liquidityToken.transferFrom(party2, address(this), gapAmountParty2); // Transfer of GapAmount to sdc contract - processState = ProcessState.Funded; - adjustSDCBalances(int(gapAmountParty1),int(gapAmountParty2)); // Update internal balances - emit ProcessFunded(); - } - /* Party 1 - Bad case: Balances are insufficient or token has not enough approval */ - else if ( (balanceParty1 < gapAmountParty1 || liquidityToken.allowance(party1,address(this)) < gapAmountParty1) && - (balanceParty2 >= gapAmountParty2 && liquidityToken.allowance(party2,address(this)) >= gapAmountParty2) ) { - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - - adjustSDCBalances(-marginRequirements[party1].terminationFee,marginRequirements[party1].terminationFee); // Update internal balances - - _processTermination(); // Release all buffers - emit TradeTerminated("Termination caused by party1 due to insufficient prefunding"); - } - /* Party 2 - Bad case: Balances are insufficient or token has not enough approval */ - else if ( (balanceParty1 >= gapAmountParty1 && liquidityToken.allowance(party1,address(this)) >= gapAmountParty1) && - (balanceParty2 < gapAmountParty2 || liquidityToken.allowance(party2,address(this)) < gapAmountParty2) ) { - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - - adjustSDCBalances(marginRequirements[party2].terminationFee,-marginRequirements[party2].terminationFee); // Update internal balances - - _processTermination(); // Release all buffers - emit TradeTerminated("Termination caused by party2 due to insufficient prefunding"); - } - /* Both parties fail: Cross Transfer of Termination Fee */ - else { - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - // if ( (balanceParty1 < gapAmountParty1 || liquidityToken.allowance(party1,address(this)) < gapAmountParty1) && (balanceParty2 < gapAmountParty2 || liquidityToken.allowance(party2,address(this)) < gapAmountParty2) ) { tradeState = TradeState.Terminated; - adjustSDCBalances(marginRequirements[party2].terminationFee-marginRequirements[party1].terminationFee,marginRequirements[party1].terminationFee-marginRequirements[party2].terminationFee); // Update internal balances: Cross Booking of termination fee - - _processTermination(); // Release all buffers - emit TradeTerminated("Termination caused by both parties due to insufficient prefunding"); - } - } - - /* - * Settlement can be initiated when margin accounts are locked, a valuation request event is emitted containing tradeData and valuationViewParty - * Changes Process State to Valuation&Settlement - * can be called only when ProcessState = Funded and TradeState = Active - */ - function initiateSettlement() external override onlyCounterparty onlyWhenProcessFundedAndTradeActive - { - processState = ProcessState.ValuationAndSettlement; - emit ProcessSettlementRequest(tradeData, lastSettlementData); - } - - /* - * Performs a settelement only when processState is ValuationAndSettlement - * Puts process state to "inTransfer" - * Checks Settlement amount according to valuationViewParty: If SettlementAmount is > 0, valuationViewParty receives - * can be called only when ProcessState = ValuationAndSettlement - */ - function performSettlement(int256 settlementAmount, string memory settlementData) onlyWhenProcessValuationAndSettlement external override - { - lastSettlementData = settlementData; - address receivingParty = settlementAmount > 0 ? receivingPartyAddress : other(receivingPartyAddress); - address payingParty = other(receivingParty); - - bool noTermination = abs(settlementAmount) <= marginRequirements[payingParty].buffer; - int256 transferAmount = (noTermination == true) ? abs(settlementAmount) : marginRequirements[payingParty].buffer + marginRequirements[payingParty].terminationFee; // Override with Buffer and Termination Fee: Max Transfer - - if(receivingParty == party1) // Adjust internal Balances, only debit is booked on sdc balance as receiving party obtains transfer amount directly from sdc - adjustSDCBalances(0, -transferAmount); - else - adjustSDCBalances(-transferAmount, 0); - - liquidityToken.transfer(receivingParty, uint256(transferAmount)); // SDC contract performs transfer to receiving party - - if (noTermination) { // Regular Settlement - emit ProcessSettled(); - processState = ProcessState.AwaitingFunding; // Set ProcessState to 'AwaitingFunding' - } else { // Termination Event, buffer not sufficient, transfer margin buffer and termination fee and process termination - tradeState = TradeState.Terminated; - processState = ProcessState.InTermination; - _processTermination(); // Transfer all locked amounts - emit TradeTerminated("Termination due to margin buffer exceedance"); - } - - if (mutuallyTerminated) { // Both counterparties agreed on a premature termination - processState = ProcessState.InTermination; - _processTermination(); - } - } - - /* - * End of Cycle - */ - - /* - * Can be called by a party for mutual termination - * Hash is generated an entry is put into pendingRequests - * TerminationRequest is emitted - * can be called only when ProcessState = Funded and TradeState = Active - */ - function requestTradeTermination(string memory _tradeID) external override onlyCounterparty onlyWhenProcessFundedAndTradeActive - { - require(keccak256(abi.encodePacked(tradeID)) == keccak256(abi.encodePacked(_tradeID)), "Trade ID mismatch"); - uint256 hash = uint256(keccak256(abi.encode(_tradeID, "terminate"))); - pendingRequests[hash] = msg.sender; - emit TradeTerminationRequest(msg.sender, _tradeID); - } - - /* - - * Same pattern as for initiation - * confirming party generates same hash, looks into pendingRequests, if entry is found with correct address, tradeState is put to terminated - * can be called only when ProcessState = Funded and TradeState = Active - */ - function confirmTradeTermination(string memory tradeId) external override onlyCounterparty onlyWhenProcessFundedAndTradeActive - { - address pendingRequestParty = msg.sender == party1 ? party2 : party1; - uint256 hashConfirm = uint256(keccak256(abi.encode(tradeId, "terminate"))); - require(pendingRequests[hashConfirm] == pendingRequestParty, "Confirmation of termination failed due to wrong party or missing request"); - delete pendingRequests[hashConfirm]; - mutuallyTerminated = true; - emit TradeTerminationConfirmed(msg.sender, tradeID); - } - - function adjustSDCBalances(int256 adjustmentAmountParty1, int256 adjustmentAmountParty2) internal { - if (adjustmentAmountParty1 < 0) - require(sdcBalances[party1] >= adjustmentAmountParty1, "SDC Balance Adjustment fails for Party1"); - if (adjustmentAmountParty2 < 0) - require(sdcBalances[party2] >= adjustmentAmountParty2, "SDC Balance Adjustment fails for Party2"); - sdcBalances[party1] = sdcBalances[party1] + adjustmentAmountParty1; - sdcBalances[party2] = sdcBalances[party2] + adjustmentAmountParty2; - } - - /* - * Utilities - */ - - /** - * Absolute value of an integer - */ - function abs(int x) private pure returns (int) { - return x >= 0 ? x : -x; - } - - /** - * Other party - */ - function other(address party) private view returns (address) { - return (party == party1 ? party2 : party1); - } - - function getTokenAddress() public view returns(address) { - return address(liquidityToken); - } - - function getTradeID() public view returns (string memory) { - return tradeID; - } - - function getTradeData() public view returns (string memory) { - return tradeData; - } - - - function getTradeState() public view returns (TradeState) { - return tradeState; - } - - function getProcessState() public view returns (ProcessState) { - return processState; - } - - function getOwnSdcBalance() public view returns (int256) { - return sdcBalances[msg.sender]; - } - - /**END OF FUNCTIONS WHICH ARE ONLY USED FOR TESTING PURPOSES */ -} \ No newline at end of file diff --git a/assets/eip-6123/contracts/SDCToken.sol b/assets/eip-6123/contracts/SDCToken.sol deleted file mode 100644 index 0e39df3e83613a..00000000000000 --- a/assets/eip-6123/contracts/SDCToken.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract SDCToken is ERC20{ - constructor() ERC20("SDCToken", "SDCT") { - - } - - function mint(address to, uint256 amount) public { - _mint(to, amount); - } -} \ No newline at end of file diff --git a/assets/eip-6123/doc/sample-tradedata-filestructure.xml b/assets/eip-6123/doc/sample-tradedata-filestructure.xml deleted file mode 100644 index c557553cb293c8..00000000000000 --- a/assets/eip-6123/doc/sample-tradedata-filestructure.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - net.finmath - finmath-smart-derivative-contract - 0.1.8 - - - - - Counterparty 1 - party1 - - constant - 10000.0 - - - constant - 1000.0 - -
0x...
-
- - Counterparty 2 - party2 - - constant - 10000.0 - - - constant - 1000.0 - -
0x...
-
-
- - - 2011-12-13T10:15:30 - - - daily - 17:00 - - - xyz - - symbol1 - symbol2 - ... - - - - - party1 - - - - - - - - - CP1 - - - - CP2 - - 2022-12-13 - - - - - - -
\ No newline at end of file diff --git a/assets/eip-6123/doc/sdc_livecycle_sequence_diagram.png b/assets/eip-6123/doc/sdc_livecycle_sequence_diagram.png deleted file mode 100644 index 253bdfe1f8ad04..00000000000000 Binary files a/assets/eip-6123/doc/sdc_livecycle_sequence_diagram.png and /dev/null differ diff --git a/assets/eip-6123/doc/sdc_trade_and_process_states.png b/assets/eip-6123/doc/sdc_trade_and_process_states.png deleted file mode 100644 index f9e1a2246cadc9..00000000000000 Binary files a/assets/eip-6123/doc/sdc_trade_and_process_states.png and /dev/null differ diff --git a/assets/eip-6123/test/SDC.js b/assets/eip-6123/test/SDC.js deleted file mode 100644 index 75049c9c7314b3..00000000000000 --- a/assets/eip-6123/test/SDC.js +++ /dev/null @@ -1,128 +0,0 @@ -const { ethers } = require("hardhat"); -const { expect } = require("chai"); -const AbiCoder = ethers.utils.AbiCoder; -const Keccak256 = ethers.utils.keccak256; - -describe("Livecycle Unit-Tests for Smart Derivative Contract", () => { - - // Define objects for TradeState enum, since solidity enums cannot provide their member names... - const TradeState = { - Inactive: 0, - Incepted: 1, - Confirmed: 2, - Active: 3, - Terminated: 4, - }; - - const abiCoder = new AbiCoder(); - const trade_data = "here are the trade specification { - const [_tokenManager, _counterparty1, _counterparty2] = await ethers.getSigners(); - tokenManager = _tokenManager; - counterparty1 = _counterparty1; - counterparty2 = _counterparty2; - const ERC20Factory = await ethers.getContractFactory("SDCToken"); - const SDCFactory = await ethers.getContractFactory("SDC"); - token = await ERC20Factory.deploy(); - await token.deployed(); - sdc = await SDCFactory.deploy(counterparty1.address, counterparty2.address,counterparty1.address, token.address,marginBufferAmount,terminationFee); - await sdc.deployed(); - console.log("SDC Address: %s", sdc.address); - }); - - it("Initial minting and approvals for SDC", async () => { - await token.connect(counterparty1).mint(counterparty1.address,initialLiquidityBalance); - await token.connect(counterparty2).mint(counterparty2.address,initialLiquidityBalance); - await token.connect(counterparty1).approve(sdc.address,terminationFee+marginBufferAmount); - await token.connect(counterparty2).approve(sdc.address,terminationFee+marginBufferAmount); - let allowanceSDCParty1 = await token.connect(counterparty1).allowance(counterparty1.address, sdc.address); - let allowanceSDCParty2 = await token.connect(counterparty2).allowance(counterparty2.address, sdc.address); - await expect(allowanceSDCParty1).equal(terminationFee+marginBufferAmount); - }); - - it("Counterparty1 incepts a trade", async () => { - const incept_call = await sdc.connect(counterparty1).inceptTrade(trade_data, "initialMarketData", 0); - let tradeid = await sdc.connect(counterparty1).getTradeID(); - //console.log("TradeId: %s", tradeid); - await expect(incept_call).to.emit(sdc, "TradeIncepted").withArgs(counterparty1.address, tradeid, trade_data); - let trade_state = await sdc.connect(counterparty1).getTradeState(); - await expect(trade_state).equal(TradeState.Incepted); - }); - - - it("Counterparty2 confirms a trade", async () => { - const confirm_call = await sdc.connect(counterparty2).confirmTrade(trade_data,"initialMarketData"); - //console.log("TradeId: %s", await sdc.callStatic.getTradeState()); - let balanceSDC = await token.connect(counterparty2).balanceOf(sdc.address); - await expect(confirm_call).to.emit(sdc, "TradeConfirmed"); - await expect(balanceSDC).equal(2*terminationFee); - let trade_state = await sdc.connect(counterparty1).getTradeState(); - await expect(trade_state).equal(TradeState.Active); - }); - - it("Processing first prefunding phase", async () => { - const call = await sdc.connect(counterparty2).initiatePrefunding(); - let balanceSDC = await token.connect(counterparty2).balanceOf(sdc.address); - let balanceCP2 = await token.connect(counterparty2).balanceOf(counterparty2.address); - await expect(balanceSDC).equal(2*(terminationFee+marginBufferAmount)); - await expect(balanceCP2).equal(initialLiquidityBalance-(terminationFee+marginBufferAmount)); - await expect(call).to.emit(sdc, "ProcessFunded"); - }); - - it("Initiate and perform first successful settlement in favour to counterparty 1", async () => { - const callInitSettlement = await sdc.connect(counterparty2).initiateSettlement(); - await expect(callInitSettlement).to.emit(sdc, "ProcessSettlementRequest"); - let balanceCP1 = parseInt(await token.connect(counterparty1).balanceOf(counterparty1.address)); - let balanceCP2 = parseInt(await token.connect(counterparty2).balanceOf(counterparty1.address)); - const callPerformSettlement = await sdc.connect(counterparty2).performSettlement(settlementAmount1,"settlementData"); - await expect(callPerformSettlement).to.emit(sdc, "ProcessSettled"); - let balanceSDC_afterSettlement = await token.connect(counterparty2).balanceOf(sdc.address); - let balanceCP1_afterSettlement = await token.connect(counterparty1).balanceOf(counterparty1.address); - let balanceCP2_afterSettlement = await token.connect(counterparty2).balanceOf(counterparty2.address); - await expect(balanceSDC_afterSettlement).equal(2*(terminationFee+marginBufferAmount)-settlementAmount1); // SDC balance less settlement - await expect(balanceCP1_afterSettlement).equal(balanceCP1+settlementAmount1); // settlement in favour to CP1 - await expect(balanceCP2_afterSettlement).equal(balanceCP2); // CP2 balance is not touched as transfer is booked from SDC balance - }); - - it("Process successfully second prefunding phase successful ", async () => { - await token.connect(counterparty2).approve(sdc.address,settlementAmount1); // CP2 increases allowance - const call = await sdc.connect(counterparty1).initiatePrefunding(); //Prefunding: SDC transfers missing gap amount from CP2 - let balanceSDC = await token.connect(counterparty2).balanceOf(sdc.address); - let balanceCP2 = await token.connect(counterparty2).balanceOf(counterparty2.address); - await expect(balanceSDC).equal(2*(terminationFee+marginBufferAmount)); - await expect(balanceCP2).equal(initialLiquidityBalance-(terminationFee+marginBufferAmount)-settlementAmount1); - await expect(call).to.emit(sdc, "ProcessFunded"); - }); - - - it("Second settlement fails due to high transfer amount in favour to counteparty 2 - Trade terminates", async () => { - const callInitSettlement = await sdc.connect(counterparty2).initiateSettlement(); - await expect(callInitSettlement).to.emit(sdc, "ProcessSettlementRequest"); - const callPerformSettlement = await sdc.connect(counterparty2).performSettlement(settlementAmount2,"settlementData"); - await expect(callPerformSettlement).to.emit(sdc, "TradeTerminated"); - - let balanceSDC = parseInt(await token.connect(counterparty2).balanceOf(sdc.address)); - let balanceCP1 = await token.connect(counterparty1).balanceOf(counterparty1.address); - let balanceCP2 = await token.connect(counterparty2).balanceOf(counterparty2.address); - let expectedBalanceCP1 = initialLiquidityBalance + settlementAmount1 - (marginBufferAmount + terminationFee); //CP1 received settlementAmount1 and paid margin buffer and termination fee - let expectedBalanceCP2 = initialLiquidityBalance - settlementAmount1 + (marginBufferAmount + terminationFee); //CP2 paid settlementAmount1 and receives margin buffer and termination fee - await expect(balanceCP1).equal(expectedBalanceCP1); - await expect(balanceCP2).equal(expectedBalanceCP2); - await expect(balanceSDC).equal(0); - }); - - - -}); \ No newline at end of file diff --git a/assets/eip-6150/contracts/ERC6150.sol b/assets/eip-6150/contracts/ERC6150.sol deleted file mode 100644 index 5dd0dc26ab6a2e..00000000000000 --- a/assets/eip-6150/contracts/ERC6150.sol +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./interfaces/IERC6150.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -abstract contract ERC6150 is ERC721, IERC6150 { - mapping(uint256 => uint256) private _parentOf; - mapping(uint256 => uint256[]) private _childrenOf; - mapping(uint256 => uint256) private _indexInChildrenArray; - - constructor( - string memory name_, - string memory symbol_ - ) ERC721(name_, symbol_) {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC721) returns (bool) { - return - interfaceId == type(IERC6150).interfaceId || - super.supportsInterface(interfaceId); - } - - function parentOf( - uint256 tokenId - ) public view virtual override returns (uint256 parentId) { - _requireMinted(tokenId); - parentId = _parentOf[tokenId]; - } - - function childrenOf( - uint256 tokenId - ) public view virtual override returns (uint256[] memory childrenIds) { - if (tokenId > 0) { - _requireMinted(tokenId); - } - childrenIds = _childrenOf[tokenId]; - } - - function isRoot( - uint256 tokenId - ) public view virtual override returns (bool) { - _requireMinted(tokenId); - return _parentOf[tokenId] == 0; - } - - function isLeaf( - uint256 tokenId - ) public view virtual override returns (bool) { - _requireMinted(tokenId); - return _childrenOf[tokenId].length == 0; - } - - function _getIndexInChildrenArray( - uint256 tokenId - ) internal view virtual returns (uint256) { - return _indexInChildrenArray[tokenId]; - } - - function _safeBatchMintWithParent( - address to, - uint256 parentId, - uint256[] memory tokenIds - ) internal virtual { - _safeBatchMintWithParent( - to, - parentId, - tokenIds, - new bytes[](tokenIds.length) - ); - } - - function _safeBatchMintWithParent( - address to, - uint256 parentId, - uint256[] memory tokenIds, - bytes[] memory datas - ) internal virtual { - require( - tokenIds.length == datas.length, - "ERC6150: tokenIds.length != datas.length" - ); - for (uint256 i = 0; i < tokenIds.length; i++) { - _safeMintWithParent(to, parentId, tokenIds[i], datas[i]); - } - } - - function _safeMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual { - _safeMintWithParent(to, parentId, tokenId, ""); - } - - function _safeMintWithParent( - address to, - uint256 parentId, - uint256 tokenId, - bytes memory data - ) internal virtual { - require(tokenId > 0, "ERC6150: tokenId is zero"); - if (parentId != 0) - require(_exists(parentId), "ERC6150: parentId doesn't exist"); - - _beforeMintWithParent(to, parentId, tokenId); - - _parentOf[tokenId] = parentId; - _indexInChildrenArray[tokenId] = _childrenOf[parentId].length; - _childrenOf[parentId].push(tokenId); - - _safeMint(to, tokenId, data); - emit Minted(msg.sender, to, parentId, tokenId); - - _afterMintWithParent(to, parentId, tokenId); - } - - function _safeBurn(uint256 tokenId) internal virtual { - require(_exists(tokenId), "ERC6150: tokenId doesn't exist"); - require(isLeaf(tokenId), "ERC6150: tokenId is not a leaf"); - - uint256 parent = _parentOf[tokenId]; - uint256 lastTokenIndex = _childrenOf[parent].length - 1; - uint256 targetTokenIndex = _indexInChildrenArray[tokenId]; - uint256 lastIndexToken = _childrenOf[parent][lastTokenIndex]; - if (lastTokenIndex > targetTokenIndex) { - _childrenOf[parent][targetTokenIndex] = lastIndexToken; - _indexInChildrenArray[lastIndexToken] = targetTokenIndex; - } - - delete _childrenOf[parent][lastIndexToken]; - delete _indexInChildrenArray[tokenId]; - delete _parentOf[tokenId]; - - _burn(tokenId); - } - - function _beforeMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual {} - - function _afterMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual {} -} diff --git a/assets/eip-6150/contracts/ERC6150AccessControl.sol b/assets/eip-6150/contracts/ERC6150AccessControl.sol deleted file mode 100644 index 97e43a01f9a089..00000000000000 --- a/assets/eip-6150/contracts/ERC6150AccessControl.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150AccessControl.sol"; - -abstract contract ERC6150AccessControl is ERC6150, IERC6150AccessControl { - mapping(address => mapping(uint256 => bool)) private _isAdminOf; - - function isAdminOf( - uint256 tokenId, - address account - ) public view virtual override returns (bool) { - return _isAdminOf[account][tokenId]; - } - - function canMintChildren( - uint256 parentId, - address account - ) public view virtual override returns (bool) { - return isAdminOf(parentId, account); - } - - function canBurnTokenByAccount( - uint256 tokenId, - address account - ) public view virtual override returns (bool) { - require(isLeaf(tokenId), "not a leaf token"); - return isAdminOf(tokenId, account); - } - - function _afterMintWithParent( - address to, - uint256 parentId, - uint256 tokenId - ) internal virtual override { - _isAdminOf[to][tokenId] = true; - } - - function _addAdmin(address admin, uint256 tokenId) internal virtual { - require(admin != address(0), "zero address"); - require(_exists(tokenId), "tokenId doesn't exist"); - _isAdminOf[admin][tokenId] = true; - } - - function _removeAdmin(address admin, uint256 tokenId) internal virtual { - require(_isAdminOf[admin][tokenId] == true, "not an admin"); - require(admin != ownerOf(tokenId), "cannot remove owner"); - _isAdminOf[admin][tokenId] = false; - } -} diff --git a/assets/eip-6150/contracts/ERC6150Burnable.sol b/assets/eip-6150/contracts/ERC6150Burnable.sol deleted file mode 100644 index aa0e3a9fe91b76..00000000000000 --- a/assets/eip-6150/contracts/ERC6150Burnable.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150Burnable.sol"; - -abstract contract ERC6150Burnable is ERC6150, IERC6150Burnable { - function safeBurn(uint256 tokenId) public virtual override { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "ERC6150Burnable: caller is neither token owner nor approved" - ); - _safeBurn(tokenId); - } - - function safeBatchBurn(uint256[] memory tokenIds) public virtual override { - for (uint256 i = 0; i < tokenIds.length; i++) { - safeBurn(tokenIds[i]); - } - } -} diff --git a/assets/eip-6150/contracts/ERC6150Enumerable.sol b/assets/eip-6150/contracts/ERC6150Enumerable.sol deleted file mode 100644 index 32a97511366430..00000000000000 --- a/assets/eip-6150/contracts/ERC6150Enumerable.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150Enumerable.sol"; - -abstract contract ERC6150Enumerable is ERC6150, IERC6150Enumerable { - function childrenCountOf( - uint256 parentId - ) external view virtual override returns (uint256) { - return childrenOf(parentId).length; - } - - function childOfParentByIndex( - uint256 parentId, - uint256 index - ) external view virtual override returns (uint256) { - uint256[] memory children = childrenOf(parentId); - return children[index]; - } - - function indexInChildrenEnumeration( - uint256 parentId, - uint256 tokenId - ) external view virtual override returns (uint256) { - require(parentOf(tokenId) == parentId, "wrong parent"); - return _getIndexInChildrenArray(tokenId); - } -} diff --git a/assets/eip-6150/contracts/ERC6150ParentTransferable.sol b/assets/eip-6150/contracts/ERC6150ParentTransferable.sol deleted file mode 100644 index cb552e87fdbad2..00000000000000 --- a/assets/eip-6150/contracts/ERC6150ParentTransferable.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC6150.sol"; -import "./interfaces/IERC6150ParentTransferable.sol"; - -abstract contract ERC6150ParentTransferable is - ERC6150, - IERC6150ParentTransferable -{ - function transferParent( - uint256 newParentId, - uint256 tokenId - ) public virtual override { - require( - _isApprovedOrOwner(_msgSender(), tokenId), - "ERC6150ParentTransferable: caller is not token owner nor approved" - ); - if (newParentId != 0) { - require( - _exists(newParentId), - "ERC6150ParentTransferable: newParentId doesn't exists" - ); - } - - address owner = ownerOf(tokenId); - uint256 oldParentId = parentOf(tokenId); - _safeBurn(tokenId); - _safeMintWithParent(owner, newParentId, tokenId); - emit ParentTransferred(tokenId, oldParentId, newParentId); - } - - function batchTransferParent( - uint256 newParentId, - uint256[] memory tokenIds - ) public virtual override { - for (uint256 i = 0; i < tokenIds.length; i++) { - transferParent(tokenIds[i], newParentId); - } - } -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150.sol b/assets/eip-6150/contracts/interfaces/IERC6150.sol deleted file mode 100644 index 7da6ed559a868d..00000000000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "IERC721.sol"; -import "IERC165.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0x897e2c73. - */ -interface IERC6150 is IERC721, IERC165 { - /** - * @notice Emitted when `tokenId` token under `parentId` is minted. - * @param minter The address of minter - * @param to The address received token - * @param parentId The id of parent token, if it's zero, it means minted `tokenId` is a root token. - * @param tokenId The id of minted token, required to be greater than zero - */ - event Minted( - address indexed minter, - address indexed to, - uint256 parentId, - uint256 tokenId - ); - - /** - * @notice Get the parent token of `tokenId` token. - * @param tokenId The child token - * @return parentId The Parent token found - */ - function parentOf(uint256 tokenId) external view returns (uint256 parentId); - - /** - * @notice Get the children tokens of `tokenId` token. - * @param tokenId The parent token - * @return childrenIds The array of children tokens - */ - function childrenOf( - uint256 tokenId - ) external view returns (uint256[] memory childrenIds); - - /** - * @notice Check the `tokenId` token if it is a root token. - * @param tokenId The token want to be checked - * @return Return `true` if it is a root token; if not, return `false` - */ - function isRoot(uint256 tokenId) external view returns (bool); - - /** - * @notice Check the `tokenId` token if it is a leaf token. - * @param tokenId The token want to be checked - * @return Return `true` if it is a leaf token; if not, return `false` - */ - function isLeaf(uint256 tokenId) external view returns (bool); -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150AccessControl.sol b/assets/eip-6150/contracts/interfaces/IERC6150AccessControl.sol deleted file mode 100644 index 661679d446dd08..00000000000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150AccessControl.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for access control - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0x1d04f0b3. - */ -interface IERC6150AccessControl is IERC6150 { - /** - * @notice Check the account whether a admin of `tokenId` token. - * @dev Each token can be set more than one admin. Admin have permission to do something to the token, like mint child token, - * or burn token, or transfer parentship. - * @param tokenId The specified token - * @param account The account to be checked - * @return If the account has admin permission, return true; otherwise, return false. - */ - function isAdminOf( - uint256 tokenId, - address account - ) external view returns (bool); - - /** - * @notice Check whether the specified parent token and account can mint children tokens - * @dev If the `parentId` is zero, check whether account can mint root nodes - * @param parentId The specified parent token to be checked - * @param account The specified account to be checked - * @return If the token and account has mint permission, return true; otherwise, return false. - */ - function canMintChildren( - uint256 parentId, - address account - ) external view returns (bool); - - /** - * @notice Check whether the specified token can be burnt by specified account - * @param tokenId The specified token to be checked - * @param account The specified account to be checked - * @return If the tokenId can be burnt by account, return true; otherwise, return false. - */ - function canBurnTokenByAccount( - uint256 tokenId, - address account - ) external view returns (bool); -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150Burnable.sol b/assets/eip-6150/contracts/interfaces/IERC6150Burnable.sol deleted file mode 100644 index 5cb8c9a302e0e9..00000000000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150Burnable.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for burnable - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0x4ac0aa46. - */ -interface IERC6150Burnable is IERC6150 { - /** - * @notice Burn the `tokenId` token. - * @dev Throws if `tokenId` is not a leaf token. - * Throws if `tokenId` is not a valid NFT. - * Throws if `owner` is not the owner of `tokenId` token. - * Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this token. - * @param tokenId The token to be burnt - */ - function safeBurn(uint256 tokenId) external; - - /** - * @notice Batch burn tokens. - * @dev Throws if one of `tokenIds` is not a leaf token. - * Throws if one of `tokenIds` is not a valid NFT. - * Throws if `owner` is not the owner of all `tokenIds` tokens. - * Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for all `tokenIds`. - * @param tokenIds The tokens to be burnt - */ - function safeBatchBurn(uint256[] memory tokenIds) external; -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150Enumerable.sol b/assets/eip-6150/contracts/interfaces/IERC6150Enumerable.sol deleted file mode 100644 index 030e4d9b3dbdb4..00000000000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150Enumerable.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; -import "IERC721Enumerable.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for enumerable - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0xba541a2e. - */ -interface IERC6150Enumerable is IERC6150, IERC721Enumerable { - /** - * @notice Get total amount of children tokens under `parentId` token. - * @dev If `parentId` is zero, it means get total amount of root tokens. - * @return The total amount of children tokens under `parentId` token. - */ - function childrenCountOf(uint256 parentId) external view returns (uint256); - - /** - * @notice Get the token at the specified index of all children tokens under `parentId` token. - * @dev If `parentId` is zero, it means get root token. - * @return The token ID at `index` of all chlidren tokens under `parentId` token. - */ - function childOfParentByIndex( - uint256 parentId, - uint256 index - ) external view returns (uint256); - - /** - * @notice Get the index position of specified token in the children enumeration under specified parent token. - * @dev Throws if the `tokenId` is not found in the children enumeration. - * If `parentId` is zero, means get root token index. - * @param parentId The parent token - * @param tokenId The specified token to be found - * @return The index position of `tokenId` found in the children enumeration - */ - function indexInChildrenEnumeration( - uint256 parentId, - uint256 tokenId - ) external view returns (uint256); -} diff --git a/assets/eip-6150/contracts/interfaces/IERC6150ParentTransferable.sol b/assets/eip-6150/contracts/interfaces/IERC6150ParentTransferable.sol deleted file mode 100644 index 2ecfa97f61dce0..00000000000000 --- a/assets/eip-6150/contracts/interfaces/IERC6150ParentTransferable.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC6150.sol"; - -/** - * @title ERC-6150 Hierarchical NFTs Token Standard, optional extension for parent transferable - * @dev See https://eips.ethereum.org/EIPS/eip-6150 - * Note: the ERC-165 identifier for this interface is 0xfa574808. - */ -interface IERC6150ParentTransferable is IERC6150 { - /** - * @notice Emitted when the parent of `tokenId` token changed. - * @param tokenId The token changed - * @param oldParentId Previous parent token - * @param newParentId New parent token - */ - event ParentTransferred( - uint256 tokenId, - uint256 oldParentId, - uint256 newParentId - ); - - /** - * @notice Transfer parentship of `tokenId` token to a new parent token - * @param newParentId New parent token id - * @param tokenId The token to be changed - */ - function transferParent(uint256 newParentId, uint256 tokenId) external; - - /** - * @notice Batch transfer parentship of `tokenIds` to a new parent token - * @param newParentId New parent token id - * @param tokenIds Array of token ids to be changed - */ - function batchTransferParent( - uint256 newParentId, - uint256[] memory tokenIds - ) external; -} diff --git a/assets/eip-6150/linux-hierarchy.png b/assets/eip-6150/linux-hierarchy.png deleted file mode 100644 index 60ecc1fc0ed2ad..00000000000000 Binary files a/assets/eip-6150/linux-hierarchy.png and /dev/null differ diff --git a/assets/eip-6150/website-hierarchy.png b/assets/eip-6150/website-hierarchy.png deleted file mode 100644 index 85215eccf6be5b..00000000000000 Binary files a/assets/eip-6150/website-hierarchy.png and /dev/null differ diff --git a/assets/eip-6220/contracts/Catalog.sol b/assets/eip-6220/contracts/Catalog.sol deleted file mode 100644 index 45d522ef8ba4fb..00000000000000 --- a/assets/eip-6220/contracts/Catalog.sol +++ /dev/null @@ -1,267 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "./ICatalog.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; - -error BadConfig(); -error IdZeroForbidden(); -error PartAlreadyExists(); -error PartDoesNotExist(); -error PartIsNotSlot(); -error ZeroLengthIdsPassed(); - -/** - * @title Catalog - * @author RMRK team - * @notice Catalog contract for RMRK equippable module. - */ -contract Catalog is ICatalog { - using Address for address; - - /** - * @notice Mapping of uint64 `partId` to Catalog `Part` struct - */ - mapping(uint64 => Part) private _parts; - - /** - * @notice Mapping of uint64 `partId` to boolean flag, indicating that a given `Part` can be equippable by any address - */ - mapping(uint64 => bool) private _isEquippableToAll; - - uint64[] private _partIds; - - string private _metadataURI; - string private _type; - - /** - * @notice Used to initialize the catalog. - * @param metadataURI Base metadata URI of the catalog - * @param type_ Type of catalog - */ - constructor(string memory metadataURI, string memory type_) { - _metadataURI = metadataURI; - _type = type_; - } - - /** - * @notice Used to limit execution of functions intended for the `Slot` parts to only execute when used with such - * parts. - * @dev Reverts execution of a function if the part with associated `partId` is uninitailized or is `Fixed`. - * @param partId ID of the part that we want the function to interact with - */ - modifier onlySlot(uint64 partId) { - _onlySlot(partId); - _; - } - - function _onlySlot(uint64 partId) private view { - ItemType itemType = _parts[partId].itemType; - if (itemType == ItemType.None) revert PartDoesNotExist(); - if (itemType == ItemType.Fixed) revert PartIsNotSlot(); - } - - /** - * @inheritdoc IERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - returns (bool) - { - return - interfaceId == type(IERC165).interfaceId || - interfaceId == type(ICatalog).interfaceId; - } - - /** - * @inheritdoc ICatalog - */ - function getMetadataURI() external view returns (string memory) { - return _metadataURI; - } - - /** - * @inheritdoc ICatalog - */ - function getType() external view returns (string memory) { - return _type; - } - - /** - * @notice Internal helper function that adds `Part` entries to storage. - * @dev Delegates to { _addPart } below. - * @param partIntake An array of `IntakeStruct` structs, consisting of `partId` and a nested `Part` struct - */ - function _addPartList(IntakeStruct[] calldata partIntake) internal { - uint256 len = partIntake.length; - for (uint256 i; i < len; ) { - _addPart(partIntake[i]); - unchecked { - ++i; - } - } - } - - /** - * @notice Internal function that adds a single `Part` to storage. - * @param partIntake `IntakeStruct` struct consisting of `partId` and a nested `Part` struct - * - */ - function _addPart(IntakeStruct calldata partIntake) internal { - uint64 partId = partIntake.partId; - Part memory part = partIntake.part; - - if (partId == uint64(0)) revert IdZeroForbidden(); - if (_parts[partId].itemType != ItemType.None) - revert PartAlreadyExists(); - if (part.itemType == ItemType.None) revert BadConfig(); - if (part.itemType == ItemType.Fixed && part.equippable.length != 0) - revert BadConfig(); - - _parts[partId] = part; - _partIds.push(partId); - - emit AddedPart( - partId, - part.itemType, - part.z, - part.equippable, - part.metadataURI - ); - } - - /** - * @notice Internal function used to add multiple `equippableAddresses` to a single catalog entry. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the `Part` that we are adding the equippable addresses to - * @param equippableAddresses An array of addresses that can be equipped into the `Part` associated with the `partId` - */ - function _addEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) internal onlySlot(partId) { - if (equippableAddresses.length <= 0) revert ZeroLengthIdsPassed(); - - uint256 len = equippableAddresses.length; - for (uint256 i; i < len; ) { - _parts[partId].equippable.push(equippableAddresses[i]); - unchecked { - ++i; - } - } - delete _isEquippableToAll[partId]; - - emit AddedEquippables(partId, equippableAddresses); - } - - /** - * @notice Internal function used to set the new list of `equippableAddresses`. - * @dev Overwrites existing `equippableAddresses`. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the `Part`s that we are overwiting the `equippableAddresses` for - * @param equippableAddresses A full array of addresses that can be equipped into this `Part` - */ - function _setEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) internal onlySlot(partId) { - if (equippableAddresses.length <= 0) revert ZeroLengthIdsPassed(); - _parts[partId].equippable = equippableAddresses; - delete _isEquippableToAll[partId]; - - emit SetEquippables(partId, equippableAddresses); - } - - /** - * @notice Internal function used to remove all of the `equippableAddresses` for a `Part` associated with the `partId`. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the part that we are clearing the `equippableAddresses` from - */ - function _resetEquippableAddresses(uint64 partId) - internal - onlySlot(partId) - { - delete _parts[partId].equippable; - delete _isEquippableToAll[partId]; - - emit SetEquippables(partId, new address[](0)); - } - - /** - * @notice Sets the isEquippableToAll flag to true, meaning that any collection may be equipped into the `Part` with this - * `partId`. - * @dev Can only be called on `Part`s of `Slot` type. - * @param partId ID of the `Part` that we are setting as equippable by any address - */ - function _setEquippableToAll(uint64 partId) internal onlySlot(partId) { - _isEquippableToAll[partId] = true; - emit SetEquippableToAll(partId); - } - - /** - * @inheritdoc ICatalog - */ - function checkIsEquippableToAll(uint64 partId) public view returns (bool) { - return _isEquippableToAll[partId]; - } - - /** - * @inheritdoc ICatalog - */ - function checkIsEquippable(uint64 partId, address targetAddress) - public - view - returns (bool) - { - // If this is equippable to all, we're good - bool isEquippable = _isEquippableToAll[partId]; - - // Otherwise, must check against each of the equippable for the part - if (!isEquippable && _parts[partId].itemType == ItemType.Slot) { - address[] memory equippable = _parts[partId].equippable; - uint256 len = equippable.length; - for (uint256 i; i < len; ) { - if (targetAddress == equippable[i]) { - isEquippable = true; - break; - } - unchecked { - ++i; - } - } - } - return isEquippable; - } - - /** - * @inheritdoc ICatalog - */ - function getPart(uint64 partId) public view returns (Part memory) { - return (_parts[partId]); - } - - /** - * @inheritdoc ICatalog - */ - function getParts(uint64[] calldata partIds) - public - view - returns (Part[] memory) - { - uint256 numParts = partIds.length; - Part[] memory parts = new Part[](numParts); - - for (uint256 i; i < numParts; ) { - uint64 partId = partIds[i]; - parts[i] = _parts[partId]; - unchecked { - ++i; - } - } - - return parts; - } -} diff --git a/assets/eip-6220/contracts/EquippableToken.sol b/assets/eip-6220/contracts/EquippableToken.sol deleted file mode 100644 index e880f233afd3f3..00000000000000 --- a/assets/eip-6220/contracts/EquippableToken.sol +++ /dev/null @@ -1,2402 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//Generally all interactions should propagate downstream - -pragma solidity ^0.8.16; - -import "./ICatalog.sol"; -import "./IERC6220.sol"; -import "./IERC6059.sol"; -import "./library/EquippableLib.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -error ApprovalForAssetsToCurrentOwner(); -error ApproveForAssetsCallerIsNotOwnerNorApprovedForAll(); -error AssetAlreadyExists(); -error BadPriorityListLength(); -error CatalogRequiredForParts(); -error ChildAlreadyExists(); -error ChildIndexOutOfRange(); -error EquippableEquipNotAllowedByCatalog(); -error ERC721AddressZeroIsNotaValidOwner(); -error ERC721ApprovalToCurrentOwner(); -error ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); -error ERC721ApproveToCaller(); -error ERC721InvalidTokenId(); -error ERC721MintToTheZeroAddress(); -error ERC721NotApprovedOrOwner(); -error ERC721TokenAlreadyMinted(); -error ERC721TransferFromIncorrectOwner(); -error ERC721TransferToNonReceiverImplementer(); -error ERC721TransferToTheZeroAddress(); -error IdZeroForbidden(); -error IndexOutOfRange(); -error IsNotContract(); -error MaxPendingAssetsReached(); -error MaxPendingChildrenReached(); -error MaxRecursiveBurnsReached(address childContract, uint256 childId); -error MintToNonNestableImplementer(); -error MustUnequipFirst(); -error NestableTooDeep(); -error NestableTransferToDescendant(); -error NestableTransferToNonNestableImplementer(); -error NestableTransferToSelf(); -error NoAssetMatchingId(); -error NotApprovedForAssetsOrOwner(); -error NotApprovedOrDirectOwner(); -error NotEquipped(); -error PendingChildIndexOutOfRange(); -error SlotAlreadyUsed(); -error TargetAssetCannotReceiveSlot(); -error TokenCannotBeEquippedWithAssetIntoSlot(); -error TokenDoesNotHaveAsset(); -error UnexpectedAssetId(); -error UnexpectedChildId(); -error UnexpectedNumberOfAssets(); -error UnexpectedNumberOfChildren(); - -/** - * @title EquippableToken - * @author RMRK team - * @notice Smart contract of the Equippable module. - */ -contract EquippableToken is - Context, - IERC165, - IERC721, - IERC6059, - IERC6220 -{ - using Address for address; - using EquippableLib for uint64[]; - - // ----------------- ERC721 ------------- - - // Mapping owner address to token count - mapping(address => uint256) private _balances; - - // Mapping from token ID to approver address to approved address - // The approver is necessary so approvals are invalidated for nested children on transfer - // WARNING: If a child NFT returns to a previous root owner, old permissions would be active again - mapping(uint256 => mapping(address => address)) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // ----------------- MULTIASSETS ------------- - - /// Mapping of uint64 Ids to asset metadata - mapping(uint64 => string) private _assets; - - /// Mapping of tokenId to new asset, to asset to be replaced - mapping(uint256 => mapping(uint64 => uint64)) private _assetReplacements; - - /// Mapping of tokenId to an array of active assets - /// @dev Active recurses is unbounded, getting all would reach gas limit at around 30k items - /// so we leave this as internal in case a custom implementation needs to implement pagination - mapping(uint256 => uint64[]) internal _activeAssets; - - /// Mapping of tokenId to an array of pending assets - mapping(uint256 => uint64[]) internal _pendingAssets; - - /// Mapping of tokenId to an array of priorities for active assets - mapping(uint256 => uint16[]) internal _activeAssetPriorities; - - /// Mapping of tokenId to assetId to whether the token has this asset assigned - mapping(uint256 => mapping(uint64 => bool)) private _tokenAssets; - - /// Mapping from owner to operator approvals for assets - mapping(address => mapping(address => bool)) - private _operatorApprovalsForAssets; - - /** - * @notice Mapping from token ID to approver address to approved address for assets. - * @dev The approver is necessary so approvals are invalidated for nested children on transfer. - * @dev WARNING: If a child NFT returns the original root owner, old permissions would be active again. - */ - mapping(uint256 => mapping(address => address)) - private _tokenApprovalsForAssets; - - // ------------------- NESTABLE -------------- - - uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100; - - // Mapping from token ID to DirectOwner struct - mapping(uint256 => DirectOwner) private _directOwners; - - // Mapping of tokenId to array of active children structs - mapping(uint256 => Child[]) private _activeChildren; - - // Mapping of tokenId to array of pending children structs - mapping(uint256 => Child[]) private _pendingChildren; - - // Mapping of child token address to child token ID to whether they are pending or active on any token - // We might have a first extra mapping from token ID, but since the same child cannot be nested into multiple tokens - // we can strip it for size/gas savings. - mapping(address => mapping(uint256 => uint256)) private _childIsInActive; - - // ------------------- EQUIPPABLE -------------- - - /// Mapping of uint64 asset ID to corresponding catalog address. - mapping(uint64 => address) private _catalogAddresses; - /// Mapping of uint64 ID to asset object. - mapping(uint64 => uint64) private _equippableGroupIds; - /// Mapping of assetId to catalog parts applicable to this asset, both fixed and slot - mapping(uint64 => uint64[]) private _partIds; - - /// Mapping of token ID to catalog address to slot part ID to equipment information. Used to compose an NFT. - mapping(uint256 => mapping(address => mapping(uint64 => Equipment))) - private _equipments; - - /// Mapping of token ID to child (nestable) address to child ID to count of equipped items. Used to check if equipped. - mapping(uint256 => mapping(address => mapping(uint256 => uint8))) - private _equipCountPerChild; - - /// Mapping of `equippableGroupId` to parent contract address and valid `slotId`. - mapping(uint64 => mapping(address => uint64)) private _validParentSlots; - - // -------------------------- MODIFIERS ---------------------------- - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrOwner(uint256 tokenId) { - _onlyApprovedOrOwner(tokenId); - _; - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or is its direct owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrDirectOwner(uint256 tokenId) { - _onlyApprovedOrDirectOwner(tokenId); - _; - } - - /** - * @notice Used to ensure that the caller is either the owner of the given token or approved to manage the token's assets - * of the owner. - * @dev If that is not the case, the execution of the function will be reverted. - * @param tokenId ID of the token that we are checking - */ - modifier onlyApprovedForAssetsOrOwner(uint256 tokenId) { - _onlyApprovedForAssetsOrOwner(tokenId); - _; - } - - // --------------------- ERC721 GETTERS --------------------- - - /** - * @notice Used to retrieve the root owner of the given token. - * @dev Root owner is always the externally owned account. - * @dev If the given token is owned by another token, it will recursively query the parent tokens until reaching the - * root owner. - * @param tokenId ID of the token for which the root owner is being retrieved - * @return address Address of the root owner of the given token - */ - function ownerOf( - uint256 tokenId - ) public view virtual override(IERC6059, IERC721) returns (address) { - (address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf( - tokenId - ); - if (isNft) { - owner = IERC6059(owner).ownerOf(ownerTokenId); - } - return owner; - } - - /** - * @inheritdoc IERC165 - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC165).interfaceId || - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC5773).interfaceId || - interfaceId == type(IERC6059).interfaceId || - interfaceId == type(IERC6220).interfaceId; - } - - /** - * @inheritdoc IERC721 - */ - function balanceOf(address owner) public view virtual returns (uint256) { - if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner(); - return _balances[owner]; - } - - /** - * @inheritdoc IERC721 - */ - function getApproved( - uint256 tokenId - ) public view virtual returns (address) { - _requireMinted(tokenId); - - return _tokenApprovals[tokenId][ownerOf(tokenId)]; - } - - /** - * @inheritdoc IERC721 - */ - function isApprovedForAll( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovals[owner][operator]; - } - - // --------------------- ERC721 SETTERS --------------------- - - /** - * @inheritdoc IERC721 - */ - function approve(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - if (to == owner) revert ERC721ApprovalToCurrentOwner(); - - if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) - revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); - - _approve(to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function setApprovalForAll(address operator, bool approved) public virtual { - if (_msgSender() == operator) revert ERC721ApproveToCaller(); - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @notice Used to burn a given token. - * @param tokenId ID of the token to burn - */ - function burn(uint256 tokenId) public virtual { - burn(tokenId, 0); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) public virtual onlyApprovedOrDirectOwner(tokenId) returns (uint256) { - return _burn(tokenId, maxChildrenBurns); - } - - /** - * @inheritdoc IERC721 - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _transfer(from, to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual { - safeTransferFrom(from, to, tokenId, ""); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _safeTransfer(from, to, tokenId, data); - } - - // --------------------- ERC721 INTERNAL --------------------- - - /** - * @notice Used to grant an approval to manage a given token. - * @dev Emits an {Approval} event. - * @param to Address to which the approval is being granted - * @param tokenId ID of the token for which the approval is being granted - */ - function _approve(address to, uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - _tokenApprovals[tokenId][owner] = to; - emit Approval(owner, to, tokenId); - } - - /** - * @notice Used to update the owner of the token and clear the approvals associated with the previous owner. - * @dev The `destinationId` should equal `0` if the new owner is an externally owned account. - * @param tokenId ID of the token being updated - * @param destinationId ID of the token to receive the given token - * @param to Address of account to receive the token - * @param isNft A boolean value signifying whether the new owner is a token (`true`) or externally owned account - * (`false`) - */ - function _updateOwnerAndClearApprovals( - uint256 tokenId, - uint256 destinationId, - address to, - bool isNft - ) internal { - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId, - isNft: isNft - }); - - // Clear approvals from the previous owner - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - } - - /** - * @notice Used to enforce that the given token has been minted. - * @dev Reverts if the `tokenId` has not been minted yet. - * @dev The validation checks whether the owner of a given token is a `0x0` address and considers it not minted if - * it is. This means that both tokens that haven't been minted yet as well as the ones that have already been - * burned will cause the transaction to be reverted. - * @param tokenId ID of the token to check - */ - function _requireMinted(uint256 tokenId) internal view virtual { - if (!_exists(tokenId)) revert ERC721InvalidTokenId(); - } - - /** - * @notice Used to check whether the given token exists. - * @dev Tokens start existing when they are minted (`_mint`) and stop existing when they are burned (`_burn`). - * @param tokenId ID of the token being checked - * @return bool The boolean value signifying whether the token exists - */ - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _directOwners[tokenId].ownerAddress != address(0); - } - - /** - * @notice Used to invoke {IERC721Receiver-onERC721Received} on a target address. - * @dev The call is not executed if the target address is not a contract. - * @param from Address representing the previous owner of the given token - * @param to Yarget address that will receive the tokens - * @param tokenId ID of the token to be transferred - * @param data Optional data to send along with the call - * @return bool Boolean value signifying whether the call correctly returned the expected magic value - */ - function _checkOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory data - ) private returns (bool) { - if (to.isContract()) { - try - IERC721Receiver(to).onERC721Received( - _msgSender(), - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert ERC721TransferToNonReceiverImplementer(); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - /** - * @notice Used to safely mint a token to a specified address. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param to Address to which to safely mint the gven token - * @param tokenId ID of the token to mint to the specified address - */ - function _safeMint(address to, uint256 tokenId) internal virtual { - _safeMint(to, tokenId, ""); - } - - /** - * @notice Used to safely mint the token to the specified address while passing the additional data to contract - * recipients. - * @param to Address to which to mint the token - * @param tokenId ID of the token to mint - * @param data Additional data to send with the tokens - */ - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _mint(to, tokenId); - if (!_checkOnERC721Received(address(0), to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to mint a specified token to a given address. - * @dev WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - `to` cannot be the zero address. - * @dev Emits a {Transfer} event. - * @param to Address to mint the token to - * @param tokenId ID of the token to mint - */ - function _mint(address to, uint256 tokenId) internal virtual { - _innerMint(to, tokenId, 0); - - emit Transfer(address(0), to, tokenId); - emit NestTransfer(address(0), to, 0, 0, tokenId); - - _afterTokenTransfer(address(0), to, tokenId); - _afterNestedTokenTransfer(address(0), to, 0, 0, tokenId); - } - - /** - * @notice Used to mint a child token to a given parent token. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new child token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestMint( - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert MintToNonNestableImplementer(); - - _innerMint(to, tokenId, destinationId); - _sendToNFT(address(0), to, 0, destinationId, tokenId, data); - } - - /** - * @notice Used to mint a child token into a given parent token. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` must not exist. - * - `tokenId` must not be `0`. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new token - */ - function _innerMint( - address to, - uint256 tokenId, - uint256 destinationId - ) private { - if (to == address(0)) revert ERC721MintToTheZeroAddress(); - if (_exists(tokenId)) revert ERC721TokenAlreadyMinted(); - if (tokenId == 0) revert IdZeroForbidden(); - - _beforeTokenTransfer(address(0), to, tokenId); - _beforeNestedTokenTransfer(address(0), to, 0, destinationId, tokenId); - - _balances[to] += 1; - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId, - isNft: destinationId != 0 - }); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @dev Emits a {NestTransfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function _burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) internal virtual returns (uint256) { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - address owner = ownerOf(tokenId); - _balances[immediateOwner] -= 1; - - _beforeTokenTransfer(owner, address(0), tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - - _approve(address(0), tokenId); - _approveForAssets(address(0), tokenId); - - Child[] memory children = childrenOf(tokenId); - - delete _activeChildren[tokenId]; - delete _pendingChildren[tokenId]; - delete _tokenApprovals[tokenId][owner]; - - uint256 pendingRecursiveBurns; - uint256 totalChildBurns; - - uint256 length = children.length; //gas savings - for (uint256 i; i < length; ) { - if (totalChildBurns >= maxChildrenBurns) - revert MaxRecursiveBurnsReached( - children[i].contractAddress, - children[i].tokenId - ); - delete _childIsInActive[children[i].contractAddress][ - children[i].tokenId - ]; - unchecked { - // At this point we know pendingRecursiveBurns must be at least 1 - pendingRecursiveBurns = maxChildrenBurns - totalChildBurns; - } - // We substract one to the next level to count for the token being burned, then add it again on returns - // This is to allow the behavior of 0 recursive burns meaning only the current token is deleted. - totalChildBurns += - IERC6059(children[i].contractAddress).burn( - children[i].tokenId, - pendingRecursiveBurns - 1 - ) + - 1; - unchecked { - ++i; - } - } - // Can't remove before burning child since child will call back to get root owner - delete _directOwners[tokenId]; - - _afterTokenTransfer(owner, address(0), tokenId); - _afterNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId - ); - emit Transfer(owner, address(0), tokenId); - emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId); - - return totalChildBurns; - } - - /** - * @notice Used to safely transfer the token form `from` to `to`. - * @dev The function checks that contract recipients are aware of the ERC721 protocol to prevent tokens from being - * forever locked. - * @dev This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. implement alternative - * mechanisms to perform token transfer, such as signature-based. - * @dev Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - * @param data Additional data with no specified format, sent in call to `to` - */ - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _transfer(from, to, tokenId); - if (!_checkOnERC721Received(from, to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to transfer the token from `from` to `to`. - * @dev As opposed to {transferFrom}, this imposes no restrictions on msg.sender. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - */ - function _transfer( - address from, - address to, - uint256 tokenId - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, 0, to, false); - _balances[to] += 1; - - emit Transfer(from, to, tokenId); - emit NestTransfer(immediateOwner, to, parentId, 0, tokenId); - - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(immediateOwner, to, parentId, 0, tokenId); - } - - // --------------------- NESTABLE GETTERS --------------------- - - /** - * @notice Used to retrieve the immediate owner of the given token. - * @dev In the event the NFT is owned by an externally owned account, `tokenId` will be `0` and `isNft` will be - * `false`. - * @param tokenId ID of the token for which the immediate owner is being retrieved - * @return address Address of the immediate owner. If the token is owned by an externally owned account, its address - * will be returned. If the token is owned by another token, the parent token's collection smart contract address - * is returned - * @return uint256 Token ID of the immediate owner. If the immediate owner is an externally owned account, the value - * should be `0` - * @return bool A boolean value signifying whether the immediate owner is a token (`true`) or not (`false`) - */ - function directOwnerOf( - uint256 tokenId - ) public view virtual returns (address, uint256, bool) { - DirectOwner memory owner = _directOwners[tokenId]; - if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId(); - - return (owner.ownerAddress, owner.tokenId, owner.isNft); - } - - /** - * @notice Used to retrieve the active child tokens of a given parent token. - * @dev Returns array of Child structs existing for parent token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the active child tokens - * @return struct[] An array of Child structs containing the parent token's active child tokens - */ - - function childrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory children = _activeChildren[parentId]; - return children; - } - - /** - * @notice Used to retrieve the pending child tokens of a given parent token. - * @dev Returns array of pending Child structs existing for given parent. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the pending child tokens - * @return struct[] An array of Child structs containing the parent token's pending child tokens - */ - - function pendingChildrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory pendingChildren = _pendingChildren[parentId]; - return pendingChildren; - } - - /** - * @notice Used to retrieve a specific active child token for a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the child is being retrieved - * @param index Index of the child token in the parent token's active child tokens array - * @return struct A Child struct containing data about the specified child - */ - function childOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (childrenOf(parentId).length <= index) revert ChildIndexOutOfRange(); - Child memory child = _activeChildren[parentId][index]; - return child; - } - - /** - * @notice Used to retrieve a specific pending child token from a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the pending child token is being retrieved - * @param index Index of the child token in the parent token's pending child tokens array - * @return struct A Child struct containting data about the specified child - */ - function pendingChildOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (pendingChildrenOf(parentId).length <= index) - revert PendingChildIndexOutOfRange(); - Child memory child = _pendingChildren[parentId][index]; - return child; - } - - /** - * @notice Used to verify that the given child tokwn is included in an active array of a token. - * @param childAddress Address of the given token's collection smart contract - * @param childId ID of the child token being checked - * @return bool A boolean value signifying whether the given child token is included in an active child tokens array - * of a token (`true`) or not (`false`) - */ - function childIsInActive( - address childAddress, - uint256 childId - ) public view virtual returns (bool) { - return _childIsInActive[childAddress][childId] != 0; - } - - // --------------------- NESTABLE SETTERS --------------------- - /** - * @notice Used to add a child token to a given parent token. - * @dev This adds the iichild token into the given parent token's pending child tokens array. - * @dev You MUST NOT call this method directly. To add a a child to an NFT you must use either - * `nestTransfer`, `nestMint` or `transferChild` to the NFT. - * @dev Requirements: - * - * - `ownerOf` on the child contract must resolve to the called contract. - * - The pending array of the parent contract must not be full. - * @param parentId ID of the parent token to receive the new child token - * @param childId ID of the new proposed child token - * @param data Additional data with no specified format - */ - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) public virtual { - _requireMinted(parentId); - - address childAddress = _msgSender(); - if (!childAddress.isContract()) revert IsNotContract(); - - Child memory child = Child({ - contractAddress: childAddress, - tokenId: childId - }); - - _beforeAddChild(parentId, childAddress, childId); - - uint256 length = pendingChildrenOf(parentId).length; - - if (length < 128) { - _pendingChildren[parentId].push(child); - } else { - revert MaxPendingChildrenReached(); - } - - // Previous length matches the index for the new child - emit ChildProposed(parentId, length, childAddress, childId); - - _afterAddChild(parentId, childAddress, childId); - } - - /** - * @notice @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) public virtual onlyApprovedOrOwner(parentId) { - _acceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @param tokenId ID of the parent token for which to reject all of the pending tokens - */ - function rejectAllChildren( - uint256 tokenId, - uint256 maxRejections - ) public virtual onlyApprovedOrOwner(tokenId) { - _rejectAllChildren(tokenId, maxRejections); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) public virtual onlyApprovedOrOwner(tokenId) { - _transferChild( - tokenId, - to, - destinationId, - childIndex, - childAddress, - childId, - isPending, - data - ); - } - - /** - * @notice Used to transfer the token into another token. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @param from Address of the direct owner of the token to be transferred - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token being transferred - * @param destinationId ID of the token to receive the token being transferred - */ - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _nestTransfer(from, to, tokenId, destinationId, data); - } - - // --------------------- NESTABLE INTERNAL --------------------- - - /** - * @notice Used to transfer a child token from a given parent token. - * @dev When transferring a child token, the owner of the token is set to `to`, or is not updated in the event of `to` - * being the `0x0` address. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits {ChildTransferred} event. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function _transferChild( - uint256 tokenId, - address to, - uint256 destinationId, // newParentId - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) internal virtual { - Child memory child; - if (!isPending) { - if (isChildEquipped(tokenId, childAddress, childId)) - revert MustUnequipFirst(); - } - if (isPending) { - child = pendingChildOf(tokenId, childIndex); - } else { - child = childOf(tokenId, childIndex); - } - _checkExpectedChild(child, childAddress, childId); - - _beforeTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - - if (isPending) { - _removeChildByIndex(_pendingChildren[tokenId], childIndex); - } else { - delete _childIsInActive[childAddress][childId]; - _removeChildByIndex(_activeChildren[tokenId], childIndex); - } - - if (to != address(0)) { - if (destinationId == 0) { - IERC721(childAddress).safeTransferFrom( - address(this), - to, - childId, - data - ); - } else { - // Destination is an NFT - IERC6059(child.contractAddress).nestTransferFrom( - address(this), - to, - child.tokenId, - destinationId, - data - ); - } - } - - emit ChildTransferred( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - _afterTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending - ); - } - - /** - * @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @dev Requirements: - * - * - `tokenId` must exist - * - `index` must be in range of the pending children array - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual { - if (pendingChildrenOf(parentId).length <= childIndex) - revert PendingChildIndexOutOfRange(); - - Child memory child = pendingChildOf(parentId, childIndex); - _checkExpectedChild(child, childAddress, childId); - if (_childIsInActive[childAddress][childId] != 0) - revert ChildAlreadyExists(); - - _beforeAcceptChild(parentId, childIndex, childAddress, childId); - - // Remove from pending: - _removeChildByIndex(_pendingChildren[parentId], childIndex); - - // Add to active: - _activeChildren[parentId].push(child); - _childIsInActive[childAddress][childId] = 1; // We use 1 as true - - emit ChildAccepted(parentId, childIndex, childAddress, childId); - - _afterAcceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @dev Requirements: - * - * - `tokenId` must exist - * @param tokenId ID of the parent token for which to reject all of the pending tokens. - * @param maxRejections Maximum number of expected children to reject, used to prevent from - * rejecting children which arrive just before this operation. - */ - function _rejectAllChildren( - uint256 tokenId, - uint256 maxRejections - ) internal virtual { - if (_pendingChildren[tokenId].length > maxRejections) - revert UnexpectedNumberOfChildren(); - - _beforeRejectAllChildren(tokenId); - delete _pendingChildren[tokenId]; - emit AllChildrenRejected(tokenId); - _afterRejectAllChildren(tokenId); - } - - function _checkExpectedChild( - Child memory child, - address expectedAddress, - uint256 expectedId - ) private pure { - if ( - expectedAddress != child.contractAddress || - expectedId != child.tokenId - ) revert UnexpectedChildId(); - } - - /** - * @notice Used to remove a specified child token form an array using its index within said array. - * @dev The caller must ensure that the length of the array is valid compared to the index passed. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param array An array od Child struct containing info about the child tokens in a given child tokens array - * @param index An index of the child token to remove in the accompanying array - */ - function _removeChildByIndex(Child[] storage array, uint256 index) private { - array[index] = array[array.length - 1]; - array.pop(); - } - - /** - * @notice Used to transfer a token into another token. - * @dev Attempting to nest a token into `0x0` address will result in reverted transaction. - * @dev Attempting to nest a token into itself will result in reverted transaction. - * @param from Address of the account currently owning the given token - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token to transfer - * @param destinationId ID of the token receiving the given token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestTransfer( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - if (to == address(this) && tokenId == destinationId) - revert NestableTransferToSelf(); - - // Destination contract checks: - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC6059).interfaceId)) - revert NestableTransferToNonNestableImplementer(); - _checkForInheritanceLoop(tokenId, to, destinationId); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - to, - parentId, - destinationId, - tokenId - ); - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, destinationId, to, true); - _balances[to] += 1; - - // Sending to NFT: - _sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data); - } - - /** - * @notice Used to send a token to another token. - * @dev If the token being sent is currently owned by an externally owned account, the `parentId` should equal `0`. - * @dev Emits {Transfer} event. - * @dev Emits {NestTransfer} event. - * @param from Address from which the token is being sent - * @param to Address of the collection smart contract of the token to receive the given token - * @param parentId ID of the current parent token of the token being sent - * @param destinationId ID of the tokento receive the token being sent - * @param tokenId ID of the token being sent - * @param data Additional data with no specified format, sent in the addChild call - */ - function _sendToNFT( - address from, - address to, - uint256 parentId, - uint256 destinationId, - uint256 tokenId, - bytes memory data - ) private { - IERC6059 destContract = IERC6059(to); - destContract.addChild(destinationId, tokenId, data); - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(from, to, parentId, destinationId, tokenId); - - emit Transfer(from, to, tokenId); - emit NestTransfer(from, to, parentId, destinationId, tokenId); - } - - /** - * @notice Used to check if nesting a given token into a specified token would create an inheritance loop. - * @dev If a loop would occur, the tokens would be unmanageable, so the execution is reverted if one is detected. - * @dev The check for inheritance loop is bounded to guard against too much gas being consumed. - * @param currentId ID of the token that would be nested - * @param targetContract Address of the collection smart contract of the token into which the given token would be - * nested - * @param targetId ID of the token into which the given token would be nested - */ - function _checkForInheritanceLoop( - uint256 currentId, - address targetContract, - uint256 targetId - ) private view { - for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) { - ( - address nextOwner, - uint256 nextOwnerTokenId, - bool isNft - ) = IERC6059(targetContract).directOwnerOf(targetId); - // If there's a final address, we're good. There's no loop. - if (!isNft) { - return; - } - // Ff the current nft is an ancestor at some point, there is an inheritance loop - if (nextOwner == address(this) && nextOwnerTokenId == currentId) { - revert NestableTransferToDescendant(); - } - // We reuse the parameters to save some contract size - targetContract = nextOwner; - targetId = nextOwnerTokenId; - unchecked { - ++i; - } - } - revert NestableTooDeep(); - } - - // --------------------- MULTIASSET GETTERS --------------------- - - /** - * @notice Used to get the address of the user that is approved to manage the specified token from the current - * owner. - * @param tokenId ID of the token we are checking - * @return address Address of the account that is approved to manage the token - */ - function getApprovedForAssets( - uint256 tokenId - ) public view virtual returns (address) { - _requireMinted(tokenId); - return _tokenApprovalsForAssets[tokenId][ownerOf(tokenId)]; - } - - /** - * @inheritdoc IERC5773 - */ - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) public view virtual returns (string memory) { - if (!_tokenAssets[tokenId][assetId]) revert TokenDoesNotHaveAsset(); - return _assets[assetId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getActiveAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _activeAssets[tokenId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getPendingAssets( - uint256 tokenId - ) public view virtual returns (uint64[] memory) { - return _pendingAssets[tokenId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getActiveAssetPriorities( - uint256 tokenId - ) public view virtual returns (uint16[] memory) { - return _activeAssetPriorities[tokenId]; - } - - /** - * @inheritdoc IERC5773 - */ - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) public view virtual returns (uint64) { - return _assetReplacements[tokenId][newAssetId]; - } - - /** - * @inheritdoc IERC5773 - */ - function isApprovedForAllForAssets( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovalsForAssets[owner][operator]; - } - - // --------------------- MULTIASSET SETTERS --------------------- - /** - * @notice Used to grant approvals for specific tokens to a specified address. - * @dev This can only be called by the owner of the token or by an account that has been granted permission to - * manage all of the owner's assets. - * @param to Address of the account to receive the approval to the specified token - * @param tokenId ID of the token for which we are granting the permission - */ - function approveForAssets(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - if (to == owner) revert ApprovalForAssetsToCurrentOwner(); - - if ( - _msgSender() != owner && - !isApprovedForAllForAssets(owner, _msgSender()) - ) revert ApproveForAssetsCallerIsNotOwnerNorApprovedForAll(); - _approveForAssets(to, tokenId); - } - - /** - * @inheritdoc IERC5773 - */ - function setApprovalForAllForAssets( - address operator, - bool approved - ) public virtual { - address owner = _msgSender(); - if (owner == operator) revert ApprovalForAssetsToCurrentOwner(); - - _operatorApprovalsForAssets[owner][operator] = approved; - emit ApprovalForAllForAssets(owner, operator, approved); - } - - /** - * @notice Accepts a asset at from the pending array of given token. - * @dev Migrates the asset from the token's pending asset array to the token's active asset array. - * @dev Active assets cannot be removed by anyone, but can be replaced by a new asset. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - `index` must be in range of the length of the pending asset array. - * @dev Emits an {AssetAccepted} event. - * @param tokenId ID of the token for which to accept the pending asset - * @param index Index of the asset in the pending array to accept - */ - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _acceptAsset(tokenId, index, assetId); - } - - /** - * @notice Rejects a asset from the pending array of given token. - * @dev Removes the asset from the token's pending asset array. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - `index` must be in range of the length of the pending asset array. - * @dev Emits a {AssetRejected} event. - * @param tokenId ID of the token that the asset is being rejected from - * @param index Index of the asset in the pending array to be rejected - */ - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _rejectAsset(tokenId, index, assetId); - } - - /** - * @notice Rejects all assets from the pending array of a given token. - * @dev Effecitvely deletes the pending array. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * @dev Emits a {AssetRejected} event with assetId = 0. - * @param tokenId ID of the token of which to clear the pending array. - * @param maxRejections Maximum number of expected assets to reject, used to prevent from rejecting assets which - * arrive just before this operation. - */ - function rejectAllAssets( - uint256 tokenId, - uint256 maxRejections - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _rejectAllAssets(tokenId, maxRejections); - } - - /** - * @notice Sets a new priority array for a given token. - * @dev The priority array is a non-sequential list of `uint16`s, where the lowest value is considered highest - * priority. - * @dev Value `0` of a priority is a special case equivalent to unitialized. - * @dev Requirements: - * - * - The caller must own the token or be approved to manage the token's assets - * - `tokenId` must exist. - * - The length of `priorities` must be equal the length of the active assets array. - * @dev Emits a {AssetPrioritySet} event. - * @param tokenId ID of the token to set the priorities for - * @param priorities An array of priority values - */ - function setPriority( - uint256 tokenId, - uint16[] calldata priorities - ) public virtual onlyApprovedForAssetsOrOwner(tokenId) { - _setPriority(tokenId, priorities); - } - - // --------------------- MULTIASSET INTERNAL --------------------- - - /** - * @notice Internal function for granting approvals for a specific token. - * @param to Address of the account we are granting an approval to - * @param tokenId ID of the token we are granting the approval for - */ - function _approveForAssets(address to, uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - _tokenApprovalsForAssets[tokenId][owner] = to; - emit ApprovalForAssets(owner, to, tokenId); - } - - /** - * @notice Used to add a asset entry. - * @dev This internal function warrants custom access control to be implemented when used. - * @param id ID of the asset being added - * @param equippableGroupId ID of the equippable group being marked as equippable into the slot associated with - * `Parts` of the `Slot` type - * @param catalogAddress Address of the `Catalog` associated with the asset - * @param metadataURI The metadata URI of the asset - * @param partIds An array of IDs of fixed and slot parts to be included in the asset - */ - function _addAssetEntry( - uint64 id, - uint64 equippableGroupId, - address catalogAddress, - string memory metadataURI, - uint64[] calldata partIds - ) internal virtual { - _addAssetEntry(id, metadataURI); - - if (catalogAddress == address(0) && partIds.length != 0) - revert CatalogRequiredForParts(); - - _catalogAddresses[id] = catalogAddress; - _equippableGroupIds[id] = equippableGroupId; - _partIds[id] = partIds; - } - - /** - * @notice Used to accept a pending asset. - * @dev The call is reverted if there is no pending asset at a given index. - * @param tokenId ID of the token for which to accept the pending asset - * @param index Index of the asset in the pending array to accept - * @param assetId ID of the asset to accept in token's pending array - */ - function _acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) internal virtual { - _validatePendingAssetAtIndex(tokenId, index, assetId); - _beforeAcceptAsset(tokenId, index, assetId); - - uint64 replacesId = _assetReplacements[tokenId][assetId]; - uint256 replaceIndex; - bool replacefound; - if (replacesId != uint64(0)) - (replaceIndex, replacefound) = _activeAssets[tokenId].indexOf( - replacesId - ); - - if (replacefound) { - // We don't want to remove and then push a new asset. - // This way we also keep the priority of the original asset - _activeAssets[tokenId][replaceIndex] = assetId; - delete _tokenAssets[tokenId][replacesId]; - } else { - // We use the current size as next priority, by default priorities would be [0,1,2...] - _activeAssetPriorities[tokenId].push( - uint16(_activeAssets[tokenId].length) - ); - _activeAssets[tokenId].push(assetId); - replacesId = uint64(0); - } - _removePendingAsset(tokenId, index, assetId); - - emit AssetAccepted(tokenId, assetId, replacesId); - _afterAcceptAsset(tokenId, index, assetId); - } - - /** - * @notice Used to reject the specified asset from the pending array. - * @dev The call is reverted if there is no pending asset at a given index. - * @param tokenId ID of the token that the asset is being rejected from - * @param index Index of the asset in the pending array to be rejected - * @param assetId ID of the asset expected to be in the index - */ - function _rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) internal virtual { - _validatePendingAssetAtIndex(tokenId, index, assetId); - _beforeRejectAsset(tokenId, index, assetId); - - _removePendingAsset(tokenId, index, assetId); - delete _tokenAssets[tokenId][assetId]; - - emit AssetRejected(tokenId, assetId); - _afterRejectAsset(tokenId, index, assetId); - } - - /** - * @notice Used to validate the index on the pending assets array - * @dev The call is reverted if the index is out of range or the asset Id is not present at the index. - * @param tokenId ID of the token that the asset is validated from - * @param index Index of the asset in the pending array - * @param assetId Id of the asset expected to be in the index - */ - function _validatePendingAssetAtIndex( - uint256 tokenId, - uint256 index, - uint64 assetId - ) private view { - if (index >= _pendingAssets[tokenId].length) revert IndexOutOfRange(); - if (assetId != _pendingAssets[tokenId][index]) - revert UnexpectedAssetId(); - } - - /** - * @notice Used to remove the asset at the index on the pending assets array - * @param tokenId ID of the token that the asset is being removed from - * @param index Index of the asset in the pending array - * @param assetId Id of the asset expected to be in the index - */ - function _removePendingAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) private { - _pendingAssets[tokenId].removeItemByIndex(index); - delete _assetReplacements[tokenId][assetId]; - } - - /** - * @notice Used to reject all of the pending assets for the given token. - * @dev When rejecting all assets, the pending array is indiscriminately cleared. - * @dev If the number of pending assets is greater than the value of `maxRejections`, the exectuion will be - * reverted. - * @param tokenId ID of the token to reject all of the pending assets. - * @param maxRejections Maximum number of expected assets to reject, used to prevent from - * rejecting assets which arrive just before this operation. - */ - function _rejectAllAssets( - uint256 tokenId, - uint256 maxRejections - ) internal virtual { - uint256 len = _pendingAssets[tokenId].length; - if (len > maxRejections) revert UnexpectedNumberOfAssets(); - - _beforeRejectAllAssets(tokenId); - - for (uint256 i; i < len; ) { - uint64 assetId = _pendingAssets[tokenId][i]; - delete _assetReplacements[tokenId][assetId]; - unchecked { - ++i; - } - } - delete (_pendingAssets[tokenId]); - - emit AssetRejected(tokenId, uint64(0)); - _afterRejectAllAssets(tokenId); - } - - /** - * @notice Used to specify the priorities for a given token's active assets. - * @dev If the length of the priorities array doesn't match the length of the active assets array, the execution - * will be reverted. - * @dev The position of the priority value in the array corresponds the position of the asset in the active - * assets array it will be applied to. - * @param tokenId ID of the token for which the priorities are being set - * @param priorities Array of priorities for the assets - */ - function _setPriority( - uint256 tokenId, - uint16[] calldata priorities - ) internal virtual { - uint256 length = priorities.length; - if (length != _activeAssets[tokenId].length) - revert BadPriorityListLength(); - - _beforeSetPriority(tokenId, priorities); - _activeAssetPriorities[tokenId] = priorities; - - emit AssetPrioritySet(tokenId); - _afterSetPriority(tokenId, priorities); - } - - /** - * @notice Used to add an asset entry. - * @dev If the specified ID is already used by another asset, the execution will be reverted. - * @dev This internal function warrants custom access control to be implemented when used. - * @param id ID of the asset to assign to the new asset - * @param metadataURI Metadata URI of the asset - */ - function _addAssetEntry( - uint64 id, - string memory metadataURI - ) internal virtual { - if (id == uint64(0)) revert IdZeroForbidden(); - if (bytes(_assets[id]).length > 0) revert AssetAlreadyExists(); - - _beforeAddAsset(id, metadataURI); - _assets[id] = metadataURI; - - emit AssetSet(id); - _afterAddAsset(id, metadataURI); - } - - /** - * @notice Used to add an asset to a token. - * @dev If the given asset is already added to the token, the execution will be reverted. - * @dev If the asset ID is invalid, the execution will be reverted. - * @dev If the token already has the maximum amount of pending assets (128), the execution will be - * reverted. - * @param tokenId ID of the token to add the asset to - * @param assetId ID of the asset to add to the token - * @param replacesAssetWithId ID of the asset to replace from the token's list of active assets - */ - function _addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual { - if (_tokenAssets[tokenId][assetId]) revert AssetAlreadyExists(); - - if (bytes(_assets[assetId]).length == 0) revert NoAssetMatchingId(); - - if (_pendingAssets[tokenId].length >= 128) - revert MaxPendingAssetsReached(); - - _beforeAddAssetToToken(tokenId, assetId, replacesAssetWithId); - _tokenAssets[tokenId][assetId] = true; - _pendingAssets[tokenId].push(assetId); - - if (replacesAssetWithId != uint64(0)) { - _assetReplacements[tokenId][assetId] = replacesAssetWithId; - } - - uint256[] memory tokenIds = new uint256[](1); - tokenIds[0] = tokenId; - emit AssetAddedToTokens(tokenIds, assetId, replacesAssetWithId); - _afterAddAssetToToken(tokenId, assetId, replacesAssetWithId); - } - - // --------------------- EQUIPPABLE GETTERS --------------------- - - /** - * @inheritdoc IERC6220 - */ - function canTokenBeEquippedWithAssetIntoSlot( - address parent, - uint256 tokenId, - uint64 assetId, - uint64 slotId - ) public view virtual returns (bool) { - uint64 equippableGroupId = _equippableGroupIds[assetId]; - uint64 equippableSlot = _validParentSlots[equippableGroupId][parent]; - if (equippableSlot == slotId) { - (, bool found) = getActiveAssets(tokenId).indexOf(assetId); - return found; - } - return false; - } - - /** - * @inheritdoc IERC6220 - */ - function isChildEquipped( - uint256 tokenId, - address childAddress, - uint256 childId - ) public view virtual returns (bool) { - return _equipCountPerChild[tokenId][childAddress][childId] != uint8(0); - } - - /** - * @inheritdoc IERC6220 - */ - function getAssetAndEquippableData( - uint256 tokenId, - uint64 assetId - ) - public - view - virtual - returns (string memory, uint64, address, uint64[] memory) - { - return ( - getAssetMetadata(tokenId, assetId), - _equippableGroupIds[assetId], - _catalogAddresses[assetId], - _partIds[assetId] - ); - } - - /** - * @inheritdoc IERC6220 - */ - function getEquipment( - uint256 tokenId, - address targetCatalogAddress, - uint64 slotPartId - ) public view virtual returns (Equipment memory) { - return _equipments[tokenId][targetCatalogAddress][slotPartId]; - } - - // --------------------- EQUIPPABLE SETTERS --------------------- - - /** - * @inheritdoc IERC6220 - */ - function equip( - IntakeEquip memory data - ) public virtual onlyApprovedOrOwner(data.tokenId) { - _equip(data); - } - - /** - * @inheritdoc IERC6220 - */ - function unequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) public virtual onlyApprovedOrOwner(tokenId) { - _unequip(tokenId, assetId, slotPartId); - } - - // --------------------- EQUIPPABLE INTERNAL --------------------- - - /** - * @notice Private function used to equip a child into a token. - * @dev If the `Slot` already has an item equipped, the execution will be reverted. - * @dev If the child can't be used in the given `Slot`, the execution will be reverted. - * @dev If the catalog doesn't allow this equip to happen, the execution will be reverted. - * @dev The `IntakeEquip` stuct contains the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data An `IntakeEquip` struct specifying the equip data - */ - function _equip(IntakeEquip memory data) internal virtual { - address catalogAddress = _catalogAddresses[data.assetId]; - uint64 slotPartId = data.slotPartId; - if ( - _equipments[data.tokenId][catalogAddress][slotPartId] - .childEquippableAddress != address(0) - ) revert SlotAlreadyUsed(); - - // Check from parent's asset perspective: - (, bool found) = _partIds[data.assetId].indexOf(slotPartId); - if (!found) revert TargetAssetCannotReceiveSlot(); - - IERC6059.Child memory child = childOf(data.tokenId, data.childIndex); - - // Check from child perspective intention to be used in part - // We add reentrancy guard because of this call, it happens before updating state - if ( - !IERC6220(child.contractAddress) - .canTokenBeEquippedWithAssetIntoSlot( - address(this), - child.tokenId, - data.childAssetId, - slotPartId - ) - ) revert TokenCannotBeEquippedWithAssetIntoSlot(); - - // Check from catalog perspective - if ( - !ICatalog(catalogAddress).checkIsEquippable( - slotPartId, - child.contractAddress - ) - ) revert EquippableEquipNotAllowedByCatalog(); - - _beforeEquip(data); - Equipment memory newEquip = Equipment({ - assetId: data.assetId, - childAssetId: data.childAssetId, - childId: child.tokenId, - childEquippableAddress: child.contractAddress - }); - - _equipments[data.tokenId][catalogAddress][slotPartId] = newEquip; - _equipCountPerChild[data.tokenId][child.contractAddress][ - child.tokenId - ] += 1; - - emit ChildAssetEquipped( - data.tokenId, - data.assetId, - slotPartId, - child.tokenId, - child.contractAddress, - data.childAssetId - ); - _afterEquip(data); - } - - /** - * @notice Private function used to unequip child from parent token. - * @param tokenId ID of the parent from which the child is being unequipped - * @param assetId ID of the parent's asset that contains the `Slot` into which the child is equipped - * @param slotPartId ID of the `Slot` from which to unequip the child - */ - function _unequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) internal virtual { - address targetCatalogAddress = _catalogAddresses[assetId]; - Equipment memory equipment = _equipments[tokenId][targetCatalogAddress][ - slotPartId - ]; - if (equipment.childEquippableAddress == address(0)) - revert NotEquipped(); - _beforeUnequip(tokenId, assetId, slotPartId); - - delete _equipments[tokenId][targetCatalogAddress][slotPartId]; - _equipCountPerChild[tokenId][equipment.childEquippableAddress][ - equipment.childId - ] -= 1; - - emit ChildAssetUnequipped( - tokenId, - assetId, - slotPartId, - equipment.childId, - equipment.childEquippableAddress, - equipment.childAssetId - ); - _afterUnequip(tokenId, assetId, slotPartId); - } - - /** - * @notice Internal function used to declare that the assets belonging to a given `equippableGroupId` are - * equippable into the `Slot` associated with the `partId` of the collection at the specified `parentAddress` - * @param equippableGroupId ID of the equippable group - * @param parentAddress Address of the parent into which the equippable group can be equipped into - * @param slotPartId ID of the `Slot` that the items belonging to the equippable group can be equipped into - */ - function _setValidParentForEquippableGroup( - uint64 equippableGroupId, - address parentAddress, - uint64 slotPartId - ) internal virtual { - if (equippableGroupId == uint64(0) || slotPartId == uint64(0)) - revert IdZeroForbidden(); - _validParentSlots[equippableGroupId][parentAddress] = slotPartId; - emit ValidParentEquippableGroupIdSet( - equippableGroupId, - slotPartId, - parentAddress - ); - } - - // --------------------- MODIFIERS IMPLEMENTATIONS --------------------- - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @dev If the caller is not the owner of the token or approved to manage it by its owner, the execution will be - * reverted. - * @param tokenId ID of the token to check - */ - function _onlyApprovedOrOwner(uint256 tokenId) private view { - if (!_isApprovedOrOwner(_msgSender(), tokenId)) - revert ERC721NotApprovedOrOwner(); - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or it its direct owner. - * @dev This does not delegate to ownerOf, which returns the root owner, but rater uses an owner from DirectOwner - * struct. - * @dev The execution is reverted if the caller is not immediate owner or approved to manage the given token. - * @dev Used for parent-scoped transfers. - * @param tokenId ID of the token to check. - */ - function _onlyApprovedOrDirectOwner(uint256 tokenId) private view { - if (!_isApprovedOrDirectOwner(_msgSender(), tokenId)) - revert NotApprovedOrDirectOwner(); - } - - /** - * @notice Used to verify that the caller is either the owner of the given token or approved to manage the token's assets - * of the owner. - * @param tokenId ID of the token that we are checking - */ - function _onlyApprovedForAssetsOrOwner(uint256 tokenId) private view { - if (!_isApprovedForAssetsOrOwner(_msgSender(), tokenId)) - revert NotApprovedForAssetsOrOwner(); - } - - /** - * @notice Used to check whether the given account is allowed to manage the given token. - * @dev Requirements: - * - * - `tokenId` must exist. - * @param spender Address that is being checked for approval - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token - */ - function _isApprovedOrOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to check whether the account is approved to manage the token or its direct owner. - * @param spender Address that is being checked for approval or direct ownership - * @param tokenId ID of the token being checked - * @return bool The boolean value indicating whether the `spender` is approved to manage the given token or its - * direct owner - */ - function _isApprovedOrDirectOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - (address owner, uint256 parentId, ) = directOwnerOf(tokenId); - // When the parent is an NFT, only it can do operations - if (parentId != 0) { - return (spender == owner); - } - // Otherwise, the owner or approved address can - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Internal function to check whether the queried user is either: - * 1. The root owner of the token associated with `tokenId`. - * 2. Is approved for all assets of the current owner via the `setApprovalForAllForAssets` function. - * 3. Is granted approval for the specific tokenId for asset management via the `approveForAssets` function. - * @param user Address of the user we are checking for permission - * @param tokenId ID of the token to query for permission for a given `user` - * @return bool A boolean value indicating whether the user is approved to manage the token or not - */ - function _isApprovedForAssetsOrOwner( - address user, - uint256 tokenId - ) internal view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (user == owner || - isApprovedForAllForAssets(owner, user) || - getApprovedForAssets(tokenId) == user); - } - - // --------------------- MULTIASSET HOOKS --------------------- - - /** - * @notice Hook that is called before an asset is added. - * @param id ID of the asset - * @param metadataURI Metadata URI of the asset - */ - function _beforeAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - /** - * @notice Hook that is called after an asset is added. - * @param id ID of the asset - * @param metadataURI Metadata URI of the asset - */ - function _afterAddAsset( - uint64 id, - string memory metadataURI - ) internal virtual {} - - /** - * @notice Hook that is called before adding an asset to a token's pending assets array. - * @dev If the asset doesn't intend to replace another asset, the `replacesAssetWithId` value should be `0`. - * @param tokenId ID of the token to which the asset is being added - * @param assetId ID of the asset that is being added - * @param replacesAssetWithId ID of the asset that this asset is attempting to replace - */ - function _beforeAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - /** - * @notice Hook that is called after an asset has been added to a token's pending assets array. - * @dev If the asset doesn't intend to replace another asset, the `replacesAssetWithId` value should be `0`. - * @param tokenId ID of the token to which the asset is has been added - * @param assetId ID of the asset that is has been added - * @param replacesAssetWithId ID of the asset that this asset is attempting to replace - */ - function _afterAddAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) internal virtual {} - - /** - * @notice Hook that is called before an asset is accepted to a token's active assets array. - * @param tokenId ID of the token for which the asset is being accepted - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to be located at the specified `index` - */ - function _beforeAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called after an asset is accepted to a token's active assets array. - * @param tokenId ID of the token for which the asset has been accepted - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to have been located at the specified `index` - */ - function _afterAcceptAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called before rejecting an asset. - * @param tokenId ID of the token from which the asset is being rejected - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to be located at the specified `index` - */ - function _beforeRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called after rejecting an asset. - * @param tokenId ID of the token from which the asset has been rejected - * @param index Index of the asset in the token's pending assets array - * @param assetId ID of the asset expected to have been located at the specified `index` - */ - function _afterRejectAsset( - uint256 tokenId, - uint256 index, - uint256 assetId - ) internal virtual {} - - /** - * @notice Hook that is called before rejecting all assets of a token. - * @param tokenId ID of the token from which all of the assets are being rejected - */ - function _beforeRejectAllAssets(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called after rejecting all assets of a token. - * @param tokenId ID of the token from which all of the assets have been rejected - */ - function _afterRejectAllAssets(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called before the priorities for token's assets is set. - * @param tokenId ID of the token for which the asset priorities are being set - * @param priorities[] An array of priorities for token's active resources - */ - function _beforeSetPriority( - uint256 tokenId, - uint16[] calldata priorities - ) internal virtual {} - - /** - * @notice Hook that is called after the priorities for token's assets is set. - * @param tokenId ID of the token for which the asset priorities have been set - * @param priorities[] An array of priorities for token's active resources - */ - function _afterSetPriority( - uint256 tokenId, - uint16[] calldata priorities - ) internal virtual {} - - // --------------------- NESTABLE HOOKS --------------------- - - /** - * @notice Hook that is called before any token transfer. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be transferred to `to`. - * - When `from` is zero, `tokenId` will be minted to `to`. - * - When `to` is zero, ``from``'s `tokenId` will be burned. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after any transfer of tokens. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token has been transferred - * @param to Address to which the token has been transferred - * @param tokenId ID of the token that has been transferred - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param fromTokenId ID of the token from which the given token is being transferred - * @param toTokenId ID of the token to which the given token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token was transferred - * @param to Address to which the token was transferred - * @param fromTokenId ID of the token from which the given token was transferred - * @param toTokenId ID of the token to which the given token was transferred - * @param tokenId ID of the token that was transferred - */ - function _afterNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will receive a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has received a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _afterAddChild( - uint256 tokenId, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that will accept a pending child token - * @param childIndex Index of the child token to accept in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that has accepted a pending child token - * @param childIndex Index of the child token that was accpeted in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's pending children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's pending children array - */ - function _afterAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will transfer a child token - * @param childIndex Index of the child token that will be transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that is expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that is expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token is being transferred from the pending child - * tokens array (`true`) or from the active child tokens array (`false`) - */ - function _beforeTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called after a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has transferred a child token - * @param childIndex Index of the child token that was transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token was transferred from the pending child tokens - * array (`true`) or from the active child tokens array (`false`) - */ - function _afterTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending - ) internal virtual {} - - /** - * @notice Hook that is called before a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will reject all of the pending child tokens - */ - function _beforeRejectAllChildren(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called after a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has rejected all of the pending child tokens - */ - function _afterRejectAllChildren(uint256 tokenId) internal virtual {} - - // --------------------- EQUIPPABLE HOOKS --------------------- - - /** - * @notice A hook to be called before a equipping a asset to the token. - * @dev The `IntakeEquip` struct consist of the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data The `IntakeEquip` struct containing data of the asset that is being equipped - */ - function _beforeEquip(IntakeEquip memory data) internal virtual {} - - /** - * @notice A hook to be called after equipping a asset to the token. - * @dev The `IntakeEquip` struct consist of the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data The `IntakeEquip` struct containing data of the asset that was equipped - */ - function _afterEquip(IntakeEquip memory data) internal virtual {} - - /** - * @notice A hook to be called before unequipping a asset from the token. - * @param tokenId ID of the token from which the asset is being unequipped - * @param assetId ID of the asset being unequipped - * @param slotPartId ID of the slot from which the asset is being unequipped - */ - function _beforeUnequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) internal virtual {} - - /** - * @notice A hook to be called after unequipping a asset from the token. - * @param tokenId ID of the token from which the asset was unequipped - * @param assetId ID of the asset that was unequipped - * @param slotPartId ID of the slot from which the asset was unequipped - */ - function _afterUnequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) internal virtual {} -} diff --git a/assets/eip-6220/contracts/ICatalog.sol b/assets/eip-6220/contracts/ICatalog.sol deleted file mode 100644 index ba91ebb7584fc1..00000000000000 --- a/assets/eip-6220/contracts/ICatalog.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/** - * @title ICatalog - * @author RMRK team - * @notice An interface Catalog for equippable module. - */ -interface ICatalog is IERC165 { - /** - * @notice Event to announce addition of a new part. - * @dev It is emitted when a new part is added. - * @param partId ID of the part that was added - * @param itemType Enum value specifying whether the part is `None`, `Slot` and `Fixed` - * @param zIndex An uint specifying the z value of the part. It is used to specify the depth which the part should - * be rendered at - * @param equippableAddresses An array of addresses that can equip this part - * @param metadataURI The metadata URI of the part - */ - event AddedPart( - uint64 indexed partId, - ItemType indexed itemType, - uint8 zIndex, - address[] equippableAddresses, - string metadataURI - ); - - /** - * @notice Event to announce new equippables to the part. - * @dev It is emitted when new addresses are marked as equippable for `partId`. - * @param partId ID of the part that had new equippable addresses added - * @param equippableAddresses An array of the new addresses that can equip this part - */ - event AddedEquippables( - uint64 indexed partId, - address[] equippableAddresses - ); - - /** - * @notice Event to announce the overriding of equippable addresses of the part. - * @dev It is emitted when the existing list of addresses marked as equippable for `partId` is overwritten by a new - * one. - * @param partId ID of the part whose list of equippable addresses was overwritten - * @param equippableAddresses The new, full, list of addresses that can equip this part - */ - event SetEquippables(uint64 indexed partId, address[] equippableAddresses); - - /** - * @notice Event to announce that a given part can be equipped by any address. - * @dev It is emitted when a given part is marked as equippable by any. - * @param partId ID of the part marked as equippable by any address - */ - event SetEquippableToAll(uint64 indexed partId); - - /** - * @notice Used to define a type of the item. Possible values are `None`, `Slot` or `Fixed`. - * @dev Used for fixed and slot parts. - */ - enum ItemType { - None, - Slot, - Fixed - } - - /** - * @notice The integral structure of a standard RMRK catalog item defining it. - * @dev Requires a minimum of 3 storage slots per catalog item, equivalent to roughly 60,000 gas as of Berlin hard fork - * (April 14, 2021), though 5-7 storage slots is more realistic, given the standard length of an IPFS URI. This - * will result in between 25,000,000 and 35,000,000 gas per 250 assets--the maximum block size of Ethereum - * mainnet is 30M at peak usage. - * @return itemType The item type of the part - * @return z The z value of the part defining how it should be rendered when presenting the full NFT - * @return equippable The array of addresses allowed to be equipped in this part - * @return metadataURI The metadata URI of the part - */ - struct Part { - ItemType itemType; //1 byte - uint8 z; //1 byte - address[] equippable; //n Collections that can be equipped into this slot - string metadataURI; //n bytes 32+ - } - - /** - * @notice The structure used to add a new `Part`. - * @dev The part is added with specified ID, so you have to make sure that you are using an unused `partId`, - * otherwise the addition of the part vill be reverted. - * @dev The full `IntakeStruct` looks like this: - * [ - * partID, - * [ - * itemType, - * z, - * [ - * permittedCollectionAddress0, - * permittedCollectionAddress1, - * permittedCollectionAddress2 - * ], - * metadataURI - * ] - * ] - * @return partId ID to be assigned to the `Part` - * @return part A `Part` to be added - */ - struct IntakeStruct { - uint64 partId; - Part part; - } - - /** - * @notice Used to return the metadata URI of the associated catalog. - * @return string Base metadata URI - */ - function getMetadataURI() external view returns (string memory); - - /** - * @notice Used to return the `itemType` of the associated catalog - * @return string `itemType` of the associated catalog - */ - function getType() external view returns (string memory); - - /** - * @notice Used to check whether the given address is allowed to equip the desired `Part`. - * @dev Returns true if a collection may equip asset with `partId`. - * @param partId The ID of the part that we are checking - * @param targetAddress The address that we are checking for whether the part can be equipped into it or not - * @return bool The status indicating whether the `targetAddress` can be equipped into `Part` with `partId` or not - */ - function checkIsEquippable(uint64 partId, address targetAddress) - external - view - returns (bool); - - /** - * @notice Used to check if the part is equippable by all addresses. - * @dev Returns true if part is equippable to all. - * @param partId ID of the part that we are checking - * @return bool The status indicating whether the part with `partId` can be equipped by any address or not - */ - function checkIsEquippableToAll(uint64 partId) external view returns (bool); - - /** - * @notice Used to retrieve a `Part` with id `partId` - * @param partId ID of the part that we are retrieving - * @return struct The `Part` struct associated with given `partId` - */ - function getPart(uint64 partId) external view returns (Part memory); - - /** - * @notice Used to retrieve multiple parts at the same time. - * @param partIds An array of part IDs that we want to retrieve - * @return struct An array of `Part` structs associated with given `partIds` - */ - function getParts(uint64[] calldata partIds) - external - view - returns (Part[] memory); -} diff --git a/assets/eip-6220/contracts/IERC5773.sol b/assets/eip-6220/contracts/IERC5773.sol deleted file mode 100644 index 98ad0a9f228dbf..00000000000000 --- a/assets/eip-6220/contracts/IERC5773.sol +++ /dev/null @@ -1,92 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC5773 { - event AssetSet(uint64 assetId); - - event AssetAddedToTokens( - uint256[] tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetAccepted( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed replacesId - ); - - event AssetRejected(uint256 indexed tokenId, uint64 indexed assetId); - - event AssetPrioritySet(uint256 indexed tokenId); - - event ApprovalForAssets( - address indexed owner, - address indexed approved, - uint256 indexed tokenId - ); - - event ApprovalForAllForAssets( - address indexed owner, - address indexed operator, - bool approved - ); - - function acceptAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAsset( - uint256 tokenId, - uint256 index, - uint64 assetId - ) external; - - function rejectAllAssets(uint256 tokenId, uint256 maxRejections) external; - - function setPriority( - uint256 tokenId, - uint16[] calldata priorities - ) external; - - function getActiveAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getPendingAssets( - uint256 tokenId - ) external view returns (uint64[] memory); - - function getActiveAssetPriorities( - uint256 tokenId - ) external view returns (uint16[] memory); - - function getAssetReplacements( - uint256 tokenId, - uint64 newAssetId - ) external view returns (uint64); - - function getAssetMetadata( - uint256 tokenId, - uint64 assetId - ) external view returns (string memory); - - function approveForAssets(address to, uint256 tokenId) external; - - function getApprovedForAssets( - uint256 tokenId - ) external view returns (address); - - function setApprovalForAllForAssets( - address operator, - bool approved - ) external; - - function isApprovedForAllForAssets( - address owner, - address operator - ) external view returns (bool); -} diff --git a/assets/eip-6220/contracts/IERC6059.sol b/assets/eip-6220/contracts/IERC6059.sol deleted file mode 100644 index dbad8969f5f4df..00000000000000 --- a/assets/eip-6220/contracts/IERC6059.sol +++ /dev/null @@ -1,114 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -interface IERC6059 { - struct DirectOwner { - uint256 tokenId; - address ownerAddress; - bool isNft; - } - - event NestTransfer( - address indexed from, - address indexed to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 indexed tokenId - ); - - event ChildProposed( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event ChildAccepted( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event AllChildrenRejected(uint256 indexed tokenId); - - event ChildTransferred( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId, - bool fromPending - ); - - struct Child { - uint256 tokenId; - address contractAddress; - } - - function ownerOf(uint256 tokenId) external view returns (address owner); - - function directOwnerOf( - uint256 tokenId - ) external view returns (address, uint256, bool); - - function burn( - uint256 tokenId, - uint256 maxRecursiveBurns - ) external returns (uint256); - - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) external; - - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) external; - - function rejectAllChildren( - uint256 parentId, - uint256 maxRejections - ) external; - - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) external; - - function childrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function pendingChildrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function childOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function pendingChildOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) external; -} diff --git a/assets/eip-6220/contracts/IERC6220.sol b/assets/eip-6220/contracts/IERC6220.sol deleted file mode 100644 index 859fb74aff5673..00000000000000 --- a/assets/eip-6220/contracts/IERC6220.sol +++ /dev/null @@ -1,194 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "./IERC5773.sol"; - -/** - * @title IERC6220 - * @author RMRK team - * @notice Interface smart contract of the equippable module. - */ -interface IERC6220 is IERC5773 { - /** - * @notice Used to store the core structure of the `Equippable` component. - * @return assetId The ID of the asset equipping a child - * @return childAssetId The ID of the asset used as equipment - * @return childId The ID of token that is equipped - * @return childEquippableAddress Address of the collection to which the child asset belongs to - */ - struct Equipment { - uint64 assetId; - uint64 childAssetId; - uint256 childId; - address childEquippableAddress; - } - - /** - * @notice Used to provide a struct for inputing equip data. - * @dev Only used for input and not storage of data. - * @return tokenId ID of the token we are managing - * @return childIndex Index of a child in the list of token's active children - * @return assetId ID of the asset that we are equipping into - * @return slotPartId ID of the slot part that we are using to equip - * @return childAssetId ID of the asset that we are equipping - */ - struct IntakeEquip { - uint256 tokenId; - uint256 childIndex; - uint64 assetId; - uint64 slotPartId; - uint64 childAssetId; - } - - /** - * @notice Used to notify listeners that a child's asset has been equipped into one of its parent assets. - * @param tokenId ID of the token that had an asset equipped - * @param assetId ID of the asset associated with the token we are equipping into - * @param slotPartId ID of the slot we are using to equip - * @param childId ID of the child token we are equipping into the slot - * @param childAddress Address of the child token's collection - * @param childAssetId ID of the asset associated with the token we are equipping - */ - event ChildAssetEquipped( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed slotPartId, - uint256 childId, - address childAddress, - uint64 childAssetId - ); - - /** - * @notice Used to notify listeners that a child's asset has been unequipped from one of its parent assets. - * @param tokenId ID of the token that had an asset unequipped - * @param assetId ID of the asset associated with the token we are unequipping out of - * @param slotPartId ID of the slot we are unequipping from - * @param childId ID of the token being unequipped - * @param childAddress Address of the collection that a token that is being unequipped belongs to - * @param childAssetId ID of the asset associated with the token we are unequipping - */ - event ChildAssetUnequipped( - uint256 indexed tokenId, - uint64 indexed assetId, - uint64 indexed slotPartId, - uint256 childId, - address childAddress, - uint64 childAssetId - ); - - /** - * @notice Used to notify listeners that the assets belonging to a `equippableGroupId` have been marked as - * equippable into a given slot and parent - * @param equippableGroupId ID of the equippable group being marked as equippable into the slot associated with - * `slotPartId` of the `parentAddress` collection - * @param slotPartId ID of the slot part of the catalog into which the parts belonging to the equippable group - * associated with `equippableGroupId` can be equipped - * @param parentAddress Address of the collection into which the parts belonging to `equippableGroupId` can be - * equipped - */ - event ValidParentEquippableGroupIdSet( - uint64 indexed equippableGroupId, - uint64 indexed slotPartId, - address parentAddress - ); - - /** - * @notice Used to equip a child into a token. - * @dev The `IntakeEquip` stuct contains the following data: - * [ - * tokenId, - * childIndex, - * assetId, - * slotPartId, - * childAssetId - * ] - * @param data An `IntakeEquip` struct specifying the equip data - */ - function equip( - IntakeEquip memory data - ) external; - - /** - * @notice Used to unequip child from parent token. - * @dev This can only be called by the owner of the token or by an account that has been granted permission to - * manage the given token by the current owner. - * @param tokenId ID of the parent from which the child is being unequipped - * @param assetId ID of the parent's asset that contains the `Slot` into which the child is equipped - * @param slotPartId ID of the `Slot` from which to unequip the child - */ - function unequip( - uint256 tokenId, - uint64 assetId, - uint64 slotPartId - ) external; - - /** - * @notice Used to check whether the token has a given child equipped. - * @dev This is used to prevent from transferring a child that is equipped. - * @param tokenId ID of the parent token for which we are querying for - * @param childAddress Address of the child token's smart contract - * @param childId ID of the child token - * @return bool The boolean value indicating whether the child token is equipped into the given token or not - */ - function isChildEquipped( - uint256 tokenId, - address childAddress, - uint256 childId - ) external view returns (bool); - - /** - * @notice Used to verify whether a token can be equipped into a given parent's slot. - * @param parent Address of the parent token's smart contract - * @param tokenId ID of the token we want to equip - * @param assetId ID of the asset associated with the token we want to equip - * @param slotId ID of the slot that we want to equip the token into - * @return bool The boolean indicating whether the token with the given asset can be equipped into the desired - * slot - */ - function canTokenBeEquippedWithAssetIntoSlot( - address parent, - uint256 tokenId, - uint64 assetId, - uint64 slotId - ) external view returns (bool); - - /** - * @notice Used to get the Equipment object equipped into the specified slot of the desired token. - * @dev The `Equipment` struct consists of the following data: - * [ - * assetId, - * childAssetId, - * childId, - * childEquippableAddress - * ] - * @param tokenId ID of the token for which we are retrieving the equipped object - * @param targetCatalogAddress Address of the `Catalog` associated with the `Slot` part of the token - * @param slotPartId ID of the `Slot` part that we are checking for equipped objects - * @return struct The `Equipment` struct containing data about the equipped object - */ - function getEquipment( - uint256 tokenId, - address targetCatalogAddress, - uint64 slotPartId - ) external view returns (Equipment memory); - - /** - * @notice Used to get the asset and equippable data associated with given `assetId`. - * @param tokenId ID of the token for which to retrieve the asset - * @param assetId ID of the asset of which we are retrieving - * @return metadataURI The metadata URI of the asset - * @return equippableGroupId ID of the equippable group this asset belongs to - * @return catalogAddress The address of the catalog the part belongs to - * @return partIds An array of IDs of parts included in the asset - */ - function getAssetAndEquippableData(uint256 tokenId, uint64 assetId) - external - view - returns ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - uint64[] calldata partIds - ); -} diff --git a/assets/eip-6220/contracts/library/EquippableLib.sol b/assets/eip-6220/contracts/library/EquippableLib.sol deleted file mode 100644 index 1ae233ab71b1ae..00000000000000 --- a/assets/eip-6220/contracts/library/EquippableLib.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -library EquippableLib { - function indexOf( - uint64[] memory A, - uint64 a - ) internal pure returns (uint256, bool) { - uint256 length = A.length; - for (uint256 i; i < length; ) { - if (A[i] == a) { - return (i, true); - } - unchecked { - ++i; - } - } - return (0, false); - } - - //For reasource storage array - function removeItemByIndex(uint64[] storage array, uint256 index) internal { - //Check to see if this is already gated by require in all calls - require(index < array.length); - array[index] = array[array.length - 1]; - array.pop(); - } -} diff --git a/assets/eip-6220/contracts/mocks/CatalogMock.sol b/assets/eip-6220/contracts/mocks/CatalogMock.sol deleted file mode 100644 index 106762aaf3c5e2..00000000000000 --- a/assets/eip-6220/contracts/mocks/CatalogMock.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../Catalog.sol"; - -contract CatalogMock is Catalog { - constructor(string memory metadataURI, string memory type_) - Catalog(metadataURI, type_) - {} - - function addPart(IntakeStruct calldata intakeStruct) external { - _addPart(intakeStruct); - } - - function addPartList(IntakeStruct[] calldata intakeStructs) external { - _addPartList(intakeStructs); - } - - function addEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) external { - _addEquippableAddresses(partId, equippableAddresses); - } - - function setEquippableAddresses( - uint64 partId, - address[] calldata equippableAddresses - ) external { - _setEquippableAddresses(partId, equippableAddresses); - } - - function setEquippableToAll(uint64 partId) external { - _setEquippableToAll(partId); - } - - function resetEquippableAddresses(uint64 partId) external { - _resetEquippableAddresses(partId); - } -} diff --git a/assets/eip-6220/contracts/mocks/ERC721Mock.sol b/assets/eip-6220/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index 995868858e680b..00000000000000 --- a/assets/eip-6220/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests with non ERC721 implementer - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} -} diff --git a/assets/eip-6220/contracts/mocks/ERC721ReceiverMock.sol b/assets/eip-6220/contracts/mocks/ERC721ReceiverMock.sol deleted file mode 100644 index 1f0665b8c304ca..00000000000000 --- a/assets/eip-6220/contracts/mocks/ERC721ReceiverMock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract ERC721ReceiverMock { - bytes4 constant ERC721_RECEIVED = 0x150b7a02; - - function onERC721Received( - address, - address, - uint256, - bytes calldata - ) public returns (bytes4) { - return ERC721_RECEIVED; - } -} diff --git a/assets/eip-6220/contracts/mocks/EquippableTokenMock.sol b/assets/eip-6220/contracts/mocks/EquippableTokenMock.sol deleted file mode 100644 index 243be8340f6b90..00000000000000 --- a/assets/eip-6220/contracts/mocks/EquippableTokenMock.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../EquippableToken.sol"; - -//Minimal public implementation of INestable for testing. -contract EquippableTokenMock is EquippableToken { - address private _issuer; - - constructor() EquippableToken() { - _setIssuer(_msgSender()); - } - - modifier onlyIssuer() { - require(_msgSender() == _issuer, "RMRK: Only issuer"); - _; - } - - function setIssuer(address issuer) external onlyIssuer { - _setIssuer(issuer); - } - - function _setIssuer(address issuer) private { - _issuer = issuer; - } - - function getIssuer() external view returns (address) { - return _issuer; - } - - function mint(address to, uint256 tokenId) external onlyIssuer { - _mint(to, tokenId); - } - - function nestMint( - address to, - uint256 tokenId, - uint256 destinationId - ) external { - _nestMint(to, tokenId, destinationId, ""); - } - - // Utility transfers: - - function transfer(address to, uint256 tokenId) public virtual { - transferFrom(_msgSender(), to, tokenId); - } - - function nestTransfer( - address to, - uint256 tokenId, - uint256 destinationId - ) public virtual { - nestTransferFrom(_msgSender(), to, tokenId, destinationId, ""); - } - - function addAssetToToken( - uint256 tokenId, - uint64 assetId, - uint64 replacesAssetWithId - ) external onlyIssuer { - _addAssetToToken(tokenId, assetId, replacesAssetWithId); - } - - function addAssetEntry( - uint64 id, - string memory metadataURI - ) external onlyIssuer { - _addAssetEntry(id, metadataURI); - } - - function addEquippableAssetEntry( - uint64 id, - uint64 equippableGroupId, - address catalogAddress, - string memory metadataURI, - uint64[] calldata partIds - ) external onlyIssuer { - _addAssetEntry( - id, - equippableGroupId, - catalogAddress, - metadataURI, - partIds - ); - } - - function setValidParentForEquippableGroup( - uint64 equippableGroupId, - address parentAddress, - uint64 partId - ) external { - _setValidParentForEquippableGroup( - equippableGroupId, - parentAddress, - partId - ); - } -} diff --git a/assets/eip-6220/contracts/mocks/NonReceiverMock.sol b/assets/eip-6220/contracts/mocks/NonReceiverMock.sol deleted file mode 100644 index e9d2d6ed3ecb2e..00000000000000 --- a/assets/eip-6220/contracts/mocks/NonReceiverMock.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.15; - -contract NonReceiverMock { - function dummy() external {} -} diff --git a/assets/eip-6220/contracts/utils/EquipRenderUtils.sol b/assets/eip-6220/contracts/utils/EquipRenderUtils.sol deleted file mode 100644 index 6637d5a86d8829..00000000000000 --- a/assets/eip-6220/contracts/utils/EquipRenderUtils.sol +++ /dev/null @@ -1,481 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../ICatalog.sol"; -import "../IEquippable.sol"; -import "../library/EquippableLib.sol"; - -error TokenHasNoAssets(); -error NotComposableAsset(); - -/** - * @title EquipRenderUtils - * @author RMRK team - * @notice Smart contract of the RMRK Equip render utils module. - * @dev Extra utility functions for composing RMRK extended assets. - */ -contract EquipRenderUtils { - using EquippableLib for uint64[]; - - /** - * @notice The structure used to display a full information of an active asset. - * @return id ID of the asset - * @return equppableGroupId ID of the equippable group this asset belongs to - * @return priority Priority of the asset in the active assets array it belongs to - * @return catalogAddress Address of the `Catalog` smart contract this asset belongs to - * @return metadata Metadata URI of the asset - * @return partIds[] An array of IDs of fixed and slot parts present in the asset - */ - struct ExtendedActiveAsset { - uint64 id; - uint64 equippableGroupId; - uint16 priority; - address catalogAddress; - string metadata; - uint64[] partIds; - } - - /** - * @notice The structure used to display a full information of a pending asset. - * @return id ID of the asset - * @return equppableGroupId ID of the equippable group this asset belongs to - * @return acceptRejectIndex The index of the given asset in the pending assets array it belongs to - * @return replacesAssetWithId ID of the asset the given asset will replace if accepted - * @return catalogAddress Address of the `Catalog` smart contract this asset belongs to - * @return metadata Metadata URI of the asset - * @return partIds[] An array of IDs of fixed and slot parts present in the asset - */ - struct ExtendedPendingAsset { - uint64 id; - uint64 equippableGroupId; - uint128 acceptRejectIndex; - uint64 replacesAssetWithId; - address catalogAddress; - string metadata; - uint64[] partIds; - } - - /** - * @notice The structure used to display a full information of an equippend slot part. - * @return partId ID of the slot part - * @return childAssetId ID of the child asset equipped into the slot part - * @return z The z value of the part defining how it should be rendered when presenting the full NFT - * @return childAddress Address of the collection smart contract of the child token equipped into the slot - * @return childId ID of the child token equipped into the slot - * @return childAssetMetadata Metadata URI of the child token equipped into the slot - * @return partMetadata Metadata URI of the given slot part - */ - struct EquippedSlotPart { - uint64 partId; - uint64 childAssetId; - uint8 z; //1 byte - address childAddress; - uint256 childId; - string childAssetMetadata; //n bytes 32+ - string partMetadata; //n bytes 32+ - } - - /** - * @notice Used to provide data about fixed parts. - * @return partId ID of the part - * @return z The z value of the asset, specifying how the part should be rendered in a composed NFT - * @return matadataURI The metadata URI of the fixed part - */ - struct FixedPart { - uint64 partId; - uint8 z; //1 byte - string metadataURI; //n bytes 32+ - } - - /** - * @notice Used to get extended active assets of the given token. - * @dev The full `ExtendedActiveAsset` looks like this: - * [ - * ID, - * equippableGroupId, - * priority, - * catalogAddress, - * metadata, - * [ - * fixedPartId0, - * fixedPartId1, - * fixedPartId2, - * slotPartId0, - * slotPartId1, - * slotPartId2 - * ] - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the extended active assets for - * @return sturct[] An array of ExtendedActiveAssets present on the given token - */ - function getExtendedActiveAssets(address target, uint256 tokenId) - public - view - virtual - returns (ExtendedActiveAsset[] memory) - { - IEquippable target_ = IEquippable(target); - - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint16[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - ExtendedActiveAsset[] memory activeAssets = new ExtendedActiveAsset[]( - len - ); - - for (uint256 i; i < len; ) { - ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - uint64[] memory partIds - ) = target_.getAssetAndEquippableData(tokenId, assets[i]); - activeAssets[i] = ExtendedActiveAsset({ - id: assets[i], - equippableGroupId: equippableGroupId, - priority: priorities[i], - catalogAddress: catalogAddress, - metadata: metadataURI, - partIds: partIds - }); - unchecked { - ++i; - } - } - return activeAssets; - } - - /** - * @notice Used to get the extended pending assets of the given token. - * @dev The full `ExtendedPendingAsset` looks like this: - * [ - * ID, - * equippableGroupId, - * acceptRejectIndex, - * replacesAssetWithId, - * catalogAddress, - * metadata, - * [ - * fixedPartId0, - * fixedPartId1, - * fixedPartId2, - * slotPartId0, - * slotPartId1, - * slotPartId2 - * ] - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the extended pending assets for - * @return sturct[] An array of ExtendedPendingAssets present on the given token - */ - function getExtendedPendingAssets(address target, uint256 tokenId) - public - view - virtual - returns (ExtendedPendingAsset[] memory) - { - IEquippable target_ = IEquippable(target); - - uint64[] memory assets = target_.getPendingAssets(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - ExtendedPendingAsset[] - memory pendingAssets = new ExtendedPendingAsset[](len); - uint64 replacesAssetWithId; - for (uint256 i; i < len; ) { - ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - uint64[] memory partIds - ) = target_.getAssetAndEquippableData(tokenId, assets[i]); - replacesAssetWithId = target_.getAssetReplacements( - tokenId, - assets[i] - ); - pendingAssets[i] = ExtendedPendingAsset({ - id: assets[i], - equippableGroupId: equippableGroupId, - acceptRejectIndex: uint128(i), - replacesAssetWithId: replacesAssetWithId, - catalogAddress: catalogAddress, - metadata: metadataURI, - partIds: partIds - }); - unchecked { - ++i; - } - } - return pendingAssets; - } - - /** - * @notice Used to retrieve the equipped parts of the given token. - * @dev NOTE: Some of the equipped children might be empty. - * @dev The full `Equipment` struct looks like this: - * [ - * assetId, - * childAssetId, - * childId, - * childEquippableAddress - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the equipped items in the asset for - * @param assetId ID of the asset being queried for equipped parts - * @return slotPartIds An array of the IDs of the slot parts present in the given asset - * @return childrenEquipped An array of `Equipment` structs containing info about the equipped children - */ - function getEquipped( - address target, - uint64 tokenId, - uint64 assetId - ) - public - view - returns ( - uint64[] memory slotPartIds, - IEquippable.Equipment[] memory childrenEquipped - ) - { - IEquippable target_ = IEquippable(target); - - (, , address catalogAddress, uint64[] memory partIds) = target_ - .getAssetAndEquippableData(tokenId, assetId); - - (slotPartIds, ) = splitSlotAndFixedParts(partIds, catalogAddress); - childrenEquipped = new IEquippable.Equipment[](slotPartIds.length); - - uint256 len = slotPartIds.length; - for (uint256 i; i < len; ) { - IEquippable.Equipment memory equipment = target_.getEquipment( - tokenId, - catalogAddress, - slotPartIds[i] - ); - if (equipment.assetId == assetId) { - childrenEquipped[i] = equipment; - } - unchecked { - ++i; - } - } - } - - /** - * @notice Used to compose the given equippables. - * @dev The full `FixedPart` struct looks like this: - * [ - * partId, - * z, - * metadataURI - * ] - * @dev The full `EquippedSlotPart` struct looks like this: - * [ - * partId, - * childAssetId, - * z, - * childAddress, - * childId, - * childAssetMetadata, - * partMetadata - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to compose the equipped items in the asset for - * @param assetId ID of the asset being queried for equipped parts - * @return metadataURI Metadata URI of the asset - * @return equippableGroupId Equippable group ID of the asset - * @return catalogAddress Address of the catalog to which the asset belongs to - * @return fixedParts An array of fixed parts respresented by the `FixedPart` structs present on the asset - * @return slotParts An array of slot parts represented by the `EquippedSlotPart` structs present on the asset - */ - function composeEquippables( - address target, - uint256 tokenId, - uint64 assetId - ) - public - view - returns ( - string memory metadataURI, - uint64 equippableGroupId, - address catalogAddress, - FixedPart[] memory fixedParts, - EquippedSlotPart[] memory slotParts - ) - { - IEquippable target_ = IEquippable(target); - uint64[] memory partIds; - - // If token does not have uint64[] memory slotPartId to save the asset, it would fail here. - (metadataURI, equippableGroupId, catalogAddress, partIds) = target_ - .getAssetAndEquippableData(tokenId, assetId); - if (catalogAddress == address(0)) revert NotComposableAsset(); - - ( - uint64[] memory slotPartIds, - uint64[] memory fixedPartIds - ) = splitSlotAndFixedParts(partIds, catalogAddress); - - // Fixed parts: - fixedParts = new FixedPart[](fixedPartIds.length); - - uint256 len = fixedPartIds.length; - if (len != 0) { - ICatalog.Part[] memory catalogFixedParts = ICatalog( - catalogAddress - ).getParts(fixedPartIds); - for (uint256 i; i < len; ) { - fixedParts[i] = FixedPart({ - partId: fixedPartIds[i], - z: catalogFixedParts[i].z, - metadataURI: catalogFixedParts[i].metadataURI - }); - unchecked { - ++i; - } - } - } - - slotParts = getEquippedSlotParts( - target_, - tokenId, - assetId, - catalogAddress, - slotPartIds - ); - } - - /** - * @notice Used to retrieve the equipped slot parts. - * @dev The full `EquippedSlotPart` struct looks like this: - * [ - * partId, - * childAssetId, - * z, - * childAddress, - * childId, - * childAssetMetadata, - * partMetadata - * ] - * @param target_ An address of the `IEquippable` smart contract to retrieve the equipped slot parts from. - * @param tokenId ID of the token for which to retrieve the equipped slot parts - * @param assetId ID of the asset on the token to retrieve the equipped slot parts - * @param catalogAddress The address of the catalog to which the given asset belongs to - * @param slotPartIds An array of slot part IDs in the asset for which to retrieve the equipped slot parts - * @return slotParts An array of `EquippedSlotPart` structs representing the equipped slot parts - */ - function getEquippedSlotParts( - IEquippable target_, - uint256 tokenId, - uint64 assetId, - address catalogAddress, - uint64[] memory slotPartIds - ) private view returns (EquippedSlotPart[] memory slotParts) { - slotParts = new EquippedSlotPart[](slotPartIds.length); - uint256 len = slotPartIds.length; - - if (len != 0) { - string memory metadata; - ICatalog.Part[] memory catalogSlotParts = ICatalog(catalogAddress) - .getParts(slotPartIds); - for (uint256 i; i < len; ) { - IEquippable.Equipment memory equipment = target_.getEquipment( - tokenId, - catalogAddress, - slotPartIds[i] - ); - if (equipment.assetId == assetId) { - metadata = IEquippable(equipment.childEquippableAddress) - .getAssetMetadata( - equipment.childId, - equipment.childAssetId - ); - slotParts[i] = EquippedSlotPart({ - partId: slotPartIds[i], - childAssetId: equipment.childAssetId, - z: catalogSlotParts[i].z, - childId: equipment.childId, - childAddress: equipment.childEquippableAddress, - childAssetMetadata: metadata, - partMetadata: catalogSlotParts[i].metadataURI - }); - } else { - slotParts[i] = EquippedSlotPart({ - partId: slotPartIds[i], - childAssetId: uint64(0), - z: catalogSlotParts[i].z, - childId: uint256(0), - childAddress: address(0), - childAssetMetadata: "", - partMetadata: catalogSlotParts[i].metadataURI - }); - } - unchecked { - ++i; - } - } - } - } - - /** - * @notice Used to split slot and fixed parts. - * @param allPartIds[] An array of `Part` IDs containing both, `Slot` and `Fixed` parts - * @param catalogAddress An address of the catalog to which the given `Part`s belong to - * @return slotPartIds An array of IDs of the `Slot` parts included in the `allPartIds` - * @return fixedPartIds An array of IDs of the `Fixed` parts included in the `allPartIds` - */ - function splitSlotAndFixedParts( - uint64[] memory allPartIds, - address catalogAddress - ) - public - view - returns (uint64[] memory slotPartIds, uint64[] memory fixedPartIds) - { - ICatalog.Part[] memory allParts = ICatalog(catalogAddress) - .getParts(allPartIds); - uint256 numFixedParts; - uint256 numSlotParts; - - uint256 numParts = allPartIds.length; - // This for loop is just to discover the right size of the split arrays, since we can't create them dynamically - for (uint256 i; i < numParts; ) { - if (allParts[i].itemType == ICatalog.ItemType.Fixed) - numFixedParts += 1; - // We could just take the numParts - numFixedParts, but it doesn't hurt to double check it's not an uninitialized part: - else if (allParts[i].itemType == ICatalog.ItemType.Slot) - numSlotParts += 1; - unchecked { - ++i; - } - } - - slotPartIds = new uint64[](numSlotParts); - fixedPartIds = new uint64[](numFixedParts); - uint256 slotPartsIndex; - uint256 fixedPartsIndex; - - // This for loop is to actually fill the split arrays - for (uint256 i; i < numParts; ) { - if (allParts[i].itemType == ICatalog.ItemType.Fixed) { - fixedPartIds[fixedPartsIndex] = allPartIds[i]; - fixedPartsIndex += 1; - } else if (allParts[i].itemType == ICatalog.ItemType.Slot) { - slotPartIds[slotPartsIndex] = allPartIds[i]; - slotPartsIndex += 1; - } - unchecked { - ++i; - } - } - } -} diff --git a/assets/eip-6220/contracts/utils/MultiAssetRenderUtils.sol b/assets/eip-6220/contracts/utils/MultiAssetRenderUtils.sol deleted file mode 100644 index 47d8dcbe945ee0..00000000000000 --- a/assets/eip-6220/contracts/utils/MultiAssetRenderUtils.sol +++ /dev/null @@ -1,194 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../IERC5773.sol"; - -error TokenHasNoAssets(); - -/** - * @title MultiAssetRenderUtils - * @author RMRK team - */ -contract MultiAssetRenderUtils { - uint16 private constant _LOWEST_POSSIBLE_PRIORITY = 2**16 - 1; - - /** - * @notice The structure used to display information about an active asset. - * @return id ID of the asset - * @return priority The priority assigned to the asset - * @return metadata The metadata URI of the asset - */ - struct ActiveAsset { - uint64 id; - uint16 priority; - string metadata; - } - - /** - * @notice The structure used to display information about a pending asset. - * @return id ID of the asset - * @return acceptRejectIndex An index to use in order to accept or reject the given asset - * @return replacesAssetWithId ID of the asset that would be replaced if this asset gets accepted - * @return metadata The metadata URI of the asset - */ - struct PendingAsset { - uint64 id; - uint128 acceptRejectIndex; - uint64 replacesAssetWithId; - string metadata; - } - - /** - * @notice Used to get the active assets of the given token. - * @dev The full `ActiveAsset` looks like this: - * [ - * id, - * priority, - * metadata - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the active assets for - * @return struct[] An array of ActiveAssets present on the given token - */ - function getActiveAssets(address target, uint256 tokenId) - public - view - virtual - returns (ActiveAsset[] memory) - { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint16[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - ActiveAsset[] memory activeAssets = new ActiveAsset[](len); - string memory metadata; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - activeAssets[i] = ActiveAsset({ - id: assets[i], - priority: priorities[i], - metadata: metadata - }); - unchecked { - ++i; - } - } - return activeAssets; - } - - /** - * @notice Used to get the pending assets of the given token. - * @dev The full `PendingAsset` looks like this: - * [ - * id, - * acceptRejectIndex, - * replacesAssetWithId, - * metadata - * ] - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the pending assets for - * @return struct[] An array of PendingAssets present on the given token - */ - function getPendingAssets(address target, uint256 tokenId) - public - view - virtual - returns (PendingAsset[] memory) - { - IERC5773 target_ = IERC5773(target); - - uint64[] memory assets = target_.getPendingAssets(tokenId); - uint256 len = assets.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - PendingAsset[] memory pendingAssets = new PendingAsset[](len); - string memory metadata; - uint64 replacesAssetWithId; - for (uint256 i; i < len; ) { - metadata = target_.getAssetMetadata(tokenId, assets[i]); - replacesAssetWithId = target_.getAssetReplacements( - tokenId, - assets[i] - ); - pendingAssets[i] = PendingAsset({ - id: assets[i], - acceptRejectIndex: uint128(i), - replacesAssetWithId: replacesAssetWithId, - metadata: metadata - }); - unchecked { - ++i; - } - } - return pendingAssets; - } - - /** - * @notice Used to retrieve the metadata URI of specified assets in the specified token. - * @dev Requirements: - * - * - `assetIds` must exist. - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token to retrieve the specified assets for - * @param assetIds[] An array of asset IDs for which to retrieve the metadata URIs - * @return string[] An array of metadata URIs belonging to specified assets - */ - function getAssetsById( - address target, - uint256 tokenId, - uint64[] calldata assetIds - ) public view virtual returns (string[] memory) { - IERC5773 target_ = IERC5773(target); - uint256 len = assetIds.length; - string[] memory assets = new string[](len); - for (uint256 i; i < len; ) { - assets[i] = target_.getAssetMetadata(tokenId, assetIds[i]); - unchecked { - ++i; - } - } - return assets; - } - - /** - * @notice Used to retrieve the metadata URI of the specified token's asset with the highest priority. - * @param target Address of the smart contract of the given token - * @param tokenId ID of the token for which to retrieve the metadata URI of the asset with the highest priority - * @return string The metadata URI of the asset with the highest priority - */ - function getTopAssetMetaForToken(address target, uint256 tokenId) - external - view - returns (string memory) - { - IERC5773 target_ = IERC5773(target); - uint16[] memory priorities = target_.getActiveAssetPriorities(tokenId); - uint64[] memory assets = target_.getActiveAssets(tokenId); - uint256 len = priorities.length; - if (len == 0) { - revert TokenHasNoAssets(); - } - - uint16 maxPriority = _LOWEST_POSSIBLE_PRIORITY; - uint64 maxPriorityAsset; - for (uint64 i; i < len; ) { - uint16 currentPrio = priorities[i]; - if (currentPrio < maxPriority) { - maxPriority = currentPrio; - maxPriorityAsset = assets[i]; - } - unchecked { - ++i; - } - } - return target_.getAssetMetadata(tokenId, maxPriorityAsset); - } -} diff --git a/assets/eip-6220/hardhat.config.ts b/assets/eip-6220/hardhat.config.ts deleted file mode 100644 index b00785fd756a8f..00000000000000 --- a/assets/eip-6220/hardhat.config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@nomiclabs/hardhat-etherscan"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.16", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6220/package.json b/assets/eip-6220/package.json deleted file mode 100644 index 3adc2cd2fc4831..00000000000000 --- a/assets/eip-6220/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "erc-6220", - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/test-helpers": "^0.5.15", - "@primitivefi/hardhat-dodoc": "^0.2.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.3", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "solc": "^0.8.9", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "walk-sync": "^3.0.0" - } -} diff --git a/assets/eip-6220/test/catalog.ts b/assets/eip-6220/test/catalog.ts deleted file mode 100644 index a9608d434e903e..00000000000000 --- a/assets/eip-6220/test/catalog.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { CatalogMock } from "../typechain-types"; - -async function catalogFixture(): Promise { - const Catalog = await ethers.getContractFactory("CatalogMock"); - const testCatalog = await Catalog.deploy("ipfs//:meta", "misc"); - await testCatalog.deployed(); - - return testCatalog; -} - -describe("CatalogMock", async () => { - let testCatalog: CatalogMock; - - let addrs: SignerWithAddress[]; - const metadataUriDefault = "src"; - - const noType = 0; - const slotType = 1; - const fixedType = 2; - - const sampleSlotPartData = { - itemType: slotType, - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - - beforeEach(async () => { - [, ...addrs] = await ethers.getSigners(); - testCatalog = await loadFixture(catalogFixture); - }); - - describe("Init Catalog", async function () { - it("has right metadataURI", async function () { - expect(await testCatalog.getMetadataURI()).to.equal("ipfs//:meta"); - }); - - it("has right type", async function () { - expect(await testCatalog.getType()).to.equal("misc"); - }); - - it("supports interface", async function () { - expect(await testCatalog.supportsInterface("0xd912401f")).to.equal(true); - }); - - it("does not support other interfaces", async function () { - expect(await testCatalog.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("add catalog entries", async function () { - it("can add fixed part", async function () { - const partId = 1; - const partData = { - itemType: fixedType, - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - - await testCatalog.addPart({ partId: partId, part: partData }); - expect(await testCatalog.getPart(partId)).to.eql([ - 2, - 0, - [], - metadataUriDefault, - ]); - }); - - it("can add slot part", async function () { - const partId = 2; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - expect(await testCatalog.getPart(partId)).to.eql([ - 1, - 0, - [], - metadataUriDefault, - ]); - }); - - it("can add parts list", async function () { - const partId = 1; - const partId2 = 2; - const partData1 = { - itemType: slotType, - z: 0, - equippable: [], - metadataURI: "src1", - }; - const partData2 = { - itemType: fixedType, - z: 1, - equippable: [], - metadataURI: "src2", - }; - await testCatalog.addPartList([ - { partId: partId, part: partData1 }, - { partId: partId2, part: partData2 }, - ]); - expect(await testCatalog.getParts([partId, partId2])).to.eql([ - [slotType, 0, [], "src1"], - [fixedType, 1, [], "src2"], - ]); - }); - - it("cannot add part with id 0", async function () { - const partId = 0; - await expect( - testCatalog.addPart({ partId: partId, part: sampleSlotPartData }) - ).to.be.revertedWithCustomError(testCatalog, "IdZeroForbidden"); - }); - - it("cannot add part with existing partId", async function () { - const partId = 3; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect( - testCatalog.addPart({ partId: partId, part: sampleSlotPartData }) - ).to.be.revertedWithCustomError(testCatalog, "PartAlreadyExists"); - }); - - it("cannot add part with item type None", async function () { - const partId = 1; - const badPartData = { - itemType: noType, - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - await expect( - testCatalog.addPart({ partId: partId, part: badPartData }) - ).to.be.revertedWithCustomError(testCatalog, "BadConfig"); - }); - - it("cannot add fixed part with equippable addresses", async function () { - const partId = 1; - const badPartData = { - itemType: fixedType, - z: 0, - equippable: [addrs[3].address], - metadataURI: metadataUriDefault, - }; - await expect( - testCatalog.addPart({ partId: partId, part: badPartData }) - ).to.be.revertedWithCustomError(testCatalog, "BadConfig"); - }); - - it("is not equippable if address was not added", async function () { - const partId = 4; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(false); - }); - - it("is equippable if added in the part definition", async function () { - const partId = 1; - const partData = { - itemType: slotType, - z: 0, - equippable: [addrs[1].address, addrs[2].address], - metadataURI: metadataUriDefault, - }; - await testCatalog.addPart({ partId: partId, part: partData }); - expect( - await testCatalog.checkIsEquippable(partId, addrs[2].address) - ).to.eql(true); - }); - - it("is equippable if added afterward", async function () { - const partId = 1; - await expect( - testCatalog.addPart({ partId: partId, part: sampleSlotPartData }) - ) - .to.emit(testCatalog, "AddedPart") - .withArgs( - partId, - sampleSlotPartData.itemType, - sampleSlotPartData.z, - sampleSlotPartData.equippable, - sampleSlotPartData.metadataURI - ); - await expect( - testCatalog.addEquippableAddresses(partId, [addrs[1].address]) - ) - .to.emit(testCatalog, "AddedEquippables") - .withArgs(partId, [addrs[1].address]); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(true); - }); - - it("is equippable if set afterward", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect( - testCatalog.setEquippableAddresses(partId, [addrs[1].address]) - ) - .to.emit(testCatalog, "SetEquippables") - .withArgs(partId, [addrs[1].address]); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(true); - }); - - it("is equippable if set to all", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect(testCatalog.setEquippableToAll(partId)) - .to.emit(testCatalog, "SetEquippableToAll") - .withArgs(partId); - expect(await testCatalog.checkIsEquippableToAll(partId)).to.eql(true); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(true); - }); - - it("cannot add nor set equippable addresses for non existing part", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await expect( - testCatalog.addEquippableAddresses(partId, []) - ).to.be.revertedWithCustomError(testCatalog, "ZeroLengthIdsPassed"); - await expect( - testCatalog.setEquippableAddresses(partId, []) - ).to.be.revertedWithCustomError(testCatalog, "ZeroLengthIdsPassed"); - }); - - it("cannot add nor set empty list of equippable addresses", async function () { - const NonExistingPartId = 1; - await expect( - testCatalog.addEquippableAddresses(NonExistingPartId, [ - addrs[1].address, - ]) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - await expect( - testCatalog.setEquippableAddresses(NonExistingPartId, [ - addrs[1].address, - ]) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - await expect( - testCatalog.setEquippableToAll(NonExistingPartId) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - }); - - it("cannot add nor set equippable addresses to non slot part", async function () { - const fixedPartId = 1; - const partData = { - itemType: fixedType, // This is what we're testing - z: 0, - equippable: [], - metadataURI: metadataUriDefault, - }; - await testCatalog.addPart({ partId: fixedPartId, part: partData }); - await expect( - testCatalog.addEquippableAddresses(fixedPartId, [addrs[1].address]) - ).to.be.revertedWithCustomError(testCatalog, "PartIsNotSlot"); - await expect( - testCatalog.setEquippableAddresses(fixedPartId, [addrs[1].address]) - ).to.be.revertedWithCustomError(testCatalog, "PartIsNotSlot"); - await expect( - testCatalog.setEquippableToAll(fixedPartId) - ).to.be.revertedWithCustomError(testCatalog, "PartIsNotSlot"); - }); - - it("cannot set equippable to all on non existing part", async function () { - const nonExistingPartId = 1; - await expect( - testCatalog.setEquippableToAll(nonExistingPartId) - ).to.be.revertedWithCustomError(testCatalog, "PartDoesNotExist"); - }); - - it("resets equippable to all if addresses are set", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await testCatalog.setEquippableToAll(partId); - - // This should reset it: - testCatalog.setEquippableAddresses(partId, [addrs[1].address]); - expect(await testCatalog.checkIsEquippableToAll(partId)).to.eql(false); - }); - - it("resets equippable to all if addresses are added", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await testCatalog.setEquippableToAll(partId); - - // This should reset it: - await testCatalog.addEquippableAddresses(partId, [addrs[1].address]); - expect(await testCatalog.checkIsEquippableToAll(partId)).to.eql(false); - }); - - it("can reset equippable addresses", async function () { - const partId = 1; - await testCatalog.addPart({ partId: partId, part: sampleSlotPartData }); - await testCatalog.addEquippableAddresses(partId, [ - addrs[1].address, - addrs[2].address, - ]); - - await testCatalog.resetEquippableAddresses(partId); - expect( - await testCatalog.checkIsEquippable(partId, addrs[1].address) - ).to.eql(false); - }); - }); -}); diff --git a/assets/eip-6220/test/equippableFixedParts.ts b/assets/eip-6220/test/equippableFixedParts.ts deleted file mode 100644 index 78c1c394272109..00000000000000 --- a/assets/eip-6220/test/equippableFixedParts.ts +++ /dev/null @@ -1,662 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { - CatalogMock, - EquippableTokenMock, - EquipRenderUtils, -} from "../typechain-types"; - -let addrs: SignerWithAddress[]; - -const partIdForHead1 = 1; -const partIdForHead2 = 2; -const partIdForHead3 = 3; -const partIdForBody1 = 4; -const partIdForBody2 = 5; -const partIdForHair1 = 6; -const partIdForHair2 = 7; -const partIdForHair3 = 8; -const partIdForMaskCatalog1 = 9; -const partIdForMaskCatalog2 = 10; -const partIdForMaskCatalog3 = 11; -const partIdForEars1 = 12; -const partIdForEars2 = 13; -const partIdForHorns1 = 14; -const partIdForHorns2 = 15; -const partIdForHorns3 = 16; -const partIdForMaskCatalogEquipped1 = 17; -const partIdForMaskCatalogEquipped2 = 18; -const partIdForMaskCatalogEquipped3 = 19; -const partIdForEarsEquipped1 = 20; -const partIdForEarsEquipped2 = 21; -const partIdForHornsEquipped1 = 22; -const partIdForHornsEquipped2 = 23; -const partIdForHornsEquipped3 = 24; -const partIdForMask = 25; - -const uniqueNeons = 10; -const uniqueMasks = 4; -// Ids could be the same since they are different collections, but to avoid log problems we have them unique -const neons: number[] = []; -const masks: number[] = []; - -const neonResIds = [100, 101, 102, 103, 104]; -const maskAssetsFull = [1, 2, 3, 4]; // Must match the total of uniqueAssets -const maskAssetsEquip = [5, 6, 7, 8]; // Must match the total of uniqueAssets -const maskpableGroupId = 1; // Assets to equip will all use this - -enum ItemType { - None, - Slot, - Fixed, -} - -let nextTokenId = 1; -let nextChildTokenId = 100; - -async function mint(token: EquippableTokenMock, to: string): Promise { - const tokenId = nextTokenId; - nextTokenId++; - await token["mint(address,uint256)"](to, tokenId); - return tokenId; -} - -async function nestMint( - token: EquippableTokenMock, - to: string, - parentId: number -): Promise { - const childTokenId = nextChildTokenId; - nextChildTokenId++; - await token["nestMint(address,uint256,uint256)"](to, childTokenId, parentId); - return childTokenId; -} - -async function setupContextForParts( - catalog: CatalogMock, - neon: EquippableTokenMock, - mask: EquippableTokenMock -) { - [, ...addrs] = await ethers.getSigners(); - await setupCatalog(); - - await mintNeons(); - await mintMasks(); - - await addAssetsToNeon(); - await addAssetsToMask(); - - async function setupCatalog(): Promise { - const partForHead1 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://head1.png", - }; - const partForHead2 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://head2.png", - }; - const partForHead3 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://head3.png", - }; - const partForBody1 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://body1.png", - }; - const partForBody2 = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "ipfs://body2.png", - }; - const partForHair1 = { - itemType: ItemType.Fixed, - z: 2, - equippable: [], - metadataURI: "ipfs://hair1.png", - }; - const partForHair2 = { - itemType: ItemType.Fixed, - z: 2, - equippable: [], - metadataURI: "ipfs://hair2.png", - }; - const partForHair3 = { - itemType: ItemType.Fixed, - z: 2, - equippable: [], - metadataURI: "ipfs://hair3.png", - }; - const partForMaskCatalog1 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalog1.png", - }; - const partForMaskCatalog2 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalog2.png", - }; - const partForMaskCatalog3 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalog3.png", - }; - const partForEars1 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://ears1.png", - }; - const partForEars2 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://ears2.png", - }; - const partForHorns1 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://horn1.png", - }; - const partForHorns2 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://horn2.png", - }; - const partForHorns3 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://horn3.png", - }; - const partForMaskCatalogEquipped1 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalogEquipped1.png", - }; - const partForMaskCatalogEquipped2 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalogEquipped2.png", - }; - const partForMaskCatalogEquipped3 = { - itemType: ItemType.Fixed, - z: 3, - equippable: [], - metadataURI: "ipfs://maskCatalogEquipped3.png", - }; - const partForEarsEquipped1 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://earsEquipped1.png", - }; - const partForEarsEquipped2 = { - itemType: ItemType.Fixed, - z: 4, - equippable: [], - metadataURI: "ipfs://earsEquipped2.png", - }; - const partForHornsEquipped1 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://hornEquipped1.png", - }; - const partForHornsEquipped2 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://hornEquipped2.png", - }; - const partForHornsEquipped3 = { - itemType: ItemType.Fixed, - z: 5, - equippable: [], - metadataURI: "ipfs://hornEquipped3.png", - }; - const partForMask = { - itemType: ItemType.Slot, - z: 2, - equippable: [mask.address], - metadataURI: "", - }; - - await catalog.addPartList([ - { partId: partIdForHead1, part: partForHead1 }, - { partId: partIdForHead2, part: partForHead2 }, - { partId: partIdForHead3, part: partForHead3 }, - { partId: partIdForBody1, part: partForBody1 }, - { partId: partIdForBody2, part: partForBody2 }, - { partId: partIdForHair1, part: partForHair1 }, - { partId: partIdForHair2, part: partForHair2 }, - { partId: partIdForHair3, part: partForHair3 }, - { partId: partIdForMaskCatalog1, part: partForMaskCatalog1 }, - { partId: partIdForMaskCatalog2, part: partForMaskCatalog2 }, - { partId: partIdForMaskCatalog3, part: partForMaskCatalog3 }, - { partId: partIdForEars1, part: partForEars1 }, - { partId: partIdForEars2, part: partForEars2 }, - { partId: partIdForHorns1, part: partForHorns1 }, - { partId: partIdForHorns2, part: partForHorns2 }, - { partId: partIdForHorns3, part: partForHorns3 }, - { - partId: partIdForMaskCatalogEquipped1, - part: partForMaskCatalogEquipped1, - }, - { - partId: partIdForMaskCatalogEquipped2, - part: partForMaskCatalogEquipped2, - }, - { - partId: partIdForMaskCatalogEquipped3, - part: partForMaskCatalogEquipped3, - }, - { partId: partIdForEarsEquipped1, part: partForEarsEquipped1 }, - { partId: partIdForEarsEquipped2, part: partForEarsEquipped2 }, - { partId: partIdForHornsEquipped1, part: partForHornsEquipped1 }, - { partId: partIdForHornsEquipped2, part: partForHornsEquipped2 }, - { partId: partIdForHornsEquipped3, part: partForHornsEquipped3 }, - { partId: partIdForMask, part: partForMask }, - ]); - } - - async function mintNeons(): Promise { - // This array is reused, so we "empty" it before - neons.length = 0; - // Using only first 3 addresses to mint - for (let i = 0; i < uniqueNeons; i++) { - const newId = await mint(neon, addrs[i % 3].address); - neons.push(newId); - } - } - - async function mintMasks(): Promise { - // This array is reused, so we "empty" it before - masks.length = 0; - // Mint one weapon to neon - for (let i = 0; i < uniqueNeons; i++) { - const newId = await nestMint(mask, neon.address, neons[i]); - masks.push(newId); - await neon - .connect(addrs[i % 3]) - .acceptChild(neons[i], 0, mask.address, newId); - } - } - - async function addAssetsToNeon(): Promise { - await neon.addEquippableAssetEntry( - neonResIds[0], - 0, - catalog.address, - "ipfs:neonRes/1", - [partIdForHead1, partIdForBody1, partIdForHair1, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[1], - 0, - catalog.address, - "ipfs:neonRes/2", - [partIdForHead2, partIdForBody2, partIdForHair2, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[2], - 0, - catalog.address, - "ipfs:neonRes/3", - [partIdForHead3, partIdForBody1, partIdForHair3, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[3], - 0, - catalog.address, - "ipfs:neonRes/4", - [partIdForHead1, partIdForBody2, partIdForHair2, partIdForMask] - ); - await neon.addEquippableAssetEntry( - neonResIds[4], - 0, - catalog.address, - "ipfs:neonRes/1", - [partIdForHead2, partIdForBody1, partIdForHair1, partIdForMask] - ); - - for (let i = 0; i < uniqueNeons; i++) { - await neon.addAssetToToken( - neons[i], - neonResIds[i % neonResIds.length], - 0 - ); - await neon - .connect(addrs[i % 3]) - .acceptAsset(neons[i], 0, neonResIds[i % neonResIds.length]); - } - } - - async function addAssetsToMask(): Promise { - // Assets for full view, composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsFull[0], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[0]}`, - [partIdForMaskCatalog1, partIdForHorns1, partIdForEars1] - ); - await mask.addEquippableAssetEntry( - maskAssetsFull[1], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[1]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars2] - ); - await mask.addEquippableAssetEntry( - maskAssetsFull[2], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[2]}`, - [partIdForMaskCatalog3, partIdForHorns1, partIdForEars2] - ); - await mask.addEquippableAssetEntry( - maskAssetsFull[3], - 0, // Not meant to equip - catalog.address, // Not meant to equip, but catalog needed for parts - `ipfs:weapon/full/${maskAssetsFull[3]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars1] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[0], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[0]}`, - [partIdForMaskCatalog1, partIdForHorns1, partIdForEars1] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[1], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[1]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars2] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[2], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[2]}`, - [partIdForMaskCatalog3, partIdForHorns1, partIdForEars2] - ); - - // Assets for equipping view, also composed with fixed parts - await mask.addEquippableAssetEntry( - maskAssetsEquip[3], - maskpableGroupId, - catalog.address, - `ipfs:weapon/equip/${maskAssetsEquip[3]}`, - [partIdForMaskCatalog2, partIdForHorns2, partIdForEars1] - ); - - // Can be equipped into neons - await mask.setValidParentForEquippableGroup( - maskpableGroupId, - neon.address, - partIdForMask - ); - - // Add 2 assets to each weapon, one full, one for equip - // There are 10 weapon tokens for 4 unique assets so we use % - for (let i = 0; i < masks.length; i++) { - await mask.addAssetToToken(masks[i], maskAssetsFull[i % uniqueMasks], 0); - await mask.addAssetToToken(masks[i], maskAssetsEquip[i % uniqueMasks], 0); - await mask - .connect(addrs[i % 3]) - .acceptAsset(masks[i], 0, maskAssetsFull[i % uniqueMasks]); - await mask - .connect(addrs[i % 3]) - .acceptAsset(masks[i], 0, maskAssetsEquip[i % uniqueMasks]); - } - } -} - -async function partsFixture() { - const baseSymbol = "NCB"; - const baseType = "mixed"; - - const baseFactory = await ethers.getContractFactory("CatalogMock"); - const equipFactory = await ethers.getContractFactory("EquippableTokenMock"); - const viewFactory = await ethers.getContractFactory("EquipRenderUtils"); - - // Catalog - const catalog = await baseFactory.deploy(baseSymbol, baseType); - await catalog.deployed(); - - // Neon token - const neon = await equipFactory.deploy(); - await neon.deployed(); - - // Weapon - const mask = await equipFactory.deploy(); - await mask.deployed(); - - // View - const view = await viewFactory.deploy(); - await view.deployed(); - - await setupContextForParts(catalog, neon, mask); - return { catalog, neon, mask, view }; -} - -// The general idea is having these tokens: Neon and Mask -// Masks can be equipped into Neons. -// All use a single catalog. -// Neon will use an asset per token, which uses fixed parts to compose the body -// Mask will have 2 assets per weapon, one for full view, one for equipping. Both are composed using fixed parts -describe("EquippableTokenMock with Parts", async () => { - let catalog: CatalogMock; - let neon: EquippableTokenMock; - let mask: EquippableTokenMock; - let view: EquipRenderUtils; - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [, ...addrs] = await ethers.getSigners(); - ({ catalog, neon, mask, view } = await loadFixture(partsFixture)); - }); - - describe("Equip", async function () { - it("can equip weapon", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = maskAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - neon - .connect(addrs[0]) - .equip([ - neons[0], - childIndex, - neonResIds[0], - partIdForMask, - weaponResId, - ]) - ) - .to.emit(neon, "ChildAssetEquipped") - .withArgs( - neons[0], - neonResIds[0], - partIdForMask, - masks[0], - mask.address, - weaponResId - ); - - // All part slots are included on the response: - const expectedSlots = [bn(partIdForMask)]; - const expectedEquips = [ - [bn(neonResIds[0]), bn(weaponResId), bn(masks[0]), mask.address], - ]; - expect( - await view.getEquipped(neon.address, neons[0], neonResIds[0]) - ).to.eql([expectedSlots, expectedEquips]); - - // Child is marked as equipped: - expect( - await neon.isChildEquipped(neons[0], mask.address, masks[0]) - ).to.eql(true); - }); - - it("cannot equip non existing child in slot", async function () { - // Weapon is child on index 0 - const badChildIndex = 3; - const weaponResId = maskAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - neon - .connect(addrs[0]) - .equip([ - neons[0], - badChildIndex, - neonResIds[0], - partIdForMask, - weaponResId, - ]) - ).to.be.reverted; // Bad index - }); - }); - - describe("Compose", async function () { - it("can compose all parts for neon", async function () { - const childIndex = 0; - const weaponResId = maskAssetsEquip[0]; // This asset is assigned to weapon first weapon - await neon - .connect(addrs[0]) - .equip([ - neons[0], - childIndex, - neonResIds[0], - partIdForMask, - weaponResId, - ]); - - const expectedFixedParts = [ - [ - bn(partIdForHead1), // partId - 1, // z - "ipfs://head1.png", // metadataURI - ], - [ - bn(partIdForBody1), // partId - 1, // z - "ipfs://body1.png", // metadataURI - ], - [ - bn(partIdForHair1), // partId - 2, // z - "ipfs://hair1.png", // metadataURI - ], - ]; - const expectedSlotParts = [ - [ - bn(partIdForMask), // partId - bn(maskAssetsEquip[0]), // childAssetId - 2, // z - mask.address, // childAddress - bn(masks[0]), // childTokenId - "ipfs:weapon/equip/5", // childAssetMetadata - "", // partMetadata - ], - ]; - const allAssets = await view.composeEquippables( - neon.address, - neons[0], - neonResIds[0] - ); - expect(allAssets).to.eql([ - "ipfs:neonRes/1", // metadataURI - bn(0), // equippableGroupId - catalog.address, // baseAddress, - expectedFixedParts, - expectedSlotParts, - ]); - }); - - it("can compose all parts for mask", async function () { - const expectedFixedParts = [ - [ - bn(partIdForMaskCatalog1), // partId - 3, // z - "ipfs://maskCatalog1.png", // metadataURI - ], - [ - bn(partIdForHorns1), // partId - 5, // z - "ipfs://horn1.png", // metadataURI - ], - [ - bn(partIdForEars1), // partId - 4, // z - "ipfs://ears1.png", // metadataURI - ], - ]; - const allAssets = await view.composeEquippables( - mask.address, - masks[0], - maskAssetsEquip[0] - ); - expect(allAssets).to.eql([ - `ipfs:weapon/equip/${maskAssetsEquip[0]}`, // metadataURI - bn(maskpableGroupId), // equippableGroupId - catalog.address, // baseAddress - expectedFixedParts, - [], - ]); - }); - - it("cannot compose equippables for neon with not associated asset", async function () { - const wrongResId = maskAssetsEquip[1]; - await expect( - view.composeEquippables(mask.address, masks[0], wrongResId) - ).to.be.revertedWithCustomError(mask, "TokenDoesNotHaveAsset"); - }); - - it("cannot compose equippables for mask for asset with no catalog", async function () { - const noCatalogAssetId = 99; - await mask.addEquippableAssetEntry( - noCatalogAssetId, - 0, // Not meant to equip - ethers.constants.AddressZero, // Not meant to equip - `ipfs:weapon/full/customAsset.png`, - [] - ); - await mask.addAssetToToken(masks[0], noCatalogAssetId, 0); - await mask.connect(addrs[0]).acceptAsset(masks[0], 0, noCatalogAssetId); - await expect( - view.composeEquippables(mask.address, masks[0], noCatalogAssetId) - ).to.be.revertedWithCustomError(view, "NotComposableAsset"); - }); - }); -}); - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} diff --git a/assets/eip-6220/test/equippableSlotParts.ts b/assets/eip-6220/test/equippableSlotParts.ts deleted file mode 100644 index 8bb59ff4b7714b..00000000000000 --- a/assets/eip-6220/test/equippableSlotParts.ts +++ /dev/null @@ -1,1130 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { - CatalogMock, - EquippableTokenMock, - EquipRenderUtils, -} from "../typechain-types"; - -const partIdForBody = 1; -const partIdForWeapon = 2; -const partIdForWeaponGem = 3; -const partIdForBackground = 4; - -const uniqueSnakeSoldiers = 10; -const uniqueWeapons = 4; -// const uniqueWeaponGems = 2; -// const uniqueBackgrounds = 3; - -const snakeSoldiersIds: number[] = []; -const weaponsIds: number[] = []; -const weaponGemsIds: number[] = []; -const backgroundsIds: number[] = []; - -const soldierResId = 100; -const weaponAssetsFull = [1, 2, 3, 4]; // Must match the total of uniqueAssets -const weaponAssetsEquip = [5, 6, 7, 8]; // Must match the total of uniqueAssets -const weaponGemAssetFull = 101; -const weaponGemAssetEquip = 102; -const backgroundAssetId = 200; - -enum ItemType { - None, - Slot, - Fixed, -} - -let addrs: SignerWithAddress[]; - -let nextTokenId = 1; -let nextChildTokenId = 100; - -async function mint(token: EquippableTokenMock, to: string): Promise { - const tokenId = nextTokenId; - nextTokenId++; - await token["mint(address,uint256)"](to, tokenId); - return tokenId; -} - -async function nestMint( - token: EquippableTokenMock, - to: string, - parentId: number -): Promise { - const childTokenId = nextChildTokenId; - nextChildTokenId++; - await token["nestMint(address,uint256,uint256)"](to, childTokenId, parentId); - return childTokenId; -} - -async function setupContextForSlots( - catalog: CatalogMock, - soldier: EquippableTokenMock, - weapon: EquippableTokenMock, - weaponGem: EquippableTokenMock, - background: EquippableTokenMock -) { - [, ...addrs] = await ethers.getSigners(); - - await setupCatalog(); - - await mintSnakeSoldiers(); - await mintWeapons(); - await mintWeaponGems(); - await mintBackgrounds(); - - await addAssetsToSoldier(); - await addAssetsToWeapon(); - await addAssetsToWeaponGem(); - await addAssetsToBackground(); - - return { - catalog, - soldier, - weapon, - background, - }; - - async function setupCatalog(): Promise { - const partForBody = { - itemType: ItemType.Fixed, - z: 1, - equippable: [], - metadataURI: "genericBody.png", - }; - const partForWeapon = { - itemType: ItemType.Slot, - z: 2, - equippable: [weapon.address], - metadataURI: "", - }; - const partForWeaponGem = { - itemType: ItemType.Slot, - z: 3, - equippable: [weaponGem.address], - metadataURI: "noGem.png", - }; - const partForBackground = { - itemType: ItemType.Slot, - z: 0, - equippable: [background.address], - metadataURI: "noBackground.png", - }; - - await catalog.addPartList([ - { partId: partIdForBody, part: partForBody }, - { partId: partIdForWeapon, part: partForWeapon }, - { partId: partIdForWeaponGem, part: partForWeaponGem }, - { partId: partIdForBackground, part: partForBackground }, - ]); - } - - async function mintSnakeSoldiers(): Promise { - // This array is reused, so we "empty" it before - snakeSoldiersIds.length = 0; - // Using only first 3 addresses to mint - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await mint(soldier, addrs[i % 3].address); - snakeSoldiersIds.push(newId); - } - } - - async function mintWeapons(): Promise { - // This array is reused, so we "empty" it before - weaponsIds.length = 0; - // Mint one weapon to soldier - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await nestMint( - weapon, - soldier.address, - snakeSoldiersIds[i] - ); - weaponsIds.push(newId); - await soldier - .connect(addrs[i % 3]) - .acceptChild(snakeSoldiersIds[i], 0, weapon.address, newId); - } - } - - async function mintWeaponGems(): Promise { - // This array is reused, so we "empty" it before - weaponGemsIds.length = 0; - // Mint one weapon gem for each weapon on each soldier - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await nestMint(weaponGem, weapon.address, weaponsIds[i]); - weaponGemsIds.push(newId); - await weapon - .connect(addrs[i % 3]) - .acceptChild(weaponsIds[i], 0, weaponGem.address, newId); - } - } - - async function mintBackgrounds(): Promise { - // This array is reused, so we "empty" it before - backgroundsIds.length = 0; - // Mint one background to soldier - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - const newId = await nestMint( - background, - soldier.address, - snakeSoldiersIds[i] - ); - backgroundsIds.push(newId); - await soldier - .connect(addrs[i % 3]) - .acceptChild(snakeSoldiersIds[i], 0, background.address, newId); - } - } - - async function addAssetsToSoldier(): Promise { - await soldier.addEquippableAssetEntry( - soldierResId, - 0, - catalog.address, - "ipfs:soldier/", - [partIdForBody, partIdForWeapon, partIdForBackground] - ); - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - await soldier.addAssetToToken(snakeSoldiersIds[i], soldierResId, 0); - await soldier - .connect(addrs[i % 3]) - .acceptAsset(snakeSoldiersIds[i], 0, soldierResId); - } - } - - async function addAssetsToWeapon(): Promise { - const equippableGroupId = 1; // Assets to equip will both use this - - for (let i = 0; i < weaponAssetsFull.length; i++) { - await weapon.addEquippableAssetEntry( - weaponAssetsFull[i], - 0, // Not meant to equip - ethers.constants.AddressZero, // Not meant to equip - `ipfs:weapon/full/${weaponAssetsFull[i]}`, - [] - ); - } - for (let i = 0; i < weaponAssetsEquip.length; i++) { - await weapon.addEquippableAssetEntry( - weaponAssetsEquip[i], - equippableGroupId, - catalog.address, - `ipfs:weapon/equip/${weaponAssetsEquip[i]}`, - [partIdForWeaponGem] - ); - } - - // Can be equipped into snakeSoldiers - await weapon.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partIdForWeapon - ); - - // Add 2 assets to each weapon, one full, one for equip - // There are 10 weapon tokens for 4 unique assets so we use % - for (let i = 0; i < weaponsIds.length; i++) { - await weapon.addAssetToToken( - weaponsIds[i], - weaponAssetsFull[i % uniqueWeapons], - 0 - ); - await weapon.addAssetToToken( - weaponsIds[i], - weaponAssetsEquip[i % uniqueWeapons], - 0 - ); - await weapon - .connect(addrs[i % 3]) - .acceptAsset(weaponsIds[i], 0, weaponAssetsFull[i % uniqueWeapons]); - await weapon - .connect(addrs[i % 3]) - .acceptAsset(weaponsIds[i], 0, weaponAssetsEquip[i % uniqueWeapons]); - } - } - - async function addAssetsToWeaponGem(): Promise { - const equippableGroupId = 1; // Assets to equip will use this - await weaponGem.addEquippableAssetEntry( - weaponGemAssetFull, - 0, // Not meant to equip - ethers.constants.AddressZero, // Not meant to equip - "ipfs:weagponGem/full/", - [] - ); - await weaponGem.addEquippableAssetEntry( - weaponGemAssetEquip, - equippableGroupId, - catalog.address, - "ipfs:weagponGem/equip/", - [] - ); - await weaponGem.setValidParentForEquippableGroup( - // Can be equipped into weapons - equippableGroupId, - weapon.address, - partIdForWeaponGem - ); - - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - await weaponGem.addAssetToToken(weaponGemsIds[i], weaponGemAssetFull, 0); - await weaponGem.addAssetToToken(weaponGemsIds[i], weaponGemAssetEquip, 0); - await weaponGem - .connect(addrs[i % 3]) - .acceptAsset(weaponGemsIds[i], 0, weaponGemAssetFull); - await weaponGem - .connect(addrs[i % 3]) - .acceptAsset(weaponGemsIds[i], 0, weaponGemAssetEquip); - } - } - - async function addAssetsToBackground(): Promise { - const equippableGroupId = 1; // Assets to equip will use this - await background.addEquippableAssetEntry( - backgroundAssetId, - equippableGroupId, - catalog.address, - "ipfs:background/", - [] - ); - // Can be equipped into snakeSoldiers - await background.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partIdForBackground - ); - - for (let i = 0; i < uniqueSnakeSoldiers; i++) { - await background.addAssetToToken(backgroundsIds[i], backgroundAssetId, 0); - await background - .connect(addrs[i % 3]) - .acceptAsset(backgroundsIds[i], 0, backgroundAssetId); - } - } -} - -async function slotsFixture() { - const catalogSymbol = "SSB"; - const catalogType = "mixed"; - - const catalogFactory = await ethers.getContractFactory("CatalogMock"); - const equipFactory = await ethers.getContractFactory("EquippableTokenMock"); - const viewFactory = await ethers.getContractFactory("EquipRenderUtils"); - - // View - const view = await viewFactory.deploy(); - await view.deployed(); - - // Catalog - const catalog = ( - await catalogFactory.deploy(catalogSymbol, catalogType) - ); - await catalog.deployed(); - - // Soldier token - const soldier = await equipFactory.deploy(); - await soldier.deployed(); - - // Weapon - const weapon = await equipFactory.deploy(); - await weapon.deployed(); - - // Weapon Gem - const weaponGem = await equipFactory.deploy(); - await weaponGem.deployed(); - - // Background - const background = await equipFactory.deploy(); - await background.deployed(); - - await setupContextForSlots(catalog, soldier, weapon, weaponGem, background); - - return { catalog, soldier, weapon, weaponGem, background, view }; -} - -// The general idea is having these tokens: Soldier, Weapon, WeaponGem and Background. -// Weapon and Background can be equipped into Soldier. WeaponGem can be equipped into Weapon -// All use a single catalog. -// Soldier will use a single enumerated fixed asset for simplicity -// Weapon will have 2 assets per weapon, one for full view, one for equipping -// Background will have a single asset for each, it can be used as full view and to equip -// Weapon Gems will have 2 enumerated assets, one for full view, one for equipping. -describe("EquippableTokenMock with Slots", async () => { - let catalog: CatalogMock; - let soldier: EquippableTokenMock; - let weapon: EquippableTokenMock; - let weaponGem: EquippableTokenMock; - let background: EquippableTokenMock; - let view: EquipRenderUtils; - - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [, ...addrs] = await ethers.getSigners(); - ({ catalog, soldier, weapon, weaponGem, background, view } = - await loadFixture(slotsFixture)); - }); - - it("can support IERC6220", async function () { - expect(await soldier.supportsInterface("0x28bc9ae4")).to.equal(true); - }); - - describe("Validations", async function () { - it("can validate equips of weapons into snakeSoldiers", async function () { - // This asset is not equippable - expect( - await weapon.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - weaponsIds[0], - weaponAssetsFull[0], - partIdForWeapon - ) - ).to.eql(false); - - // This asset is equippable into weapon part - expect( - await weapon.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - weaponsIds[0], - weaponAssetsEquip[0], - partIdForWeapon - ) - ).to.eql(true); - - // This asset is NOT equippable into weapon gem part - expect( - await weapon.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - weaponsIds[0], - weaponAssetsEquip[0], - partIdForWeaponGem - ) - ).to.eql(false); - }); - - it("can validate equips of weapon gems into weapons", async function () { - // This asset is not equippable - expect( - await weaponGem.canTokenBeEquippedWithAssetIntoSlot( - weapon.address, - weaponGemsIds[0], - weaponGemAssetFull, - partIdForWeaponGem - ) - ).to.eql(false); - - // This asset is equippable into weapon gem slot - expect( - await weaponGem.canTokenBeEquippedWithAssetIntoSlot( - weapon.address, - weaponGemsIds[0], - weaponGemAssetEquip, - partIdForWeaponGem - ) - ).to.eql(true); - - // This asset is NOT equippable into background slot - expect( - await weaponGem.canTokenBeEquippedWithAssetIntoSlot( - weapon.address, - weaponGemsIds[0], - weaponGemAssetEquip, - partIdForBackground - ) - ).to.eql(false); - }); - - it("can validate equips of backgrounds into snakeSoldiers", async function () { - // This asset is equippable into background slot - expect( - await background.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - backgroundsIds[0], - backgroundAssetId, - partIdForBackground - ) - ).to.eql(true); - - // This asset is NOT equippable into weapon slot - expect( - await background.canTokenBeEquippedWithAssetIntoSlot( - soldier.address, - backgroundsIds[0], - backgroundAssetId, - partIdForWeapon - ) - ).to.eql(false); - }); - }); - - describe("Equip", async function () { - it("can equip weapon", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await equipWeaponAndCheckFromAddress( - soldierOwner, - childIndex, - weaponResId - ); - }); - - it("can equip weapon if approved", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const approved = addrs[1]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(soldierOwner) - .approve(approved.address, snakeSoldiersIds[0]); - await equipWeaponAndCheckFromAddress(approved, childIndex, weaponResId); - }); - - it("can equip weapon if approved for all", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const approved = addrs[1]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(soldierOwner) - .setApprovalForAll(approved.address, true); - await equipWeaponAndCheckFromAddress(approved, childIndex, weaponResId); - }); - - it("can equip weapon and background", async function () { - // Weapon is child on index 0, background on index 1 - const weaponChildIndex = 0; - const backgroundChildIndex = 1; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - weaponChildIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - backgroundChildIndex, - soldierResId, - partIdForBackground, - backgroundAssetId, - ]); - - const expectedSlots = [bn(partIdForWeapon), bn(partIdForBackground)]; - const expectedEquips = [ - [bn(soldierResId), bn(weaponResId), bn(weaponsIds[0]), weapon.address], - [ - bn(soldierResId), - bn(backgroundAssetId), - bn(backgroundsIds[0]), - background.address, - ], - ]; - expect( - await view.getEquipped( - soldier.address, - snakeSoldiersIds[0], - soldierResId - ) - ).to.eql([expectedSlots, expectedEquips]); - - // Children are marked as equipped: - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - weapon.address, - weaponsIds[0] - ) - ).to.eql(true); - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - background.address, - backgroundsIds[0] - ) - ).to.eql(true); - }); - - it("cannot equip non existing child in slot (weapon in background)", async function () { - // Weapon is child on index 0, background on index 1 - const badChildIndex = 3; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - badChildIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.reverted; // Bad index - }); - - it("cannot set a valid equippable group with id 0", async function () { - const equippableGroupId = 0; - // The malicious child indicates it can be equipped into soldier: - await expect( - weaponGem.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partIdForWeaponGem - ) - ).to.be.revertedWithCustomError(weaponGem, "IdZeroForbidden"); - }); - - it("cannot set a valid equippable group with part id 0", async function () { - const equippableGroupId = 1; - const partId = 0; - // The malicious child indicates it can be equipped into soldier: - await expect( - weaponGem.setValidParentForEquippableGroup( - equippableGroupId, - soldier.address, - partId - ) - ).to.be.revertedWithCustomError(weaponGem, "IdZeroForbidden"); - }); - - it("cannot equip into a slot not set on the parent asset (gem into soldier)", async function () { - const soldierOwner = addrs[0]; - const soldierId = snakeSoldiersIds[0]; - const childIndex = 2; - - const newWeaponGemId = await nestMint( - weaponGem, - soldier.address, - soldierId - ); - await soldier - .connect(soldierOwner) - .acceptChild(soldierId, 0, weaponGem.address, newWeaponGemId); - - // Add assets to weapon - await weaponGem.addAssetToToken(newWeaponGemId, weaponGemAssetFull, 0); - await weaponGem.addAssetToToken(newWeaponGemId, weaponGemAssetEquip, 0); - await weaponGem - .connect(soldierOwner) - .acceptAsset(newWeaponGemId, 0, weaponGemAssetFull); - await weaponGem - .connect(soldierOwner) - .acceptAsset(newWeaponGemId, 0, weaponGemAssetEquip); - - // The malicious child indicates it can be equipped into soldier: - await weaponGem.setValidParentForEquippableGroup( - 1, // equippableGroupId for gems - soldier.address, - partIdForWeaponGem - ); - - // Weapon is child on index 0, background on index 1 - await expect( - soldier - .connect(addrs[0]) - .equip([ - soldierId, - childIndex, - soldierResId, - partIdForWeaponGem, - weaponGemAssetEquip, - ]) - ).to.be.revertedWithCustomError(soldier, "TargetAssetCannotReceiveSlot"); - }); - - it("cannot equip wrong child in slot (weapon in background)", async function () { - // Weapon is child on index 0, background on index 1 - const backgroundChildIndex = 1; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - backgroundChildIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "TokenCannotBeEquippedWithAssetIntoSlot" - ); - }); - - it("cannot equip child in wrong slot (weapon in background)", async function () { - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForBackground, - weaponResId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "TokenCannotBeEquippedWithAssetIntoSlot" - ); - }); - - it("cannot equip child with wrong asset (weapon in background)", async function () { - const childIndex = 0; - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - backgroundAssetId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "TokenCannotBeEquippedWithAssetIntoSlot" - ); - }); - - it("cannot equip if not owner", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await expect( - soldier - .connect(addrs[1]) // Owner is addrs[0] - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.revertedWithCustomError(soldier, "ERC721NotApprovedOrOwner"); - }); - - it("cannot equip 2 children into the same slot", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - const weaponAssetIndex = 3; - await mintWeaponToSoldier( - addrs[0], - snakeSoldiersIds[0], - weaponAssetIndex - ); - - const newWeaponChildIndex = 2; - const newWeaponResId = weaponAssetsEquip[weaponAssetIndex]; - await expect( - soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - newWeaponChildIndex, - soldierResId, - partIdForWeapon, - newWeaponResId, - ]) - ).to.be.revertedWithCustomError(soldier, "SlotAlreadyUsed"); - }); - - it("cannot equip if not intented on catalog", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - - // Remove equippable addresses for part. - await catalog.resetEquippableAddresses(partIdForWeapon); - await expect( - soldier - .connect(addrs[0]) // Owner is addrs[0] - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ).to.be.revertedWithCustomError( - soldier, - "EquippableEquipNotAllowedByCatalog" - ); - }); - }); - - describe("Unequip", async function () { - it("can unequip", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await unequipWeaponAndCheckFromAddress(soldierOwner); - }); - - it("can unequip if approved", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - const approved = addrs[1]; - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await soldier - .connect(soldierOwner) - .approve(approved.address, snakeSoldiersIds[0]); - await unequipWeaponAndCheckFromAddress(approved); - }); - - it("can unequip if approved for all", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - const approved = addrs[1]; - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await soldier - .connect(soldierOwner) - .setApprovalForAll(approved.address, true); - await unequipWeaponAndCheckFromAddress(approved); - }); - - it("cannot unequip if not equipped", async function () { - await expect( - soldier - .connect(addrs[0]) - .unequip(snakeSoldiersIds[0], soldierResId, partIdForWeapon) - ).to.be.revertedWithCustomError(soldier, "NotEquipped"); - }); - - it("cannot unequip if not owner", async function () { - // Weapon is child on index 0, background on index 1 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await expect( - soldier - .connect(addrs[1]) - .unequip(snakeSoldiersIds[0], soldierResId, partIdForWeapon) - ).to.be.revertedWithCustomError(soldier, "ERC721NotApprovedOrOwner"); - }); - }); - - describe("Transfer equipped", async function () { - it("can unequip and transfer child", async function () { - // Weapon is child on index 0, background on index 1 - const soldierOwner = addrs[0]; - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - - await soldier - .connect(soldierOwner) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await unequipWeaponAndCheckFromAddress(soldierOwner); - await soldier - .connect(soldierOwner) - .transferChild( - snakeSoldiersIds[0], - soldierOwner.address, - 0, - childIndex, - weapon.address, - weaponsIds[0], - false, - "0x" - ); - }); - - it("child transfer fails if child is equipped", async function () { - const soldierOwner = addrs[0]; - // Weapon is child on index 0 - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - await expect( - soldier - .connect(soldierOwner) - .transferChild( - snakeSoldiersIds[0], - soldierOwner.address, - 0, - childIndex, - weapon.address, - weaponsIds[0], - false, - "0x" - ) - ).to.be.revertedWithCustomError(weapon, "MustUnequipFirst"); - }); - }); - - describe("Compose", async function () { - it("can compose equippables for soldier", async function () { - const childIndex = 0; - const weaponResId = weaponAssetsEquip[0]; // This asset is assigned to weapon first weapon - await soldier - .connect(addrs[0]) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]); - - const expectedFixedParts = [ - [ - bn(partIdForBody), // partId - 1, // z - "genericBody.png", // metadataURI - ], - ]; - const expectedSlotParts = [ - [ - bn(partIdForWeapon), // partId - bn(weaponAssetsEquip[0]), // childAssetId - 2, // z - weapon.address, // childAddress - bn(weaponsIds[0]), // childTokenId - "ipfs:weapon/equip/5", // childAssetMetadata - "", // partMetadata - ], - [ - // Nothing on equipped on background slot: - bn(partIdForBackground), // partId - bn(0), // childAssetId - 0, // z - ethers.constants.AddressZero, // childAddress - bn(0), // childTokenId - "", // childAssetMetadata - "noBackground.png", // partMetadata - ], - ]; - const allAssets = await view.composeEquippables( - soldier.address, - snakeSoldiersIds[0], - soldierResId - ); - expect(allAssets).to.eql([ - "ipfs:soldier/", // metadataURI - bn(0), // equippableGroupId - catalog.address, // catalogAddress - expectedFixedParts, - expectedSlotParts, - ]); - }); - - it("can compose equippables for simple asset", async function () { - const allAssets = await view.composeEquippables( - background.address, - backgroundsIds[0], - backgroundAssetId - ); - expect(allAssets).to.eql([ - "ipfs:background/", // metadataURI - bn(1), // equippableGroupId - catalog.address, // catalogAddress, - [], - [], - ]); - }); - - it("cannot compose equippables for soldier with not associated asset", async function () { - const wrongResId = weaponAssetsEquip[1]; - await expect( - view.composeEquippables(weapon.address, weaponsIds[0], wrongResId) - ).to.be.revertedWithCustomError(weapon, "TokenDoesNotHaveAsset"); - }); - }); - - async function equipWeaponAndCheckFromAddress( - from: SignerWithAddress, - childIndex: number, - weaponResId: number - ): Promise { - await expect( - soldier - .connect(from) - .equip([ - snakeSoldiersIds[0], - childIndex, - soldierResId, - partIdForWeapon, - weaponResId, - ]) - ) - .to.emit(soldier, "ChildAssetEquipped") - .withArgs( - snakeSoldiersIds[0], - soldierResId, - partIdForWeapon, - weaponsIds[0], - weapon.address, - weaponAssetsEquip[0] - ); - // All part slots are included on the response: - const expectedSlots = [bn(partIdForWeapon), bn(partIdForBackground)]; - // If a slot has nothing equipped, it returns an empty equip: - const expectedEquips = [ - [bn(soldierResId), bn(weaponResId), bn(weaponsIds[0]), weapon.address], - [bn(0), bn(0), bn(0), ethers.constants.AddressZero], - ]; - expect( - await view.getEquipped(soldier.address, snakeSoldiersIds[0], soldierResId) - ).to.eql([expectedSlots, expectedEquips]); - - // Child is marked as equipped: - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - weapon.address, - weaponsIds[0] - ) - ).to.eql(true); - } - - async function unequipWeaponAndCheckFromAddress( - from: SignerWithAddress - ): Promise { - await expect( - soldier - .connect(from) - .unequip(snakeSoldiersIds[0], soldierResId, partIdForWeapon) - ) - .to.emit(soldier, "ChildAssetUnequipped") - .withArgs( - snakeSoldiersIds[0], - soldierResId, - partIdForWeapon, - weaponsIds[0], - weapon.address, - weaponAssetsEquip[0] - ); - - const expectedSlots = [bn(partIdForWeapon), bn(partIdForBackground)]; - // If a slot has nothing equipped, it returns an empty equip: - const expectedEquips = [ - [bn(0), bn(0), bn(0), ethers.constants.AddressZero], - [bn(0), bn(0), bn(0), ethers.constants.AddressZero], - ]; - expect( - await view.getEquipped(soldier.address, snakeSoldiersIds[0], soldierResId) - ).to.eql([expectedSlots, expectedEquips]); - - // Child is marked as not equipped: - expect( - await soldier.isChildEquipped( - snakeSoldiersIds[0], - weapon.address, - weaponsIds[0] - ) - ).to.eql(false); - } - - async function mintWeaponToSoldier( - soldierOwner: SignerWithAddress, - soldierId: number, - assetIndex: number - ): Promise { - // Mint another weapon to the soldier and accept it - const newWeaponId = await nestMint(weapon, soldier.address, soldierId); - await soldier - .connect(soldierOwner) - .acceptChild(soldierId, 0, weapon.address, newWeaponId); - - // Add assets to weapon - await weapon.addAssetToToken(newWeaponId, weaponAssetsFull[assetIndex], 0); - await weapon.addAssetToToken(newWeaponId, weaponAssetsEquip[assetIndex], 0); - await weapon - .connect(soldierOwner) - .acceptAsset(newWeaponId, 0, weaponAssetsFull[assetIndex]); - await weapon - .connect(soldierOwner) - .acceptAsset(newWeaponId, 0, weaponAssetsEquip[assetIndex]); - - return newWeaponId; - } -}); - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} diff --git a/assets/eip-6220/test/multiasset.ts b/assets/eip-6220/test/multiasset.ts deleted file mode 100644 index 20742984738a60..00000000000000 --- a/assets/eip-6220/test/multiasset.ts +++ /dev/null @@ -1,711 +0,0 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { - EquippableTokenMock, - ERC721ReceiverMock, - MultiAssetRenderUtils, - NonReceiverMock, -} from "../typechain-types"; - -describe("MultiAsset", async () => { - let token: EquippableTokenMock; - let renderUtils: MultiAssetRenderUtils; - let nonReceiver: NonReceiverMock; - let receiver721: ERC721ReceiverMock; - - let owner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - const metaURIDefault = "metaURI"; - - beforeEach(async () => { - [owner, ...addrs] = await ethers.getSigners(); - - const equppableFactory = await ethers.getContractFactory( - "EquippableTokenMock" - ); - token = await equppableFactory.deploy(); - await token.deployed(); - - const renderFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - renderUtils = await renderFactory.deploy(); - await renderUtils.deployed(); - }); - - describe("ERC165 check", async function () { - it("can support IERC165", async function () { - expect(await token.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("can support IERC721", async function () { - expect(await token.supportsInterface("0x80ac58cd")).to.equal(true); - }); - - it("can support IMultiAsset", async function () { - expect(await token.supportsInterface("0xd1526708")).to.equal(true); - }); - - it("cannot support other interfaceId", async function () { - expect(await token.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("Check OnReceived ERC721 and Multiasset", async function () { - it("Revert on transfer to non onERC721/onMultiasset implementer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const NonReceiver = await ethers.getContractFactory("NonReceiverMock"); - nonReceiver = await NonReceiver.deploy(); - await nonReceiver.deployed(); - - await expect( - token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - nonReceiver.address, - 1 - ) - ).to.be.revertedWithCustomError( - token, - "ERC721TransferToNonReceiverImplementer" - ); - }); - - it("onERC721Received callback on transfer", async function () { - const tokenId = 1; - await token.mint(owner.address, tokenId); - - const ERC721Receiver = await ethers.getContractFactory( - "ERC721ReceiverMock" - ); - receiver721 = await ERC721Receiver.deploy(); - await receiver721.deployed(); - - await token - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - receiver721.address, - 1 - ); - expect(await token.ownerOf(1)).to.equal(receiver721.address); - }); - }); - - describe("Asset storage", async function () { - it("can add asset", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - }); - - it("cannot get non existing asset", async function () { - const tokenId = 1; - const resId = 10; - await token.mint(owner.address, tokenId); - await expect( - token.getAssetMetadata(tokenId, resId) - ).to.be.revertedWithCustomError(token, "TokenDoesNotHaveAsset"); - }); - - it("cannot add asset entry if not issuer", async function () { - const id = 10; - await expect( - token.connect(addrs[1]).addAssetEntry(id, metaURIDefault) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("can set and get issuer", async function () { - const newIssuerAddr = addrs[1].address; - expect(await token.getIssuer()).to.equal(owner.address); - - await token.setIssuer(newIssuerAddr); - expect(await token.getIssuer()).to.equal(newIssuerAddr); - }); - - it("cannot set issuer if not issuer", async function () { - const newIssuer = addrs[1]; - await expect( - token.connect(newIssuer).setIssuer(newIssuer.address) - ).to.be.revertedWith("RMRK: Only issuer"); - }); - - it("cannot overwrite asset", async function () { - const id = 10; - - await token.addAssetEntry(id, metaURIDefault); - await expect( - token.addAssetEntry(id, metaURIDefault) - ).to.be.revertedWithCustomError(token, "AssetAlreadyExists"); - }); - - it("cannot add asset with id 0", async function () { - const id = ethers.utils.hexZeroPad("0x0", 8); - - await expect( - token.addAssetEntry(id, metaURIDefault) - ).to.be.revertedWithCustomError(token, "IdZeroForbidden"); - }); - - it("cannot add same asset twice", async function () { - const id = 10; - - await expect(token.addAssetEntry(id, metaURIDefault)) - .to.emit(token, "AssetSet") - .withArgs(id); - - await expect( - token.addAssetEntry(id, metaURIDefault) - ).to.be.revertedWithCustomError(token, "AssetAlreadyExists"); - }); - }); - - describe("Adding assets", async function () { - it("can add asset to token", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await expect(token.addAssetToToken(tokenId, resId, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - await expect(token.addAssetToToken(tokenId, resId2, 0)).to.emit( - token, - "AssetAddedToTokens" - ); - - const pendingIds = await token.getPendingAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, pendingIds) - ).to.be.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot add non existing asset to token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect( - token.addAssetToToken(tokenId, resId, 0) - ).to.be.revertedWithCustomError(token, "NoAssetMatchingId"); - }); - - it("can add asset to non existing token and it is pending when minted", async function () { - const resId = 1; - const tokenId = 1; - await addAssets([resId]); - - await token.addAssetToToken(tokenId, resId, 0); - await token.mint(owner.address, tokenId); - expect(await token.getPendingAssets(tokenId)).to.eql([ - ethers.BigNumber.from(resId), - ]); - }); - - it("cannot add asset twice to the same token", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.addAssetToToken(tokenId, ethers.BigNumber.from(resId), 0) - ).to.be.revertedWithCustomError(token, "AssetAlreadyExists"); - }); - - it("cannot add too many assets to the same token", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - for (let i = 1; i <= 128; i++) { - await addAssets([i]); - await token.addAssetToToken(tokenId, i, 0); - } - - // Now it's full, next should fail - const resId = 129; - await addAssets([resId]); - await expect( - token.addAssetToToken(tokenId, resId, 0) - ).to.be.revertedWithCustomError(token, "MaxPendingAssetsReached"); - }); - - it("can add same asset to 2 different tokens", async function () { - const resId = 1; - const tokenId1 = 1; - const tokenId2 = 2; - - await token.mint(owner.address, tokenId1); - await token.mint(owner.address, tokenId2); - await addAssets([resId]); - await token.addAssetToToken(tokenId1, resId, 0); - await token.addAssetToToken(tokenId2, resId, 0); - }); - }); - - describe("Accepting assets", async function () { - it("can accept asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkAcceptFromAddress(approved, tokenId); - }); - - it("can accept multiple assets", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 1, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, 0); - await expect(token.acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault, metaURIDefault]); - }); - - it("cannot accept asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - }); - - it("cannot accept asset if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect( - token.connect(addrs[1]).acceptAsset(tokenId, 0, resId) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - }); - - it("cannot accept non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect( - token.acceptAsset(tokenId, 0, 1) - ).to.be.revertedWithCustomError(token, "IndexOutOfRange"); - }); - }); - - describe("Overwriting assets", async function () { - it("can add asset to token overwritting an existing one", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Add new asset to overwrite the first, and accept - const activeAssets = await token.getActiveAssets(tokenId); - await expect(token.addAssetToToken(tokenId, resId2, activeAssets[0])) - .to.emit(token, "AssetAddedToTokens") - .withArgs([tokenId], resId2, resId); - const pendingAssets = await token.getPendingAssets(tokenId); - - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(activeAssets[0]); - await expect(token.acceptAsset(tokenId, 0, resId2)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId2, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - // Overwrite should be gone - expect( - await token.getAssetReplacements(tokenId, pendingAssets[0]) - ).to.eql(ethers.BigNumber.from(0)); - }); - - it("can overwrite non existing asset to token, it could have been deleted", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken( - tokenId, - resId, - ethers.utils.hexZeroPad("0x1", 8) - ); - await token.acceptAsset(tokenId, 0, resId); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - }); - }); - - describe("Rejecting assets", async function () { - it("can reject asset if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject asset if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectFromAddress(approved, tokenId); - }); - - it("can reject all assets if owner", async function () { - const { tokenOwner, tokenId } = await mintSampleToken(); - const approved = tokenOwner; - - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[1]; - - await token.approveForAssets(approved.address, tokenId); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject all assets if approved for assets for all", async function () { - const { tokenId } = await mintSampleToken(); - const approved = addrs[2]; - - await token.setApprovalForAllForAssets(approved.address, true); - await checkRejectAllFromAddress(approved, tokenId); - }); - - it("can reject asset and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject it - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAsset(tokenId, 0, resId2); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all assets and overwrites are cleared", async function () { - const resId = 1; - const resId2 = 2; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.acceptAsset(tokenId, 0, resId); - - // Will try to overwrite but we reject all - await token.addAssetToToken(tokenId, resId2, resId); - await token.rejectAllAssets(tokenId, 1); - - expect(await token.getAssetReplacements(tokenId, resId2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("can reject all pending assets at max capacity", async function () { - const tokenId = 1; - const resArr = []; - - for (let i = 1; i < 128; i++) { - resArr.push(i); - } - - await token.mint(owner.address, tokenId); - await addAssets(resArr); - - for (let i = 1; i < 128; i++) { - await token.addAssetToToken(tokenId, i, 1); - } - await token.rejectAllAssets(tokenId, 128); - - expect(await token.getAssetReplacements(1, 2)).to.eql( - ethers.BigNumber.from(0) - ); - }); - - it("cannot reject asset twice", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await token.rejectAsset(tokenId, 0, resId); - }); - - it("cannot reject asset nor reject all if not owner", async function () { - const resId = 1; - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(addrs[1]).rejectAsset(tokenId, 0, resId) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - await expect( - token.connect(addrs[1]).rejectAllAssets(tokenId, 1) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - }); - - it("cannot reject non existing asset", async function () { - const tokenId = 1; - - await token.mint(owner.address, tokenId); - await expect( - token.rejectAsset(tokenId, 0, 1) - ).to.be.revertedWithCustomError(token, "IndexOutOfRange"); - }); - }); - - describe("Priorities", async function () { - it("can set and get priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([0, 1]); - await expect(token.setPriority(tokenId, [2, 1])) - .to.emit(token, "AssetPrioritySet") - .withArgs(tokenId); - expect(await token.getActiveAssetPriorities(tokenId)).to.be.eql([2, 1]); - }); - - it("cannot set priorities for non owned token", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.connect(addrs[1]).setPriority(tokenId, [2, 1]) - ).to.be.revertedWithCustomError(token, "NotApprovedForAssetsOrOwner"); - }); - - it("cannot set different number of priorities", async function () { - const tokenId = 1; - await addAssetsToToken(tokenId); - await expect( - token.setPriority(tokenId, [1]) - ).to.be.revertedWithCustomError(token, "BadPriorityListLength"); - await expect( - token.setPriority(tokenId, [2, 1, 3]) - ).to.be.revertedWithCustomError(token, "BadPriorityListLength"); - }); - - it("cannot set priorities for non existing token", async function () { - const tokenId = 1; - await expect( - token.connect(addrs[1]).setPriority(tokenId, []) - ).to.be.revertedWithCustomError(token, "ERC721InvalidTokenId"); - }); - }); - - describe("Approval Cleaning", async function () { - it("cleans token and assets approvals on transfer", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const newOwner = addrs[2]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner).transfer(newOwner.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql( - ethers.constants.AddressZero - ); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - ethers.constants.AddressZero - ); - }); - - it("cleans token and assets approvals on burn", async function () { - const tokenId = 1; - const tokenOwner = addrs[1]; - const approved = addrs[3]; - await token.mint(tokenOwner.address, tokenId); - await token.connect(tokenOwner).approve(approved.address, tokenId); - await token - .connect(tokenOwner) - .approveForAssets(approved.address, tokenId); - - expect(await token.getApproved(tokenId)).to.eql(approved.address); - expect(await token.getApprovedForAssets(tokenId)).to.eql( - approved.address - ); - - await token.connect(tokenOwner)["burn(uint256)"](tokenId); - - await expect(token.getApproved(tokenId)).to.be.revertedWithCustomError( - token, - "ERC721InvalidTokenId" - ); - await expect( - token.getApprovedForAssets(tokenId) - ).to.be.revertedWithCustomError(token, "ERC721InvalidTokenId"); - }); - }); - - async function mintSampleToken(): Promise<{ - tokenOwner: SignerWithAddress; - tokenId: number; - }> { - const tokenOwner = owner; - const tokenId = 1; - await token.mint(tokenOwner.address, tokenId); - - return { tokenOwner, tokenId }; - } - - async function addAssets(ids: number[]): Promise { - ids.forEach(async (resId) => { - await token.addAssetEntry(resId, metaURIDefault); - }); - } - - async function addAssetsToToken(tokenId: number): Promise { - const resId = 1; - const resId2 = 2; - await token.mint(owner.address, tokenId); - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - await token.acceptAsset(tokenId, 0, resId); - await token.acceptAsset(tokenId, 0, resId2); - } - - async function checkAcceptFromAddress( - accepter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - await expect(token.connect(accepter).acceptAsset(tokenId, 0, resId)) - .to.emit(token, "AssetAccepted") - .withArgs(tokenId, resId, 0); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - - const activeIds = await token.getActiveAssets(tokenId); - expect( - await renderUtils.getAssetsById(token.address, tokenId, activeIds) - ).to.eql([metaURIDefault]); - } - - async function checkRejectFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - - await addAssets([resId]); - await token.addAssetToToken(tokenId, resId, 0); - - await expect( - token.connect(rejecter).rejectAsset(tokenId, 0, resId) - ).to.emit(token, "AssetRejected"); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } - - async function checkRejectAllFromAddress( - rejecter: SignerWithAddress, - tokenId: number - ): Promise { - const resId = 1; - const resId2 = 2; - - await addAssets([resId, resId2]); - await token.addAssetToToken(tokenId, resId, 0); - await token.addAssetToToken(tokenId, resId2, 0); - - await expect(token.connect(rejecter).rejectAllAssets(tokenId, 2)).to.emit( - token, - "AssetRejected" - ); - - expect(await token.getPendingAssets(tokenId)).to.be.eql([]); - expect(await token.getActiveAssets(tokenId)).to.be.eql([]); - } -}); diff --git a/assets/eip-6220/test/nestable.ts b/assets/eip-6220/test/nestable.ts deleted file mode 100644 index 8c4fbc68de45b8..00000000000000 --- a/assets/eip-6220/test/nestable.ts +++ /dev/null @@ -1,1493 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber, constants } from "ethers"; -import { ethers } from "hardhat"; -import { EquippableTokenMock } from "../typechain-types"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -const ADDRESS_ZERO = constants.AddressZero; - -async function parentChildFixture(): Promise<{ - parent: EquippableTokenMock; - child: EquippableTokenMock; -}> { - const factory = await ethers.getContractFactory("EquippableTokenMock"); - - const parent = await factory.deploy(); - await parent.deployed(); - const child = await factory.deploy(); - await child.deployed(); - return { parent, child }; -} - -describe("NestableToken", function () { - let parent: EquippableTokenMock; - let child: EquippableTokenMock; - let owner: SignerWithAddress; - let tokenOwner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [owner, tokenOwner, ...addrs] = await ethers.getSigners(); - ({ parent, child } = await loadFixture(parentChildFixture)); - }); - - describe("Minting", async function () { - it("cannot mint id 0", async function () { - const tokenId1 = 0; - await expect( - child.mint(owner.address, tokenId1) - ).to.be.revertedWithCustomError(child, "IdZeroForbidden"); - }); - - it("cannot nest mint id 0", async function () { - const parentId = 1; - await child.mint(owner.address, parentId); - const childId1 = 0; - await expect( - child.nestMint(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "IdZeroForbidden"); - }); - - it("cannot mint already minted token", async function () { - const tokenId1 = 1; - await child.mint(owner.address, tokenId1); - await expect( - child.mint(owner.address, tokenId1) - ).to.be.revertedWithCustomError(child, "ERC721TokenAlreadyMinted"); - }); - - it("cannot nest mint already minted token", async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "ERC721TokenAlreadyMinted"); - }); - - it("cannot nest mint already minted token", async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "ERC721TokenAlreadyMinted"); - }); - - it("can mint with no destination", async function () { - const tokenId1 = 1; - await child.mint(tokenOwner.address, tokenId1); - expect(await child.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await child.directOwnerOf(tokenId1)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - }); - - it("has right owners", async function () { - const otherOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(tokenOwner.address, tokenId1); - const tokenId2 = 2; - await parent.mint(otherOwner.address, tokenId2); - const tokenId3 = 3; - await parent.mint(otherOwner.address, tokenId3); - - expect(await parent.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await parent.ownerOf(tokenId2)).to.equal(otherOwner.address); - expect(await parent.ownerOf(tokenId3)).to.equal(otherOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(2); - - await expect(parent.ownerOf(9999)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - }); - - it("cannot mint to zero address", async function () { - await expect(child.mint(ADDRESS_ZERO, 1)).to.be.revertedWithCustomError( - child, - "ERC721MintToTheZeroAddress" - ); - }); - - it("cannot nest mint to a non-contract destination", async function () { - await expect( - child.nestMint(tokenOwner.address, 1, 1) - ).to.be.revertedWithCustomError(child, "IsNotContract"); - }); - - it("cannot nest mint to non nestable receiver", async function () { - const ERC721 = await ethers.getContractFactory("ERC721Mock"); - const nonReceiver = await ERC721.deploy("Non receiver", "NR"); - await nonReceiver.deployed(); - - await expect( - child.nestMint(nonReceiver.address, 1, 1) - ).to.be.revertedWithCustomError(child, "MintToNonNestableImplementer"); - }); - - it("cannot nest mint to a non-existent token", async function () { - await expect( - child.nestMint(parent.address, 1, 1) - ).to.be.revertedWithCustomError(child, "ERC721InvalidTokenId"); - }); - - it("cannot nest mint to zero address", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect( - child.nestMint(ADDRESS_ZERO, parentId, 1) - ).to.be.revertedWithCustomError(child, "IsNotContract"); - }); - - it("can mint to contract and owners are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // owner is the same adress - expect(await parent.ownerOf(parentId)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - }); - - it("can mint to contract and direct owners are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Direct owner is an address for the parent - expect(await parent.directOwnerOf(parentId)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - // Direct owner is a contract for the child - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(parentId), - true, - ]); - }); - - it("can mint to contract and parent's children are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - const children = await parent.childrenOf(parentId); - expect(children).to.eql([]); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([[bn(childId1), child.address]]); - expect(await parent.pendingChildOf(parentId, 0)).to.eql([ - bn(childId1), - child.address, - ]); - }); - - it("cannot get child out of index", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.childOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - "ChildIndexOutOfRange" - ); - }); - - it("cannot get pending child out of index", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect( - parent.pendingChildOf(parentId, 0) - ).to.be.revertedWithCustomError(parent, "PendingChildIndexOutOfRange"); - }); - - it("can mint multiple children", async function () { - const parentId = 1; - const childId1 = 99; - const childId2 = 100; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId2)).to.equal(tokenOwner.address); - - expect(await child.balanceOf(parent.address)).to.equal(2); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([ - [bn(childId1), child.address], - [bn(childId2), child.address], - ]); - }); - - it("can mint child into child", async function () { - const parentId = 1; - const childId1 = 99; - const granchildId = 999; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - - // Check balances -- yes, technically the counted balance indicates `child` owns an instance of itself - // and this is a little counterintuitive, but the root owner is the EOA. - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - const pendingChildrenOfChunky10 = await parent.pendingChildrenOf( - parentId - ); - const pendingChildrenOfMonkey1 = await child.pendingChildrenOf(childId1); - - expect(pendingChildrenOfChunky10).to.eql([[bn(childId1), child.address]]); - expect(pendingChildrenOfMonkey1).to.eql([ - [bn(granchildId), child.address], - ]); - - expect(await child.directOwnerOf(granchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - - expect(await child.ownerOf(granchildId)).to.eql(tokenOwner.address); - }); - - it("cannot have too many pending children", async () => { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - - // First 128 should be fine. - for (let i = 1; i <= 128; i++) { - await child.nestMint(parent.address, i, parentId); - } - - await expect( - child.nestMint(parent.address, 129, parentId) - ).to.be.revertedWithCustomError(child, "MaxPendingChildrenReached"); - }); - }); - - describe("Interface support", async function () { - it("can support IERC165", async function () { - expect(await parent.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("can support IERC721", async function () { - expect(await parent.supportsInterface("0x80ac58cd")).to.equal(true); - }); - - it("can support INestable", async function () { - expect(await parent.supportsInterface("0x42b0e56f")).to.equal(true); - }); - - it("cannot support other interfaceId", async function () { - expect(await parent.supportsInterface("0xffffffff")).to.equal(false); - }); - }); - - describe("Adding child", async function () { - it("cannot add child from user address", async function () { - const tokenOwner1 = addrs[0]; - const tokenOwner2 = addrs[1]; - const parentId = 1; - await parent.mint(tokenOwner1.address, parentId); - const childId1 = 99; - await child.mint(tokenOwner2.address, childId1); - await expect( - parent.addChild(parentId, childId1, "0x") - ).to.be.revertedWithCustomError(parent, "IsNotContract"); - }); - }); - - describe("Accept child", async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it("can accept child", async function () { - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1) - ) - .to.emit(parent, "ChildAccepted") - .withArgs(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it("can accept child if approved", async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent - .connect(approved) - .acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it("can accept child if approved for all", async function () { - const operator = addrs[2]; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - await parent - .connect(operator) - .acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it("cannot accept not owned child", async function () { - const notOwner = addrs[3]; - await expect( - parent - .connect(notOwner) - .acceptChild(parentId, 0, child.address, childId1) - ).to.be.revertedWithCustomError(parent, "ERC721NotApprovedOrOwner"); - }); - - it("cannot accept child if address or id do not match", async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, otherChildId) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 0, otherAddress, childId1) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - }); - - it("cannot accept children for non existing index", async () => { - await expect( - parent - .connect(tokenOwner) - .acceptChild(parentId, 1, child.address, childId1) - ).to.be.revertedWithCustomError(parent, "PendingChildIndexOutOfRange"); - }); - - async function checkChildWasAccepted() { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([ - [bn(childId1), child.address], - ]); - } - }); - - describe("Rejecting children", async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, 99, parentId); - }); - - it("can reject all pending children", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect(parent.connect(tokenOwner).rejectAllChildren(parentId, 3)) - .to.emit(parent, "AllChildrenRejected") - .withArgs(parentId); - await checkNoChildrenNorPending(parentId); - - // They are still on the child - expect(await child.balanceOf(parent.address)).to.equal(3); - }); - - it("cannot reject all pending children if there are more than expected", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect( - parent.connect(tokenOwner).rejectAllChildren(parentId, 1) - ).to.be.revertedWithCustomError(parent, "UnexpectedNumberOfChildren"); - }); - - it("can reject all pending children if approved", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const rejecter = addrs[1]; - await parent.connect(tokenOwner).approve(rejecter.address, parentId); - await parent.connect(rejecter).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it("can reject all pending children if approved for all", async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const operator = addrs[2]; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - await parent.connect(operator).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it("cannot reject all pending children for not owned pending child", async function () { - const notOwner = addrs[3]; - - await expect( - parent.connect(notOwner).rejectAllChildren(parentId, 2) - ).to.be.revertedWithCustomError(parent, "ERC721NotApprovedOrOwner"); - }); - }); - - describe("Burning", async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - }); - - it("can burn token", async function () { - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - await parent.connect(tokenOwner)["burn(uint256)"](parentId); - await checkBurntParent(); - }); - - it("can burn token if approved", async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved)["burn(uint256)"](parentId); - await checkBurntParent(); - }); - - it("can burn token if approved for all", async function () { - const operator = addrs[2]; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - await parent.connect(operator)["burn(uint256)"](parentId); - await checkBurntParent(); - }); - - it("can recursively burn nested token", async function () { - const childId1 = 99; - const granchildId = 999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1); - await child - .connect(tokenOwner) - .acceptChild(childId1, 0, child.address, granchildId); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - expect(await parent.childrenOf(parentId)).to.eql([ - [bn(childId1), child.address], - ]); - expect(await child.childrenOf(childId1)).to.eql([ - [bn(granchildId), child.address], - ]); - expect(await child.directOwnerOf(granchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - - // Sets recursive burns to 2 - await parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 2); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(0); - expect(await child.balanceOf(parent.address)).to.equal(0); - expect(await child.balanceOf(child.address)).to.equal(0); - - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - await expect( - parent.directOwnerOf(parentId) - ).to.be.revertedWithCustomError(parent, "ERC721InvalidTokenId"); - - await expect(child.ownerOf(childId1)).to.be.revertedWithCustomError( - child, - "ERC721InvalidTokenId" - ); - await expect(child.directOwnerOf(childId1)).to.be.revertedWithCustomError( - child, - "ERC721InvalidTokenId" - ); - - await expect(parent.ownerOf(granchildId)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - await expect( - parent.directOwnerOf(granchildId) - ).to.be.revertedWithCustomError(parent, "ERC721InvalidTokenId"); - }); - - it("can recursively burn nested token with the right number of recursive burns", async function () { - // Parent - // -> Child1 - // -> GrandChild1 - // -> GrandChild2 - // -> GreatGrandChild1 - // -> Child2 - // Total tree 5 (4 recursive burns) - const childId1 = 99; - const childId2 = 100; - const grandChild1 = 999; - const grandChild2 = 1000; - const greatGrandChild1 = 9999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - await child.nestMint(child.address, grandChild1, childId1); - await child.nestMint(child.address, grandChild2, childId1); - await child.nestMint(child.address, greatGrandChild1, grandChild2); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId2); - await child - .connect(tokenOwner) - .acceptChild(childId1, 0, child.address, grandChild1); - await child - .connect(tokenOwner) - .acceptChild(childId1, 0, child.address, grandChild2); - await child - .connect(tokenOwner) - .acceptChild(grandChild2, 0, child.address, greatGrandChild1); - - // 0 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 0) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, childId1); - // 1 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 1) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, grandChild1); - // 2 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 2) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, grandChild2); - // 3 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 3) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, greatGrandChild1); - // 4 is not enough - await expect( - parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 4) - ) - .to.be.revertedWithCustomError(parent, "MaxRecursiveBurnsReached") - .withArgs(child.address, childId2); - // 5 is just enough - await parent.connect(tokenOwner)["burn(uint256,uint256)"](parentId, 5); - }); - - async function checkBurntParent() { - expect(await parent.balanceOf(addrs[1].address)).to.equal(0); - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - "ERC721InvalidTokenId" - ); - } - }); - - describe("Transferring Active Children", async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - childId1 = 99; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await parent - .connect(tokenOwner) - .acceptChild(parentId, 0, child.address, childId1); - }); - - it("can transfer child with to as root owner", async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - tokenOwner.address, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(); - }); - - it("can transfer child to another address", async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, false); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it("can transfer child to another NFT", async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - false, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, false); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(newParentId), - true, - ]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([ - [bn(childId1), child.address], - ]); - }); - - it("cannot transfer child out of index", async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - badIndex, - child.address, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "ChildIndexOutOfRange"); - }); - - it("cannot transfer child if address or id do not match", async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - otherAddress, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - otherChildId, - false, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - }); - - it("can transfer child if approved", async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child if approved for all", async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child with grandchild and children are ok", async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - }); - - it("cannot transfer child if not child root owner", async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent - .connect(notOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721NotApprovedOrOwner"); - }); - - it("cannot transfer child from not existing parent", async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - badChildId, - toOwner, - 0, - 0, - child.address, - childId1, - false, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721InvalidTokenId"); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - rootOwnerAddress, - bn(0), - false, - ]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe("Transferring Pending Children", async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it("can transfer child with to as root owner", async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - tokenOwner.address, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(); - }); - - it("can transfer child to another address", async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, true); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it("can transfer child to another NFT", async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - true, - "0x" - ) - ) - .to.emit(parent, "ChildTransferred") - .withArgs(parentId, 0, child.address, childId1, true); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(newParentId), - true, - ]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([ - [bn(childId1), child.address], - ]); - }); - - it("cannot transfer child out of index", async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - badIndex, - child.address, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "PendingChildIndexOutOfRange"); - }); - - it("cannot transfer child if address or id do not match", async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - otherAddress, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwnerAddress, - 0, - 0, - child.address, - otherChildId, - true, - "0x" - ) - ).to.be.revertedWithCustomError(parent, "UnexpectedChildId"); - }); - - it("can transfer child if approved", async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child if approved for all", async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent - .connect(tokenOwner) - .setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ); - await checkChildMovedToRootOwner(); - }); - - it("can transfer child with grandchild and children are ok", async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - tokenOwner.address, - bn(0), - false, - ]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([ - child.address, - bn(childId1), - true, - ]); - }); - - it("cannot transfer child if not child root owner", async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent - .connect(notOwner) - .transferChild( - parentId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721NotApprovedOrOwner"); - }); - - it("cannot transfer child from not existing parent", async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild( - badChildId, - toOwner, - 0, - 0, - child.address, - childId1, - true, - "0x" - ) - ).to.be.revertedWithCustomError(child, "ERC721InvalidTokenId"); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([ - rootOwnerAddress, - bn(0), - false, - ]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe("Transfer", async function () { - it("can transfer token", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - // Balances and ownership are updated - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - }); - - it("cannot transfer not owned token", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(newOwner).transfer(newOwner.address, tokenId1) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("cannot transfer to address zero", async function () { - const firstOwner = addrs[1]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(firstOwner).transfer(ADDRESS_ZERO, tokenId1) - ).to.be.revertedWithCustomError(child, "ERC721TransferToTheZeroAddress"); - }); - - it("can transfer token from approved address (not owner)", async function () { - const firstOwner = addrs[1]; - const approved = addrs[2]; - const newOwner = addrs[3]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - - await parent.connect(firstOwner).approve(approved.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - }); - - it("can transfer not nested token with child to address and owners/children are ok", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await parent.connect(firstOwner).transfer(newOwner.address, parentId); - - // Balances and ownership are updated - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - - expect(await parent.ownerOf(parentId)).to.eql(newOwner.address); - expect(await parent.directOwnerOf(parentId)).to.eql([ - newOwner.address, - bn(0), - false, - ]); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(newOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(parentId), - true, - ]); - - // Parent still has its children - expect(await parent.pendingChildrenOf(parentId)).to.eql([ - [bn(childId1), child.address], - ]); - }); - - it("cannot directly transfer nested child", async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.connect(firstOwner).transfer(newOwner.address, childId1) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("can transfer parent token to token with same owner, family tree is ok", async function () { - const firstOwner = addrs[1]; - const grandParentId = 999; - await parent.mint(firstOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(firstOwner.address)).to.equal(2); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Transfers token parentId to (parent.address, token grandParentId) - await parent - .connect(firstOwner) - .nestTransfer(parent.address, parentId, grandParentId); - - // Balances unchanged since root owner is the same - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - - it("can transfer parent token to token with different owner, family tree is ok", async function () { - const firstOwner = addrs[1]; - const otherOwner = addrs[2]; - const grandParentId = 999; - await parent.mint(otherOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // firstOwner calls parent to transfer parent token parent - await parent - .connect(firstOwner) - .nestTransfer(parent.address, parentId, grandParentId); - - // Balances update - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - }); - - describe("Nest Transfer", async function () { - let firstOwner: SignerWithAddress; - let parentId: number; - let childId1: number; - - beforeEach(async function () { - firstOwner = addrs[1]; - parentId = 1; - childId1 = 99; - await parent.mint(firstOwner.address, parentId); - await child.mint(firstOwner.address, childId1); - }); - - it("cannot nest tranfer from non immediate owner (owner of parent)", async function () { - const otherParentId = 2; - await parent.mint(firstOwner.address, otherParentId); - // We send it to the parent first - await child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, parentId); - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, otherParentId) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("cannot nest tranfer to same NFT", async function () { - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child - .connect(firstOwner) - .nestTransfer(child.address, childId1, childId1) - ).to.be.revertedWithCustomError(child, "NestableTransferToSelf"); - }); - - it("cannot nest tranfer a descendant same NFT", async function () { - // We can no longer nest transfer it, even if we are the root owner: - await child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, parentId); - const grandChildId = 999; - await child.nestMint(child.address, grandChildId, childId1); - // Ownership is now parent->child->granChild - // Cannot send parent to grandChild - await expect( - parent - .connect(firstOwner) - .nestTransfer(child.address, parentId, grandChildId) - ).to.be.revertedWithCustomError(child, "NestableTransferToDescendant"); - // Cannot send parent to child - await expect( - parent - .connect(firstOwner) - .nestTransfer(child.address, parentId, childId1) - ).to.be.revertedWithCustomError(child, "NestableTransferToDescendant"); - }); - - it("cannot nest tranfer if ancestors tree is too deep", async function () { - let lastId = childId1; - for (let i = 101; i <= 200; i++) { - await child.nestMint(child.address, i, lastId); - lastId = i; - } - // Ownership is now parent->child->child->child->child...->lastChild - // Cannot send parent to lastChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, lastId) - ).to.be.revertedWithCustomError(child, "NestableTooDeep"); - }); - - it("cannot nest tranfer if not owner", async function () { - const notOwner = addrs[3]; - await expect( - child.connect(notOwner).nestTransfer(parent.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "NotApprovedOrDirectOwner"); - }); - - it("cannot nest tranfer to address 0", async function () { - await expect( - child.connect(firstOwner).nestTransfer(ADDRESS_ZERO, childId1, parentId) - ).to.be.revertedWithCustomError(child, "ERC721TransferToTheZeroAddress"); - }); - - it("cannot nest tranfer to a non contract", async function () { - const newOwner = addrs[2]; - await expect( - child - .connect(firstOwner) - .nestTransfer(newOwner.address, childId1, parentId) - ).to.be.revertedWithCustomError(child, "IsNotContract"); - }); - - it("cannot nest tranfer to contract if it does implement INestable", async function () { - const ERC721 = await ethers.getContractFactory("ERC721Mock"); - const nonNestable = await ERC721.deploy("Non receiver", "NR"); - await nonNestable.deployed(); - await expect( - child - .connect(firstOwner) - .nestTransfer(nonNestable.address, childId1, parentId) - ).to.be.revertedWithCustomError( - child, - "NestableTransferToNonNestableImplementer" - ); - }); - - it("can nest tranfer to INestable contract", async function () { - await child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, parentId); - expect(await child.ownerOf(childId1)).to.eql(firstOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([ - parent.address, - bn(parentId), - true, - ]); - }); - - it("cannot nest tranfer to non existing parent token", async function () { - const notExistingParentId = 9999; - await expect( - child - .connect(firstOwner) - .nestTransfer(parent.address, childId1, notExistingParentId) - ).to.be.revertedWithCustomError(parent, "ERC721InvalidTokenId"); - }); - }); - - async function checkNoChildrenNorPending(parentId: number): Promise { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([]); - } - - async function checkAcceptedAndPendingChildren( - contract: EquippableTokenMock, - tokenId1: number, - expectedAccepted: any[], - expectedPending: any[] - ) { - const accepted = await contract.childrenOf(tokenId1); - expect(accepted).to.eql(expectedAccepted); - - const pending = await contract.pendingChildrenOf(tokenId1); - expect(pending).to.eql(expectedPending); - } -}); diff --git a/assets/eip-6220/test/renderUtils.ts b/assets/eip-6220/test/renderUtils.ts deleted file mode 100644 index d946d09fa4ac01..00000000000000 --- a/assets/eip-6220/test/renderUtils.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { ethers } from "hardhat"; -import { - EquippableTokenMock, - EquipRenderUtils, - MultiAssetRenderUtils, -} from "../typechain-types"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -async function assetsFixture() { - const equipFactory = await ethers.getContractFactory("EquippableTokenMock"); - const renderUtilsFactory = await ethers.getContractFactory( - "MultiAssetRenderUtils" - ); - const renderUtilsEquipFactory = await ethers.getContractFactory( - "EquipRenderUtils" - ); - - const equip = await equipFactory.deploy(); - await equip.deployed(); - - const renderUtils = await renderUtilsFactory.deploy(); - await renderUtils.deployed(); - - const renderUtilsEquip = ( - await renderUtilsEquipFactory.deploy() - ); - await renderUtilsEquip.deployed(); - - return { equip, renderUtils, renderUtilsEquip }; -} - -describe("Render Utils", async function () { - let owner: SignerWithAddress; - let someCatalog: SignerWithAddress; - let equip: EquippableTokenMock; - let renderUtils: MultiAssetRenderUtils; - let renderUtilsEquip: EquipRenderUtils; - let tokenId: number; - - const resId = bn(1); - const resId2 = bn(2); - const resId3 = bn(3); - const resId4 = bn(4); - - before(async function () { - ({ equip, renderUtils, renderUtilsEquip } = await loadFixture( - assetsFixture - )); - - const signers = await ethers.getSigners(); - owner = signers[0]; - someCatalog = signers[1]; - tokenId = 1; - - await equip.mint(owner.address, tokenId); - await equip.addEquippableAssetEntry( - resId, - 0, - ethers.constants.AddressZero, - "ipfs://res1.jpg", - [] - ); - await equip.addEquippableAssetEntry( - resId2, - 1, - someCatalog.address, - "ipfs://res2.jpg", - [1, 3, 4] - ); - await equip.addEquippableAssetEntry( - resId3, - 0, - ethers.constants.AddressZero, - "ipfs://res3.jpg", - [] - ); - await equip.addEquippableAssetEntry( - resId4, - 2, - someCatalog.address, - "ipfs://res4.jpg", - [4] - ); - await equip.addAssetToToken(tokenId, resId, 0); - await equip.addAssetToToken(tokenId, resId2, 0); - await equip.addAssetToToken(tokenId, resId3, resId); - await equip.addAssetToToken(tokenId, resId4, 0); - - await equip.acceptAsset(tokenId, 0, resId); - await equip.acceptAsset(tokenId, 1, resId2); - await equip.setPriority(tokenId, [10, 5]); - }); - - describe("Render Utils MultiAsset", async function () { - it("can get active assets", async function () { - expect(await renderUtils.getActiveAssets(equip.address, tokenId)).to.eql([ - [resId, 10, "ipfs://res1.jpg"], - [resId2, 5, "ipfs://res2.jpg"], - ]); - }); - - it("can get assets by id", async function () { - expect( - await renderUtils.getAssetsById(equip.address, tokenId, [resId, resId2]) - ).to.eql(["ipfs://res1.jpg", "ipfs://res2.jpg"]); - }); - - it("can get pending assets", async function () { - expect(await renderUtils.getPendingAssets(equip.address, tokenId)).to.eql( - [ - [resId4, bn(0), bn(0), "ipfs://res4.jpg"], - [resId3, bn(1), resId, "ipfs://res3.jpg"], - ] - ); - }); - - it("can get top asset by priority", async function () { - expect( - await renderUtils.getTopAssetMetaForToken(equip.address, tokenId) - ).to.eql("ipfs://res2.jpg"); - }); - - it("cannot get top asset if token has no assets", async function () { - const otherTokenId = 2; - await equip.mint(owner.address, otherTokenId); - await expect( - renderUtils.getTopAssetMetaForToken(equip.address, otherTokenId) - ).to.be.revertedWithCustomError(renderUtils, "TokenHasNoAssets"); - }); - }); - - describe("Render Utils Equip", async function () { - it("can get active assets", async function () { - expect( - await renderUtilsEquip.getExtendedActiveAssets(equip.address, tokenId) - ).to.eql([ - [resId, bn(0), 10, ethers.constants.AddressZero, "ipfs://res1.jpg", []], - [ - resId2, - bn(1), - 5, - someCatalog.address, - "ipfs://res2.jpg", - [bn(1), bn(3), bn(4)], - ], - ]); - }); - - it("can get pending assets", async function () { - expect( - await renderUtilsEquip.getExtendedPendingAssets(equip.address, tokenId) - ).to.eql([ - [ - resId4, - bn(2), - bn(0), - bn(0), - someCatalog.address, - "ipfs://res4.jpg", - [bn(4)], - ], - [ - resId3, - bn(0), - bn(1), - resId, - ethers.constants.AddressZero, - "ipfs://res3.jpg", - [], - ], - ]); - }); - }); -}); diff --git a/assets/eip-6327/zkpass-1.png b/assets/eip-6327/zkpass-1.png deleted file mode 100644 index 5f073fdb76146f..00000000000000 Binary files a/assets/eip-6327/zkpass-1.png and /dev/null differ diff --git a/assets/eip-6327/zkpass-2.png b/assets/eip-6327/zkpass-2.png deleted file mode 100644 index 51bd261d86191d..00000000000000 Binary files a/assets/eip-6327/zkpass-2.png and /dev/null differ diff --git a/assets/eip-6327/zkpass-3.png b/assets/eip-6327/zkpass-3.png deleted file mode 100644 index bc4cb54bc01c37..00000000000000 Binary files a/assets/eip-6327/zkpass-3.png and /dev/null differ diff --git a/assets/eip-6353/contracts/CharityToken.sol b/assets/eip-6353/contracts/CharityToken.sol deleted file mode 100644 index e7834da34654f9..00000000000000 --- a/assets/eip-6353/contracts/CharityToken.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./ERC20Charity.sol"; - -/** -*@title ERC720 charity Token -*@dev Extension of ERC720 Token that can be partially donated to a charity project -* -*This extensions keeps track of donations to charity addresses. The whitelisted adress are from a another contract (Reserve) - */ - -contract CharityToken is ERC20Charity{ - constructor() ERC20("TestToken", "TST") { - _mint(msg.sender, 10000 * 10 ** decimals()); - } - - /** @dev Creates `amount` tokens and assigns them to `to`, increasing - * the total supply. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - * @param to The address to assign the amount to. - * @param amount The amount of token to mint. - */ - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } - - function selfmint() public { - _mint(msg.sender, 100 * 10 ** decimals()); - } - - - //Test support for ERC-Charity - bytes4 private constant _INTERFACE_ID_ERC_CHARITY = type(IERC20charity).interfaceId; // 0x557512b6 - //bytes4 private constant _INTERFACE_ID_ERCcharity =type(IERC165).interfaceId; // ERC165S - function checkInterface(address testContract) external view returns (bool) { - (bool success) = IERC165(testContract).supportsInterface(_INTERFACE_ID_ERC_CHARITY); - return success; - } - - /*function InterfaceId() external returns (bytes4) { - bytes4 _INTERFACE_ID = type(IERC20charity).interfaceId; - return _INTERFACE_ID ; - }*/ - -} diff --git a/assets/eip-6353/contracts/ERC20Charity.sol b/assets/eip-6353/contracts/ERC20Charity.sol deleted file mode 100644 index 73d2c05c4ac3a9..00000000000000 --- a/assets/eip-6353/contracts/ERC20Charity.sol +++ /dev/null @@ -1,328 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./interfaces/IERC20charity.sol"; - -/** - *@title ERC720 charity Token - *@author Aubay - *@dev Extension of ERC720 Token that can be partially donated to a charity project - * - *This extensions keeps track of donations to charity addresses. The owner can chose the charity adresses listed. - *Users can active the donation option or not and specify a different pourcentage than the default one donate. - * A pourcentage af the amount of token transfered will be added and send to a charity address. - */ - -abstract contract ERC20Charity is IERC20charity, ERC20, Ownable { - mapping(address => uint256) public whitelistedRate; //Keep track of the rate for each charity address - mapping(address => uint256) internal indexOfAddresses; - mapping(address => mapping(address => uint256)) private _donation; //Keep track of the desired rate to donate for each user - mapping(address => address) private _defaultAddress; //keep track of each user's default charity address - - address[] whitelistedAddresses; //Addresses whitelisted - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165) returns (bool) { - return - interfaceId == type(IERC20charity).interfaceId || - interfaceId == type(IERC165).interfaceId; - } - - /** - *@dev The default rate of donation can be override - */ - function _defaultRate() internal pure virtual returns (uint256) { - return 10; // 0.1% - } - - /** - *@dev The denominator to interpret the rate of donation , defaults to 10000 so rate are expressed in basis points, but may be customized by an override. - * base 10000 , so 10000 =100% , 0 = 0% , 2000 =20% - */ - function _feeDenominator() internal pure virtual returns (uint256) { - return 10000; - } - - /** - *@notice Add address to whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toAdd` cannot be the zero address. - * - * @param toAdd The address to whitelist. - */ - function addToWhitelist(address toAdd) external virtual onlyOwner { - if (indexOfAddresses[toAdd] == 0) { - whitelistedRate[toAdd] = _defaultRate(); - whitelistedAddresses.push(toAdd); - indexOfAddresses[toAdd] = whitelistedAddresses.length; - } - - emit AddedToWhitelist(toAdd); - } - - /** - *@notice Remove the address from the whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toRemove` cannot be the zero address. - * - * @param toRemove The address to remove from whitelist. - */ - function deleteFromWhitelist(address toRemove) external virtual onlyOwner { - uint256 index1 = indexOfAddresses[toRemove]; - require(index1 > 0, "Invalid index"); //Indexing starts at 1, 0 is not allowed - // move the last item into the index being vacated - address lastValue = whitelistedAddresses[ - whitelistedAddresses.length - 1 - ]; - whitelistedAddresses[index1 - 1] = lastValue; // adjust for 1-based indexing - indexOfAddresses[lastValue] = index1; - whitelistedAddresses.pop(); - indexOfAddresses[toRemove] = 0; - - delete whitelistedRate[toRemove]; //whitelistedRate[toRemove] =0; - emit RemovedFromWhitelist(toRemove); - } - - /// @notice Get all registered charity addresses - /// @return List of all registered donations addresses - function getAllWhitelistedAddresses() external view returns (address[] memory) { - return whitelistedAddresses; - } - - /// @notice Display for a user the rate of the default charity address that will receive donation. - /// @return The default rate of the registered address for the user. - function getRate() external view returns (uint256) { - return _donation[msg.sender][_defaultAddress[msg.sender]]; - } - - /** - *@notice Set for a user a default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - * @param whitelistedAddr The address to set as default. - */ - function setSpecificDefaultAddress( - address whitelistedAddr - ) external virtual { - require( - whitelistedRate[whitelistedAddr] != 0, - "ERC20Charity: invalid whitelisted rate" - ); - _defaultAddress[msg.sender] = whitelistedAddr; - _donation[msg.sender][whitelistedAddr] = whitelistedRate[ - whitelistedAddr - ]; - emit DonnationAddressChanged(whitelistedAddr); - } - - /** - *@notice Set for a user a default charity address that will receive donation. - * The rate is specified by the user. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate - * or to the rate specified by the owner of this contract in {whitelistedRate}. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificDefaultAddressAndRate( - address whitelistedAddr, - uint256 rate - ) external virtual { - require( - rate <= _feeDenominator(), - "ERC20Charity: rate must be between 0 and _feeDenominator" - ); - require( - rate >= _defaultRate(), - "ERC20Charity: rate fee must exceed default rate" - ); - require( - rate >= whitelistedRate[whitelistedAddr], - "ERC20Charity: rate fee must exceed the fee set by the owner" - ); - require( - whitelistedRate[whitelistedAddr] != 0, - "ERC20Charity: invalid whitelisted address" - ); - _defaultAddress[msg.sender] = whitelistedAddr; - _donation[msg.sender][whitelistedAddr] = rate; - emit DonnationAddressAndRateChanged(whitelistedAddr, rate); - } - - /** - *@notice Set personlised rate for charity address in {whitelistedRate}. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificRate( - address whitelistedAddr, - uint256 rate - ) external virtual onlyOwner { - require( - rate <= _feeDenominator(), - "ERC20Charity: rate must be between 0 and _feeDenominator" - ); - require( - rate >= _defaultRate(), - "ERC20Charity: rate fee must exceed default rate" - ); - require( - whitelistedRate[whitelistedAddr] != 0, - "ERC20Charity: invalid whitelisted address" - ); - whitelistedRate[whitelistedAddr] = rate; - emit ModifiedCharityRate(whitelistedAddr, rate); - } - - /** - *@notice Display for a user the default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - */ - function specificDefaultAddress() external view virtual returns (address) { - return _defaultAddress[msg.sender]; - } - - /** - * inherit IERC20charity - */ - function charityInfo( - address charityAddr - ) external view virtual returns (bool, uint256 rate) { - rate = whitelistedRate[charityAddr]; - if (rate != 0) { - return (true, rate); - } else { - return (false, rate); - } - } - - /** - *@notice Delete The Default Address and so deactivate donnations . - */ - function deleteDefaultAddress() external virtual { - _defaultAddress[msg.sender] = address(0); - emit DonnationAddressChanged(address(0)); - } - - /** - *@notice Return the rate to donate. - * @dev Requirements: - * - * - `from` cannot be the zero address - * - * @param from The address to get rate of donation. - */ - function _returnRate(address from) internal virtual returns (uint256 rate) { - address whitelistedAddr = _defaultAddress[from]; - rate = _donation[from][whitelistedAddr]; - if ( - whitelistedRate[whitelistedAddr] == 0 || - _defaultAddress[from] == address(0) - ) { - rate = 0; - } - return rate; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer( - address to, - uint256 amount - ) public virtual override returns (bool) { - address owner = _msgSender(); - - if (_defaultAddress[msg.sender] != address(0)) { - address whitelistedAddr = _defaultAddress[msg.sender]; - uint256 rate = _returnRate(msg.sender); - uint256 donate = (amount * rate) / _feeDenominator(); - _transfer(owner, whitelistedAddr, donate); - } - _transfer(owner, to, amount); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - - if (_defaultAddress[from] != address(0)) { - address whitelistedAddr = _defaultAddress[from]; - uint256 rate = _returnRate(from); - uint256 donate = (amount * rate) / _feeDenominator(); - _spendAllowance(from, spender, donate); - _transfer(from, whitelistedAddr, donate); - } - _transfer(from, to, amount); - return true; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve( - address spender, - uint256 amount - ) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - if (_defaultAddress[msg.sender] != address(0)) { - uint256 rate = _returnRate(msg.sender); - uint256 donate = (amount * rate) / _feeDenominator(); - _approve(owner, spender, (donate + amount)); - } - return true; - } -} diff --git a/assets/eip-6353/contracts/graphs/charity graph4.svg b/assets/eip-6353/contracts/graphs/charity graph4.svg deleted file mode 100644 index abdc319b185a3b..00000000000000 --- a/assets/eip-6353/contracts/graphs/charity graph4.svg +++ /dev/null @@ -1,378 +0,0 @@ - - - - - - - - -ERC20Charity - - - - -Legend - - - - - -supportsInterface - - - - - -type - - - - - - - - - - - - - - - - - -_defaultRate - - - - - -_feeDenominator - - - - - -addToWhitelist - - - - - - - - - - - -AddedToWhitelist - - - - - - - - - - - -deleteFromWhitelist - - - - - -RemovedFromWhitelist - - - - - - - - - - - -setSpecificDefaultAddress - - - - - -DonnationAddressChanged - - - - - - - - - - - -setSpecificDefaultAddressAndRate - - - - - - - - - - - - - - - - - -DonnationAddressAndRateChanged - - - - - - - - - - - -setSpecificRate - - - - - - - - - - - - - - - - - -ModifiedCharityRate - - - - - - - - - - - -SpecificDefaultAddress - - - - - -charityInfo - - - - - -DeleteDefaultAddress - - - - - - - - - - - -_returnRate - - - - - -transfer - - - - - - - - - - - - - - - - - -_msgSender - - - - - - - - - - - -_transfer - - - - - - - - - - - - - - - - - -transferFrom - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -_spendAllowance - - - - - - - - - - - - - - - - - -approve - - - - - - - - - - - - - - - - - - - - - - - -_approve - - - - - - - - - - - - - - - - - -Internal Call -External Call -Defined Contract -Undefined Contract - - - - - -    -    - -    - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/eip-6353/contracts/graphs/test graph3.svg b/assets/eip-6353/contracts/graphs/test graph3.svg deleted file mode 100644 index b9cc579ee2250f..00000000000000 --- a/assets/eip-6353/contracts/graphs/test graph3.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - -CharityToken - - - - -Legend - - - - - -<Constructor> - - - - - -_mint - - - - - - - - - - - -decimals - - - - - - - - - - - -mint - - - - - - - - - - - -checkInterface - - - - - -IERC165 - - - - - - - - - - - -Internal Call -External Call -Defined Contract -Undefined Contract - - - - - -    -    - -    - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/assets/eip-6353/contracts/interfaces/IERC20charity.sol b/assets/eip-6353/contracts/interfaces/IERC20charity.sol deleted file mode 100644 index 928b78aeb87b2d..00000000000000 --- a/assets/eip-6353/contracts/interfaces/IERC20charity.sol +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; -//import "./IERC165.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; - -/// -/// @dev Required interface of an ERC20 Charity compliant contract. -/// -interface IERC20charity is IERC165 { - /// ERC165 bytes to add to interface array - set in parent contract - /// implementing this standard - /// - ///type(IERC20charity).interfaceId.interfaceId == 0x557512b6 - /// bytes4 private constant _INTERFACE_ID_ERCcharity = 0x557512b6; - /// _registerInterface(_INTERFACE_ID_ERCcharity); - - - /** - * @dev Emitted when `toAdd` charity address is added to `whitelistedRate`. - */ - event AddedToWhitelist (address toAdd); - - /** - * @dev Emitted when `toRemove` charity address is deleted from `whitelistedRate`. - */ - event RemovedFromWhitelist (address toRemove); - - /** - * @dev Emitted when `_defaultAddress` charity address is modified and set to `whitelistedAddr`. - */ - event DonnationAddressChanged (address whitelistedAddr); - - /** - * @dev Emitted when `_defaultAddress` charity address is modified and set to `whitelistedAddr` - * and _donation is set to `rate`. - */ - event DonnationAddressAndRateChanged (address whitelistedAddr,uint256 rate); - - /** - * @dev Emitted when `whitelistedRate` for `whitelistedAddr` is modified and set to `rate`. - */ - event ModifiedCharityRate(address whitelistedAddr,uint256 rate); - - /** - *@notice Called with the charity address to determine if the contract whitelisted the address - *and if it is the rate assigned. - *@param addr - the Charity address queried for donnation information. - *@return whitelisted - true if the contract whitelisted the address to receive donnation - *@return defaultRate - the rate defined by the contract owner by default , the minimum rate allowed different from 0 - */ - function charityInfo( - address addr - ) external view returns ( - bool whitelisted, - uint256 defaultRate - ); - - /** - *@notice Add address to whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toAdd` cannot be the zero address. - * - * @param toAdd The address to whitelist. - */ - function addToWhitelist(address toAdd) external; - - /** - *@notice Remove the address from the whitelist and set rate to the default rate. - * @dev Requirements: - * - * - `toRemove` cannot be the zero address. - * - * @param toRemove The address to remove from whitelist. - */ - function deleteFromWhitelist(address toRemove) external; - - /** - *@notice Get all registered charity addresses. - */ - function getAllWhitelistedAddresses() external view returns (address[] memory) ; - - /** - *@notice Display for a user the rate of the default charity address that will receive donation. - */ - function getRate() external view returns (uint256); - - /** - *@notice Set personlised rate for charity address in {whitelistedRate}. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificRate(address whitelistedAddr , uint256 rate) external; - - /** - *@notice Set for a user a default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - * @param whitelistedAddr The address to set as default. - */ - function setSpecificDefaultAddress(address whitelistedAddr) external; - - /** - *@notice Set for a user a default charity address that will receive donation. - * The rate is specified by the user. - * @dev Requirements: - * - * - `whitelistedAddr` cannot be the zero address. - * - `rate` cannot be inferior to the default rate - * or to the rate specified by the owner of this contract in {whitelistedRate}. - * - * @param whitelistedAddr The address to set as default. - * @param rate The personalised rate for donation. - */ - function setSpecificDefaultAddressAndRate(address whitelistedAddr , uint256 rate) external; - - /** - *@notice Display for a user the default charity address that will receive donation. - * The default rate specified in {whitelistedRate} will be applied. - */ - function specificDefaultAddress() external view returns ( - address defaultAddress - ); - - /** - *@notice Delete The Default Address and so deactivate donnations . - */ - function deleteDefaultAddress() external; -} \ No newline at end of file diff --git a/assets/eip-6353/test/.gitkeep b/assets/eip-6353/test/.gitkeep deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/assets/eip-6353/test/charity.js b/assets/eip-6353/test/charity.js deleted file mode 100644 index 01f80f648d26c5..00000000000000 --- a/assets/eip-6353/test/charity.js +++ /dev/null @@ -1,173 +0,0 @@ -const { expect } = require("chai"); -const { ethers } = require("hardhat"); - -describe('Compile: Test charity donation configuration ', function() { - before(async function() { - [owner, user, charity1, charity2, charity3, user2]= await ethers.getSigners(); - }); - it("Deploy contract ", async function() { - charityTokenContract = await ethers.getContractFactory("CharityToken"); - charity = await charityTokenContract.deploy(); - decimals = await charity.decimals(); - - console.log("CharityToken Contract : ", await charity.address); - console.log("Owner : ", owner.address); - console.log("User : ", user.address); - console.log("User2 : ", user2.address); - console.log("Charity 1 : ", charity1.address); - console.log("Charity 2 : ", charity2.address); - console.log("Charity 3 : ", charity3.address); - }); - - it("Owner: Whitelist a charity address ", async function () { - const amount_send= 10000; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.mint(user.address,amnt.toString() ); - - await charity.addToWhitelist(charity1.address); - - //console.log((await charity.whitelistedRate(charity1)).toString()); - - expect(await charity.whitelistedRate(charity1.address)).to.equal( 10, "Failed to store defaultRate"); - - await charity.addToWhitelist(charity2.address); - }); - - it("Owner: custom rate for charity address ", async function () { - - await charity.setSpecificRate(charity2.address,200); - expect(await charity.whitelistedRate(charity2.address)).to.equal( 200, "Failed to custom ate"); - }); - - it("Fails: User Whitelist a charity address ", async function () { - await expect(charity.connect(user).addToWhitelist(charity1.address)).to.be.revertedWith( "Ownable: caller is not the owner"); - }); - - it("User: custom rate for default charity ", async function () { - expect(await charity.connect(user).specificDefaultAddress()).to.equal('0x0000000000000000000000000000000000000000',"The address isn't set yet it should be 0x0000000000000000000000000000000000000000 "); - //console.log("default charity adress" , await charity.connect(user).specificDefaultAddress()); - //set - await charity.connect(user).setSpecificDefaultAddressAndRate(charity1.address,20); //rate is set to 2% for charity1 - expect(await charity.connect(user).specificDefaultAddress()).to.equal(charity1.address,"The address isn't set to charity1 "); - //console.log("default charity adress" , await charity.connect(user).specificDefaultAddress()); - }); - - it("Fails: User Whitelist a charity address that is not whitelisted ", async function () { - await expect(charity.connect(user).setSpecificDefaultAddressAndRate(charity3.address,20)).to.be.revertedWith( "ERC20Charity: invalid whitelisted address"); - }); - - it("Fails: User Whitelist a charity address with an insufficient rate", async function () { - await expect(charity.connect(user).setSpecificDefaultAddressAndRate(charity1.address,5)).to.be.revertedWith( "ERC20Charity: rate fee must exceed default rate"); - await expect(charity.connect(user).setSpecificDefaultAddressAndRate(charity2.address,100)).to.be.revertedWith( "ERC20Charity: rate fee must exceed the fee set by the owner"); - }); - - it("User: transfer an amount to charity when token is transferred", async function () { - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).transfer(user2.address,amnt); //default address is charity1 with 2% rate - - console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.2, "Failed : charity balance should be increased to 0.2"); - - }); - - it("User: transfer (from) an amount to charity when token is transferred", async function () { - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).approve(owner.address,amnt); - await charity.connect( owner).transferFrom(user.address,user2.address,amnt); - - console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.4, "Failed : charity balance should be increased to 0.4"); - }); - - it("User: User deactivate/activate donation", async function () { - //deactivate donnation - await charity.connect(user).deleteDefaultAddress(); - expect(await charity.connect(user).specificDefaultAddress()).to.equal( '0x0000000000000000000000000000000000000000', "Failed : address shloud be null"); - - //try to transfer now - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).transfer(user2.address,amnt); - - //console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - //console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - //console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - //the default address of user1 is no longer whitelisted , the donation shouldn't happen. - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.4, "Failed : charity balance should be of 0.4"); - - //activate donnation and transfer - await charity.connect(user).setSpecificDefaultAddressAndRate(charity1.address,20); //rate is reset to 2% for charity1 - console.log("custom rate changed", (await charity.connect(user).getRate()).toString()); - await charity.connect(user).transfer(user2.address,amnt); - - //console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - //console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - //console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.6, "Failed : charity balance should be of 0.6"); - - }); - - it("Owner: delete charity ", async function () { - await charity.deleteFromWhitelist(charity1.address); - //console.log("Charity 1 rate: "(await charity.whitelistedRate.call(charity1)).toString()); - expect(await charity.whitelistedRate(charity1.address)).to.equal(0, "Failed to delete defaultRate for charity"); - - //try to transfer now - const amount_send= 100; - const amnt = ethers.utils.parseEther(amount_send.toString()); - await charity.connect(user).transfer(user2.address,amnt); - - console.log("user 1 balance: " + (await charity.balanceOf(user.address)/ (10 ** decimals))); - console.log("user 2 balance: " + (await charity.balanceOf(user2.address)/ (10 ** decimals))); - console.log("charity 1 balance: " + (await charity.balanceOf(charity1.address)/ (10 ** decimals))); - //the default address of user1 is no longer whitelisted , the donation shouldn't happen. - expect(await charity.balanceOf(charity1.address)/ (10 ** decimals)).to.equal( 0.6, "Failed : charity balance should be of 0.6"); - }); - - it("Interface test ", async function () { - // const support = await charity.checkInterface.call(charity.address); - const support = await charity.callStatic.checkInterface(charity.address); // to correct - //console.log(typeof support); - console.log(support); - expect(support).to.equal( true); - - // see if the charity address is whitelisted - const info1 = await charity.charityInfo(charity1.address); - const info2 = await charity.charityInfo(charity2.address); - - console.log("charity 1: ",info1[0],info1[1].toString()); - - expect(info1[0]).to.equal( false, "Failed : charity should'nt be whitelisted"); - expect(info1[1]).to.equal( 0, "Failed : charity rate should be null"); - - console.log("charity 2: ",info2[0],info2[1].toString()); - - expect(info2[0]).to.equal( true, "Failed : charity should be whitelisted"); - expect(info2[1]).to.equal( 200, "Failed : charity rate should be set to 200 (2%)"); - - }); - - it("Charity list (add/delete) test ", async function () { - await charity.addToWhitelist(charity1.address); - await charity.addToWhitelist(charity3.address); - - listAddr = await charity.getAllWhitelistedAddresses(); - console.log(listAddr); - - await charity.deleteFromWhitelist(charity1.address); - console.log( await charity.getAllWhitelistedAddresses()); - - await charity.deleteFromWhitelist(charity2.address); - console.log( await charity.getAllWhitelistedAddresses()); - - await charity.deleteFromWhitelist(charity3.address); - console.log( await charity.getAllWhitelistedAddresses()); - }); -}); diff --git a/assets/eip-6358/img/o-dlt.png b/assets/eip-6358/img/o-dlt.png deleted file mode 100644 index 58bc8396f6f493..00000000000000 Binary files a/assets/eip-6358/img/o-dlt.png and /dev/null differ diff --git a/assets/eip-6358/src/.gitignore b/assets/eip-6358/src/.gitignore deleted file mode 100644 index bc00b7b5c1bba9..00000000000000 --- a/assets/eip-6358/src/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -build -.secret - -package-lock.json -truffle-config.js diff --git a/assets/eip-6358/src/README.md b/assets/eip-6358/src/README.md deleted file mode 100644 index 54a44878314ad4..00000000000000 --- a/assets/eip-6358/src/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Example implementation of EIP-6358 - -## Prerequisites -- truffle >= v5.7.9 -- node >= v18.12.1 -- npm >= 8.19.2 -- npx >= 8.19.2 - -## Installation -``` -npm install -``` - -Add the configuration file `truffle-config.js` into the directory `./`. The file `truffle-config.js` can be generated by executing the command in an $empty directory$: -``` -npx truffle init -``` - -**Note that:** - -- type `N` when asked `Overwrite contracts?` -- type `N` when asked `Overwrite migrations?` -- type `N` when asked `Overwrite test?` - -After `truffle-config.js` is generated, then: - -- Uncommnet the content of `development`, like this: - -``` -development: { - host: "127.0.0.1", // Localhost (default: none) - port: 8545, // Standard Ethereum port (default: none) - network_id: "*", // Any network (default: none) - }, -``` - -## Compilation -``` -touch .secret -npx truffle compile -``` - -## Unit test -### Launch local testnet -``` -npx ganache -s 0 -``` - -### Test -Open another terminate - -``` -npx truffle test -``` \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/ERC20.sol b/assets/eip-6358/src/contracts/ERC20.sol deleted file mode 100644 index 7eaa2d4ac0e134..00000000000000 --- a/assets/eip-6358/src/contracts/ERC20.sol +++ /dev/null @@ -1,383 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol) - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; - -/** - * @dev Implementation of the {IERC20} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * For a generic mechanism see {ERC20PresetMinterPauser}. - * - * TIP: For a detailed writeup see our guide - * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * We have followed general OpenZeppelin Contracts guidelines: functions revert - * instead returning `false` on failure. This behavior is nonetheless - * conventional and does not conflict with the expectations of ERC20 - * applications. - * - * Additionally, an {Approval} event is emitted on calls to {transferFrom}. - * This allows applications to reconstruct the allowance for all accounts just - * by listening to said events. Other implementations of the EIP may not emit - * these events, as it isn't required by the specification. - * - * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} - * functions have been added to mitigate the well-known issues around setting - * allowances. See {IERC20-approve}. - */ -contract ERC20 is Context, IERC20, IERC20Metadata { - mapping(address => uint256) internal _balances; - - mapping(address => mapping(address => uint256)) internal _allowances; - - uint256 internal _totalSupply; - - string private _name; - string private _symbol; - - /** - * @dev Sets the values for {name} and {symbol}. - * - * The default value of {decimals} is 18. To select a different value for - * {decimals} you should overload it. - * - * All two of these values are immutable: they can only be set once during - * construction. - */ - constructor(string memory name_, string memory symbol_) { - _name = name_; - _symbol = symbol_; - } - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the value {ERC20} uses, unless this function is - * overridden; - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual override returns (uint8) { - return 18; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account]; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, amount); - return true; - } - - /** - * @dev See {IERC20-allowance}. - */ - function allowance(address owner, address spender) public view virtual override returns (uint256) { - return _allowances[owner][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, allowance(owner, spender) + addedValue); - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `subtractedValue`. - */ - function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { - address owner = _msgSender(); - uint256 currentAllowance = allowance(owner, spender); - require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero"); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /** - * @dev Moves `amount` of tokens from `from` to `to`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { - require(from != address(0), "ERC20: transfer from the zero address"); - require(to != address(0), "ERC20: transfer to the zero address"); - - _beforeTokenTransfer(from, to, amount); - - uint256 fromBalance = _balances[from]; - require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); - unchecked { - _balances[from] = fromBalance - amount; - } - _balances[to] += amount; - - emit Transfer(from, to, amount); - - _afterTokenTransfer(from, to, amount); - } - - /** @dev Creates `amount` tokens and assigns them to `account`, increasing - * the total supply. - * - * Emits a {Transfer} event with `from` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - */ - function _mint(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: mint to the zero address"); - - _beforeTokenTransfer(address(0), account, amount); - - _totalSupply += amount; - _balances[account] += amount; - emit Transfer(address(0), account, amount); - - _afterTokenTransfer(address(0), account, amount); - } - - /** - * @dev Destroys `amount` tokens from `account`, reducing the - * total supply. - * - * Emits a {Transfer} event with `to` set to the zero address. - * - * Requirements: - * - * - `account` cannot be the zero address. - * - `account` must have at least `amount` tokens. - */ - function _burn(address account, uint256 amount) internal virtual { - require(account != address(0), "ERC20: burn from the zero address"); - - _beforeTokenTransfer(account, address(0), amount); - - uint256 accountBalance = _balances[account]; - require(accountBalance >= amount, "ERC20: burn amount exceeds balance"); - unchecked { - _balances[account] = accountBalance - amount; - } - _totalSupply -= amount; - - emit Transfer(account, address(0), amount); - - _afterTokenTransfer(account, address(0), amount); - } - - /** - * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), "ERC20: approve from the zero address"); - require(spender != address(0), "ERC20: approve to the zero address"); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Updates `owner` s allowance for `spender` based on spent `amount`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, "ERC20: insufficient allowance"); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - /** - * @dev Hook that is called before any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} - - /** - * @dev Hook that is called after any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * has been transferred to `to`. - * - when `from` is zero, `amount` tokens have been minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens have been burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual {} -} diff --git a/assets/eip-6358/src/contracts/ERC6358FungibleExample.sol b/assets/eip-6358/src/contracts/ERC6358FungibleExample.sol deleted file mode 100644 index a74a7c764a0e93..00000000000000 --- a/assets/eip-6358/src/contracts/ERC6358FungibleExample.sol +++ /dev/null @@ -1,379 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./ERC20.sol"; -import "./libraries/OmniverseProtocolHelper.sol"; -import "./interfaces/IERC6358Fungible.sol"; - -/** -* @notice Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded -* -* @member op: The operation type -* NOTE op: 0-31 are reserved values, 32-255 are custom values -* op: 0 - omniverse account `from` transfers `amount` tokens to omniverse account `exData`, `from` have at least `amount` tokens -* op: 1 - omniverse account `from` mints `amount` tokens to omniverse account `exData` -* op: 2 - omniverse account `from` burns `amount` tokens from his own, `from` have at least `amount` tokens -* @member exData: The operation data. This sector could be empty and is determined by `op`. For example: - when `op` is 0 and 1, `exData` stores the omniverse account that receives. - when `op` is 2, `exData` is empty. -* @member amount: The amount of tokens being operated - */ -struct Fungible { - uint8 op; - bytes exData; - uint256 amount; -} - -/** - * @notice Implementation of the {IERC6358Fungible} interface - */ -contract ERC6358FungibleExample is ERC20, Ownable, IERC6358Fungible { - uint8 constant TRANSFER = 0; - uint8 constant MINT = 1; - uint8 constant BURN = 2; - - /** @notice Used to index a delayed transaction - * sender: The account which sent the transaction - * nonce: The nonce of the delayed transaction - */ - struct DelayedTx { - bytes sender; - uint256 nonce; - } - - /** - * @notice The member information - * chainId: The chain which the member belongs to - * contractAddr: The contract address on the member chain - */ - struct Member { - uint32 chainId; - bytes contractAddr; - } - - // Chain id used to distinguish different chains - uint32 chainId; - // O-transaction cooling down time - uint256 public cdTime; - // Omniverse accounts record - mapping(bytes => RecordedCertificate) transactionRecorder; - // Transactions to be executed - mapping(bytes => OmniverseTx) public transactionCache; - - // All information of chains on which the token is deployed - Member[] members; - // Omniverse balances - mapping(bytes => uint256) omniverseBalances; - // Delay-executing transactions - DelayedTx[] delayedTxs; - // Account map from evm address to public key - mapping(address => bytes) accountsMap; - - event OmniverseTokenTransfer(bytes from, bytes to, uint256 value); - - /** - * @notice Initiates the contract - * @param _chainId The chain which the contract is deployed on - * @param _name The name of the token - * @param _symbol The symbol of the token - */ - constructor(uint32 _chainId, string memory _name, string memory _symbol) ERC20(_name, _symbol) { - chainId = _chainId; - } - - /** - * @notice See {IERC6358Fungible-sendOmniverseTransaction} - * Send an omniverse transaction - */ - function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external override { - _omniverseTransaction(_data); - } - - /** - * @notice See {IERC6358Fungible-triggerExecution} - */ - function triggerExecution() external { - require(delayedTxs.length > 0, "No delayed tx"); - - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - require(cache.timestamp != 0, "Not cached"); - require(cache.txData.nonce == delayedTxs[0].nonce, "Nonce error"); - (ERC6358TransactionData storage txData, uint256 timestamp) = (cache.txData, cache.timestamp); - require(block.timestamp >= timestamp + cdTime, "Not executable"); - delayedTxs[0] = delayedTxs[delayedTxs.length - 1]; - delayedTxs.pop(); - cache.timestamp = 0; - // Add to transaction recorder - RecordedCertificate storage rc = transactionRecorder[txData.from]; - rc.txList.push(cache); - - Fungible memory fungible = decodeData(txData.payload); - if (fungible.op == TRANSFER) { - _omniverseTransfer(txData.from, fungible.exData, fungible.amount); - } - else if (fungible.op == MINT) { - _checkOwner(txData.from); - _omniverseMint(fungible.exData, fungible.amount); - } - else if (fungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(fungible.exData, fungible.amount); - _omniverseBurn(fungible.exData, fungible.amount); - } - emit TransactionExecuted(txData.from, txData.nonce); - } - - /** - * @notice Check if the transaction can be executed successfully - */ - function _checkExecution(ERC6358TransactionData memory txData) internal view { - Fungible memory fungible = decodeData(txData.payload); - if (fungible.op == TRANSFER) { - _checkOmniverseTransfer(txData.from, fungible.amount); - } - else if (fungible.op == MINT) { - _checkOwner(txData.from); - } - else if (fungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(fungible.exData, fungible.amount); - } - else { - revert("OP code error"); - } - } - - /** - * @notice Returns the nearest exexutable delayed transaction info - * or returns default if not found - */ - function getExecutableDelayedTx() external view returns (DelayedTx memory ret) { - if (delayedTxs.length > 0) { - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - if (block.timestamp >= cache.timestamp + cdTime) { - ret = delayedTxs[0]; - } - } - } - - /** - * @notice Returns the count of delayed transactions - */ - function getDelayedTxCount() external view returns (uint256) { - return delayedTxs.length; - } - - /** - * @notice See {IERC6358Fungible-omniverseBalanceOf} - * Returns the omniverse balance of a user - */ - function omniverseBalanceOf(bytes calldata _pk) external view override returns (uint256) { - return omniverseBalances[_pk]; - } - - /** - * @notice See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual override returns (uint256) { - bytes storage pk = accountsMap[account]; - if (pk.length == 0) { - return 0; - } - else { - return omniverseBalances[pk]; - } - } - - /** - * @notice Receive and check an omniverse transaction - */ - function _omniverseTransaction(ERC6358TransactionData memory _data) internal { - // Check if the tx initiateSC is correct - bool found = false; - for (uint256 i = 0; i < members.length; i++) { - if (members[i].chainId == _data.chainId) { - require(keccak256(members[i].contractAddr) == keccak256(_data.initiateSC), "Wrong initiateSC"); - found = true; - } - } - require(found, "Wrong initiateSC"); - - // Check if the sender is honest - // to be continued, we can use block list instead of `isMalicious` - require(!isMalicious(_data.from), "User malicious"); - - // Verify the signature - VerifyResult verifyRet = OmniverseProtocolHelper.verifyTransaction(transactionRecorder[_data.from], _data); - - if (verifyRet == VerifyResult.Success) { - // Check cache - OmniverseTx storage cache = transactionCache[_data.from]; - require(cache.timestamp == 0, "Transaction cached"); - // Logic verification - _checkExecution(_data); - // Delays in executing - cache.txData = _data; - cache.timestamp = block.timestamp; - delayedTxs.push(DelayedTx(_data.from, _data.nonce)); - if (_data.chainId == chainId) { - emit TransactionSent(_data.from, _data.nonce); - } - } - else if (verifyRet == VerifyResult.Duplicated) { - emit TransactionExecuted(_data.from, _data.nonce); - } - else if (verifyRet == VerifyResult.Malicious) { - // Slash - } - } - - /** - * @notice Check if an omniverse transfer operation can be executed successfully - */ - function _checkOmniverseTransfer(bytes memory _from, uint256 _amount) internal view { - uint256 fromBalance = omniverseBalances[_from]; - require(fromBalance >= _amount, "Exceed balance"); - } - - /** - * @notice Exucute an omniverse transfer operation - */ - function _omniverseTransfer(bytes memory _from, bytes memory _to, uint256 _amount) internal { - _checkOmniverseTransfer(_from, _amount); - - uint256 fromBalance = omniverseBalances[_from]; - - unchecked { - omniverseBalances[_from] = fromBalance - _amount; - } - omniverseBalances[_to] += _amount; - - emit OmniverseTokenTransfer(_from, _to, _amount); - - address toAddr = OmniverseProtocolHelper.pkToAddress(_to); - accountsMap[toAddr] = _to; - } - - /** - * @notice Check if the public key is the owner - */ - function _checkOwner(bytes memory _pk) internal view { - address fromAddr = OmniverseProtocolHelper.pkToAddress(_pk); - require(fromAddr == owner(), "Not owner"); - } - - /** - * @notice Execute an omniverse mint operation - */ - function _omniverseMint(bytes memory _to, uint256 _amount) internal { - omniverseBalances[_to] += _amount; - emit OmniverseTokenTransfer("", _to, _amount); - - address toAddr = OmniverseProtocolHelper.pkToAddress(_to); - accountsMap[toAddr] = _to; - } - - /** - * @notice Check if an omniverse burn operation can be executed successfully - */ - function _checkOmniverseBurn(bytes memory _from, uint256 _amount) internal view { - uint256 fromBalance = omniverseBalances[_from]; - require(fromBalance >= _amount, "Exceed balance"); - } - - /** - * @notice Execute an omniverse burn operation - */ - function _omniverseBurn(bytes memory _from, uint256 _amount) internal { - omniverseBalances[_from] -= _amount; - emit OmniverseTokenTransfer(_from, "", _amount); - } - - /** - * @notice Add new chain members to the token - */ - function setMembers(Member[] calldata _members) external onlyOwner { - for (uint256 i = 0; i < _members.length; i++) { - if (i < members.length) { - members[i] = _members[i]; - } - else { - members.push(_members[i]); - } - } - - for (uint256 i = _members.length; i < members.length; i++) { - delete members[i]; - } - } - - /** - * @notice Returns chain members of the token - */ - function getMembers() external view returns (Member[] memory) { - return members; - } - - /** - @notice See {IERC20-decimals}. - */ - function decimals() public view virtual override returns (uint8) { - return 12; - } - - /** - * @notice See IERC6358Fungible - */ - function getTransactionCount(bytes memory _pk) external override view returns (uint256) { - return transactionRecorder[_pk].txList.length; - } - - /** - * @notice See IERC6358Fungible - */ - function getTransactionData(bytes calldata _user, uint256 _nonce) external override view returns (ERC6358TransactionData memory txData, uint256 timestamp) { - RecordedCertificate storage rc = transactionRecorder[_user]; - OmniverseTx storage omniTx = rc.txList[_nonce]; - txData = omniTx.txData; - timestamp = omniTx.timestamp; - } - - /** - * @notice Set the cooling down time of an omniverse transaction - */ - function setCoolingDownTime(uint256 _time) external { - cdTime = _time; - } - - /** - * @notice Index the user is malicious or not - */ - function isMalicious(bytes memory _pk) public view returns (bool) { - RecordedCertificate storage rc = transactionRecorder[_pk]; - return (rc.evilTxList.length > 0); - } - - /** - * @notice See IERC6358Fungible - */ - function getChainId() external view returns (uint32) { - return chainId; - } - - /** - * @notice Decode `_data` from bytes to Fungible - */ - function decodeData(bytes memory _data) internal pure returns (Fungible memory) { - (uint8 op, bytes memory exData, uint256 amount) = abi.decode(_data, (uint8, bytes, uint256)); - return Fungible(op, exData, amount); - } - - /** - * @notice See IERC6358Application - */ - function getPayloadRawData(bytes memory _payload) external pure returns (bytes memory) { - Fungible memory fungible = decodeData(_payload); - return abi.encodePacked(fungible.op, fungible.exData, uint128(fungible.amount)); - } -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol b/assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol deleted file mode 100644 index 842bec82ac901d..00000000000000 --- a/assets/eip-6358/src/contracts/ERC6358NonFungibleExample.sol +++ /dev/null @@ -1,519 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "./libraries/OmniverseProtocolHelper.sol"; -import "./interfaces/IERC6358NonFungible.sol"; - -/** -* @notice Non-Fungible token data structure, from which the field `payload` in `ERC6358TransactionData` will be encoded -* -* @member op: The operation type -* NOTE op: 0-31 are reserved values, 32-255 are custom values -* op: 0 - omniverse account `from` transfers the token with id `tokenId` to omniverse account `exData`, `from` have the token with id `tokenId` -* op: 1 - omniverse account `from` mints the token with id `tokenId` to omniverse account `exData` -* op: 2 - omniverse account `from` burns the token with id `tokenId` from omniverse account `exData`, `exData` MUST have the token with id `tokenId` -* @member exData: The operation data. This sector could be empty and is determined by `op`. For example: - when `op` is 0 and 1, `exData` stores the omniverse account that receives. - when `op` is 2, `exData` is empty. -* @member tokenId: The token id of the non-fungible token being operated - */ -struct NonFungible { - uint8 op; - bytes exData; - uint256 tokenId; -} - -/** - * @notice Implementation of the {IERC6358NonFungible} interface - */ -contract ERC6358NonFungibleExample is Ownable, IERC6358NonFungible, IERC721, IERC721Metadata { - using Strings for uint256; - - uint8 constant TRANSFER = 0; - uint8 constant MINT = 1; - uint8 constant BURN = 2; - - /** @notice Used to index a delayed transaction - * sender: The account which sent the transaction - * nonce: The nonce of the delayed transaction - */ - struct DelayedTx { - bytes sender; - uint256 nonce; - } - - /** - * @notice The member information - * chainId: The chain which the member belongs to - * contractAddr: The contract address on the member chain - */ - struct Member { - uint32 chainId; - bytes contractAddr; - } - - // Token name - string private tokenName; - // Token symbol - string private tokenSymbol; - // Base URI - string public baseURI; - // Chain id used to distinguish different chains - uint32 chainId; - // O-transaction cooling down time - uint256 public cdTime; - // Omniverse accounts record - mapping(bytes => RecordedCertificate) transactionRecorder; - // Transactions to be executed - mapping(bytes => OmniverseTx) public transactionCache; - - // All information of chains on which the token is deployed - Member[] members; - // Token owners - mapping(uint256 => bytes) omniverseOwners; - // Omniverse balances - mapping(bytes => uint256) omniverseBalances; - // Delay-executing transactions - DelayedTx[] delayedTxs; - // Account map from evm address to public key - mapping(address => bytes) accountsMap; - - event OmniverseTokenTransfer(bytes from, bytes to, uint256 value); - - /** - * @notice Initiates the contract - * @param _chainId The chain which the contract is deployed on - * @param _name The name of the token - * @param _symbol The symbol of the token - */ - constructor(uint32 _chainId, string memory _name, string memory _symbol) { - chainId = _chainId; - tokenName = _name; - tokenSymbol = _symbol; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC721Metadata).interfaceId || - interfaceId == type(IERC165).interfaceId; - } - - /** - * @notice See {IERC6358NonFungible-sendOmniverseTransaction} - * Send an omniverse transaction - */ - function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external override { - _omniverseTransaction(_data); - } - - /** - * @notice See {IERC6358NonFungible-triggerExecution} - */ - function triggerExecution() external { - require(delayedTxs.length > 0, "No delayed tx"); - - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - require(cache.timestamp != 0, "Not cached"); - require(cache.txData.nonce == delayedTxs[0].nonce, "Nonce error"); - (ERC6358TransactionData storage txData, uint256 timestamp) = (cache.txData, cache.timestamp); - require(block.timestamp >= timestamp + cdTime, "Not executable"); - delayedTxs[0] = delayedTxs[delayedTxs.length - 1]; - delayedTxs.pop(); - cache.timestamp = 0; - // Add to transaction recorder - RecordedCertificate storage rc = transactionRecorder[txData.from]; - rc.txList.push(cache); - - NonFungible memory nonFungible = decodeData(txData.payload); - if (nonFungible.op == TRANSFER) { - _omniverseTransfer(txData.from, nonFungible.exData, nonFungible.tokenId); - } - else if (nonFungible.op == MINT) { - _checkOwner(txData.from); - _omniverseMint(nonFungible.exData, nonFungible.tokenId); - } - else if (nonFungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(nonFungible.exData, nonFungible.tokenId); - _omniverseBurn(nonFungible.exData, nonFungible.tokenId); - } - } - - /** - * @notice See {IERC721-balanceOf}. - */ - function balanceOf(address owner) public view returns (uint256 balance) { - bytes storage pk = accountsMap[owner]; - if (pk.length == 0) { - balance = 0; - } - else { - balance = omniverseBalances[pk]; - } - } - - /** - * @notice See {IERC721-ownerOf}. - */ - function ownerOf(uint256 tokenId) external view returns (address owner) { - bytes memory ret = this.omniverseOwnerOf(tokenId); - return OmniverseProtocolHelper.pkToAddress(ret); - } - - /** - * @notice See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata data - ) external { - - } - - /** - * @notice See {IERC721-safeTransferFrom}. - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) external { - - } - - /** - * @notice See {IERC721-transferFrom}. - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) external { - - } - - /** - * @notice See {IERC721-approve}. - */ - function approve(address /*to*/, uint256 /*tokenId*/) external { - - } - - /** - * @notice See {IERC721-setApprovalForAll}. - */ - function setApprovalForAll(address /*operator*/, bool /*_approved*/) external { - - } - - /** - * @notice See {IERC721-getApproved}. - */ - function getApproved(uint256 /*tokenId*/) external pure returns (address /*operator*/) { - revert("Forbidden"); - } - - /** - * @notice See {IERC721-isApprovedForAll}. - */ - function isApprovedForAll(address /*owner*/, address /*operator*/) external pure returns (bool) { - return false; - } - - /** - * @notice See {IERC721Metadata-name}. - */ - function name() external view returns (string memory) { - return tokenName; - } - - /** - * @notice See {IERC721Metadata-symbol}. - */ - function symbol() external view returns (string memory) { - return tokenSymbol; - } - - /** - * @notice See {IERC721Metadata-tokenURI}. - */ - function tokenURI(uint256 tokenId) external view returns (string memory) { - bytes memory ret = omniverseOwners[tokenId]; - require(keccak256(ret) != keccak256(bytes("")), "ERC721Metadata: URI query for nonexistent token"); - - return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; - } - - /** - * @notice Sets the base URI. - */ - function setBaseURI(string calldata _baseURI) public { - baseURI = _baseURI; - } - - /** - * @notice Check if the transaction can be executed successfully - */ - function _checkExecution(ERC6358TransactionData memory txData) internal view { - NonFungible memory nonFungible = decodeData(txData.payload); - if (nonFungible.op == TRANSFER) { - _checkOmniverseTransfer(txData.from, nonFungible.tokenId); - } - else if (nonFungible.op == MINT) { - _checkOwner(txData.from); - _checkOmniverseMint(nonFungible.tokenId); - } - else if (nonFungible.op == BURN) { - _checkOwner(txData.from); - _checkOmniverseBurn(nonFungible.exData, nonFungible.tokenId); - } - else { - revert("OP code error"); - } - } - - /** - * @notice Returns the nearest exexutable delayed transaction info - * or returns default if not found - */ - function getExecutableDelayedTx() external view returns (DelayedTx memory ret) { - if (delayedTxs.length > 0) { - OmniverseTx storage cache = transactionCache[delayedTxs[0].sender]; - if (block.timestamp >= cache.timestamp + cdTime) { - ret = delayedTxs[0]; - } - } - } - - /** - * @notice Returns the count of delayed transactions - */ - function getDelayedTxCount() external view returns (uint256) { - return delayedTxs.length; - } - - /** - * @notice See {IERC6358NonFungible-omniverseBalanceOf} - * Returns the omniverse balance of a user - */ - function omniverseBalanceOf(bytes calldata _pk) external view override returns (uint256) { - return omniverseBalances[_pk]; - } - - /** - * @notice See {IERC6358NonFungible-omniverseOwnerOf} - * Returns the owner of a token - */ - function omniverseOwnerOf(uint256 _tokenId) external view returns (bytes memory) { - bytes memory ret = omniverseOwners[_tokenId]; - require(keccak256(ret) != keccak256(bytes("")), "Token not exist"); - return ret; - } - - /** - * @notice Receive and check an omniverse transaction - */ - function _omniverseTransaction(ERC6358TransactionData memory _data) internal { - // Check if the tx initiateSC is correct - bool found = false; - for (uint256 i = 0; i < members.length; i++) { - if (members[i].chainId == _data.chainId) { - require(keccak256(members[i].contractAddr) == keccak256(_data.initiateSC), "Wrong initiateSC"); - found = true; - } - } - require(found, "Wrong initiateSC"); - - // Check if the sender is honest - // to be continued, we can use block list instead of `isMalicious` - require(!isMalicious(_data.from), "User malicious"); - - // Verify the signature - VerifyResult verifyRet = OmniverseProtocolHelper.verifyTransaction(transactionRecorder[_data.from], _data); - - if (verifyRet == VerifyResult.Success) { - // Check cache - OmniverseTx storage cache = transactionCache[_data.from]; - require(cache.timestamp == 0, "Transaction cached"); - // Logic verification - _checkExecution(_data); - // Delays in executing - cache.txData = _data; - cache.timestamp = block.timestamp; - delayedTxs.push(DelayedTx(_data.from, _data.nonce)); - if (_data.chainId == chainId) { - emit TransactionSent(_data.from, _data.nonce); - } - } - else if (verifyRet == VerifyResult.Duplicated) { - emit TransactionExecuted(_data.from, _data.nonce); - } - else if (verifyRet == VerifyResult.Malicious) { - // Slash - } - } - - /** - * @notice Check if an omniverse transfer operation can be executed successfully - */ - function _checkOmniverseTransfer(bytes memory _from, uint256 _tokenId) internal view { - require(keccak256(this.omniverseOwnerOf(_tokenId)) == keccak256(_from), "Not owner"); - } - - /** - * @notice Exucute an omniverse transfer operation - */ - function _omniverseTransfer(bytes memory _from, bytes memory _to, uint256 _tokenId) internal { - _checkOmniverseTransfer(_from, _tokenId); - - omniverseOwners[_tokenId] = _to; - omniverseBalances[_from] -= 1; - omniverseBalances[_to] += 1; - - emit OmniverseTokenTransfer(_from, _to, _tokenId); - - address fromAddr = OmniverseProtocolHelper.pkToAddress(_from); - address toAddr = OmniverseProtocolHelper.pkToAddress(_to); - accountsMap[toAddr] = _to; - emit Transfer(fromAddr, toAddr, _tokenId); - } - - /** - * @notice Check if the public key is the owner - */ - function _checkOwner(bytes memory _pk) internal view { - address fromAddr = OmniverseProtocolHelper.pkToAddress(_pk); - require(fromAddr == owner(), "Not owner"); - } - - /** - * @notice Check if an omniverse mint operation can be executed successfully - */ - function _checkOmniverseMint(uint256 _tokenId) internal view { - require(keccak256(omniverseOwners[_tokenId]) == keccak256(""), "Token already exist"); - } - - /** - * @notice Execute an omniverse mint operation - */ - function _omniverseMint(bytes memory _to, uint256 _tokenId) internal { - _checkOmniverseMint(_tokenId); - - omniverseOwners[_tokenId] = _to; - omniverseBalances[_to] += 1; - emit OmniverseTokenTransfer("", _to, _tokenId); - - address toAddr = OmniverseProtocolHelper.pkToAddress(_to); - accountsMap[toAddr] = _to; - emit Transfer(address(0), toAddr, _tokenId); - } - - /** - * @notice Check if an omniverse burn operation can be executed successfully - */ - function _checkOmniverseBurn(bytes memory _from, uint256 _tokenId) internal view { - require(keccak256(this.omniverseOwnerOf(_tokenId)) == keccak256(_from), "Not token owner"); - } - - /** - * @notice Execute an omniverse burn operation - */ - function _omniverseBurn(bytes memory _from, uint256 _tokenId) internal { - delete omniverseOwners[_tokenId]; - omniverseBalances[_from] -= 1; - emit OmniverseTokenTransfer(_from, "", _tokenId); - - address fromAddr = OmniverseProtocolHelper.pkToAddress(_from); - emit Transfer(fromAddr, address(0), _tokenId); - } - - /** - * @notice Add new chain members to the token - */ - function setMembers(Member[] calldata _members) external onlyOwner { - for (uint256 i = 0; i < _members.length; i++) { - if (i < members.length) { - members[i] = _members[i]; - } - else { - members.push(_members[i]); - } - } - - for (uint256 i = _members.length; i < members.length; i++) { - delete members[i]; - } - } - - /** - * @notice Returns chain members of the token - */ - function getMembers() external view returns (Member[] memory) { - return members; - } - - /** - * @notice See IERC6358NonFungible - */ - function getTransactionCount(bytes memory _pk) external override view returns (uint256) { - return transactionRecorder[_pk].txList.length; - } - - /** - * @notice See IERC6358NonFungible - */ - function getTransactionData(bytes calldata _user, uint256 _nonce) external override view returns (ERC6358TransactionData memory txData, uint256 timestamp) { - RecordedCertificate storage rc = transactionRecorder[_user]; - OmniverseTx storage omniTx = rc.txList[_nonce]; - txData = omniTx.txData; - timestamp = omniTx.timestamp; - } - - /** - * @notice Set the cooling down time of an omniverse transaction - */ - function setCoolingDownTime(uint256 _time) external { - cdTime = _time; - } - - /** - * @notice Index the user is malicious or not - */ - function isMalicious(bytes memory _pk) public view returns (bool) { - RecordedCertificate storage rc = transactionRecorder[_pk]; - return (rc.evilTxList.length > 0); - } - - /** - * @notice See IERC6358NonFungible - */ - function getChainId() external view returns (uint32) { - return chainId; - } - - /** - * @notice Decode `_data` from bytes to Fungible - */ - function decodeData(bytes memory _data) internal pure returns (NonFungible memory) { - (uint8 op, bytes memory exData, uint256 tokenId) = abi.decode(_data, (uint8, bytes, uint256)); - return NonFungible(op, exData, tokenId); - } - - /** - * @notice See IERC6358Application - */ - function getPayloadRawData(bytes memory _payload) external pure returns (bytes memory) { - NonFungible memory nonFungible = decodeData(_payload); - return abi.encodePacked(nonFungible.op, nonFungible.exData, uint128(nonFungible.tokenId)); - } -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358.sol deleted file mode 100644 index 834c90aa152605..00000000000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -/** - * @notice Omniverse transaction data structure - * @member nonce: The number of the o-transactions. If the current nonce of an omniverse account is `k`, the valid nonce of this o-account in the next o-transaction is `k+1`. - * @member chainId: The chain where the o-transaction is initiated - * @member initiateSC: The contract address from which the o-transaction is first initiated - * @member from: The Omniverse account which signs the o-transaction - * @member payload: The encoded bussiness logic data, which is maintained by the developer - * @member signature: The signature of the above informations. - */ -struct ERC6358TransactionData { - uint128 nonce; - uint32 chainId; - bytes initiateSC; - bytes from; - bytes payload; - bytes signature; -} - -/** - * @notice Interface of the ERC Omniverse-DLT - */ -interface IERC6358 { - /** - * @notice Emitted when a o-transaction which has nonce `nonce` and was signed by user `pk` is sent by calling {sendOmniverseTransaction} - */ - event TransactionSent(bytes pk, uint256 nonce); - - /** - * @notice Sends an omniverse transaction - * @dev - * Note: MUST implement the validation of the `_data.signature` - * Note: A map maintaining the omniverse account and the related transaction nonce is RECOMMENDED - * Note: MUST implement the validation of the `_data.nonce` according to the current account nonce - * Note: MUST implement the validation of the `_data. payload` - * Note: This interface is just for sending an omniverse transaction, and the execution MUST NOT be within this interface - * Note: The actual execution of an omniverse transaction is RECOMMENDED to be in another function and MAY be delayed for a time, - * which is determined all by who publishes an O-DLT token - * @param _data: the omniverse transaction data with type {ERC6358TransactionData} - * See more information in the defination of {ERC6358TransactionData} - * - * Emit a {TransactionSent} event - */ - function sendOmniverseTransaction(ERC6358TransactionData calldata _data) external; - - /** - * @notice Get the number of omniverse transactions sent by user `_pk`, - * which is also the valid `nonce` of a new omniverse transactions of user `_pk` - * @param _pk: Omniverse account to be queried - * @return The number of omniverse transactions sent by user `_pk` - */ - function getTransactionCount(bytes memory _pk) external view returns (uint256); - - /** - * @notice Get the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce` - * @param _user Omniverse account to be queried - * @param _nonce The nonce to be queried - * @return Returns the transaction data `txData` and timestamp `timestamp` of the user `_use` at a specified nonce `_nonce` - */ - function getTransactionData(bytes calldata _user, uint256 _nonce) external view returns (ERC6358TransactionData memory, uint256); - - /** - * @notice Get the chain ID - * @return Returns the chain ID - */ - function getChainId() external view returns (uint32); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358Application.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358Application.sol deleted file mode 100644 index fcf168a180adf6..00000000000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358Application.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -/** - * @notice Interface of the omniverse application contract - */ -interface IERC6358Application { - /** - * @notice Emitted when a o-transaction which has nonce `nonce` and was signed by user `pk` is executed - */ - event TransactionExecuted(bytes pk, uint256 nonce); - - /** - * @notice From the `_payload`, calculate the raw data which is used to generate signature - * @param _payload Original data committed by synchronizers, and stored in hostorical transaction list - * @return Returns The raw data of `_payload` - */ - function getPayloadRawData(bytes memory _payload) external pure returns (bytes memory); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol deleted file mode 100644 index a82c92340be15e..00000000000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358Fungible.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "./IERC6358.sol"; -import "./IERC6358Application.sol"; - -/** - * @notice Interface of the omniverse fungible token, which inherits {IERC6358} - */ -interface IERC6358Fungible is IERC6358, IERC6358Application { - /** - * @notice Get the omniverse balance of a user `_pk` - * @param _pk Omniverse account to be queried - * @return Returns the omniverse balance of a user `_pk` - */ - function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol b/assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol deleted file mode 100644 index 5b62ed62a86de6..00000000000000 --- a/assets/eip-6358/src/contracts/interfaces/IERC6358NonFungible.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "./IERC6358.sol"; -import "./IERC6358Application.sol"; - -/** - * @notice Interface of the omniverse non fungible token, which inherits {IERC6358} - */ -interface IERC6358NonFungible is IERC6358, IERC6358Application { - /** - * @notice Get the number of tokens in account `_pk` - * @param _pk Omniverse account to be queried - * @return Returns the number of tokens in account `_pk` - */ - function omniverseBalanceOf(bytes calldata _pk) external view returns (uint256); - - /** - * @notice Get the owner of a token `tokenId` - * @param _tokenId Omniverse token id to be queried - * @return Returns the owner of a token `tokenId` - */ - function omniverseOwnerOf(uint256 _tokenId) external view returns (bytes memory); -} \ No newline at end of file diff --git a/assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol b/assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol deleted file mode 100644 index eccfbeb55f175c..00000000000000 --- a/assets/eip-6358/src/contracts/libraries/OmniverseProtocolHelper.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0 <0.9.0; - -import "../interfaces/IERC6358.sol"; -import "../interfaces/IERC6358Application.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -/** - * @notice Used to record one omniverse transaction data - * txData: The original omniverse transaction data committed to the contract - * timestamp: When the omniverse transaction data is committed - */ -struct OmniverseTx { - ERC6358TransactionData txData; - uint256 timestamp; -} - -/** - * @notice An malicious omniverse transaction data - * oData: The recorded omniverse transaction data - * hisNonce: The nonce of the historical transaction which it conflicts with - */ -struct EvilTxData { - OmniverseTx oData; - uint256 hisNonce; -} - -/** - * @notice Used to record the historical omniverse transactions of a user - * txList: Successful historical omniverse transaction list - * evilTxList: Malicious historical omniverse transaction list - */ -struct RecordedCertificate { - OmniverseTx[] txList; - EvilTxData[] evilTxList; -} - -// Result of verification of an omniverse transaction -enum VerifyResult { - Success, - Malicious, - Duplicated -} - -/** - * @notice The library is mainly responsible for omniverse transaction verification and - * provides some basic methods. - * NOTE The verification method is for reference only, and developers can design appropriate - * verification mechanism based on their bussiness logic. - */ -library OmniverseProtocolHelper { - /** - * @notice Get the raw data of a transaction - */ - function getRawData(ERC6358TransactionData memory _data) internal view returns (bytes memory) { - bytes memory payloadRawData = IERC6358Application(address(this)).getPayloadRawData(_data.payload); - bytes memory rawData = abi.encodePacked(_data.nonce, _data.chainId, _data.initiateSC, _data.from, payloadRawData); - return rawData; - } - - /** - * @notice Get the hash of a transaction - */ - function getTransactionHash(ERC6358TransactionData memory _data) internal view returns (bytes32) { - bytes memory rawData = getRawData(_data); - return keccak256(rawData); - } - - /** - * @notice Recover the address - */ - function recoverAddress(bytes32 _hash, bytes memory _signature) public pure returns (address) { - uint8 v; - bytes32 r; - bytes32 s; - assembly { - r := mload(add(_signature, 32)) - s := mload(add(_signature, 64)) - v := mload(add(_signature, 65)) - } - address recovered = ecrecover(_hash, v, r, s); - return recovered; - } - - /** - * @notice Convert the public key to evm address - */ - function pkToAddress(bytes memory _pk) public pure returns (address) { - bytes32 hash = keccak256(_pk); - return address(uint160(uint256(hash))); - } - - /** - * @notice Verify if the signature matches the address - */ - function verifySignature(bytes memory _rawData, bytes memory _signature, address _address) public pure returns (bool) { - bytes32 hash = keccak256(_rawData); - address pkAddress = recoverAddress(hash, _signature); - bytes memory PREFIX = hex"19457468657265756d205369676e6564204d6573736167653a0a"; - - if (pkAddress == address(0) || pkAddress != _address) { - hash = keccak256(abi.encodePacked(PREFIX, bytes(Strings.toString(_rawData.length)), _rawData)); - pkAddress = recoverAddress(hash, _signature); - if (pkAddress == address(0) || pkAddress != _address) { - return false; - } - } - - return true; - } - - /** - * @notice Verify an omniverse transaction - */ - function verifyTransaction(RecordedCertificate storage rc, ERC6358TransactionData memory _data) public returns (VerifyResult) { - bytes memory rawData = getRawData(_data); - address senderAddress = pkToAddress(_data.from); - require(verifySignature(rawData, _data.signature, senderAddress), "Signature error"); - - // Check nonce - uint256 nonce = rc.txList.length; - if (nonce == _data.nonce) { - return VerifyResult.Success; - } - else if (nonce > _data.nonce) { - // The message has been received, check conflicts - OmniverseTx storage hisTx = rc.txList[_data.nonce]; - bytes32 hisTxHash = getTransactionHash(hisTx.txData); - bytes32 txHash = getTransactionHash(_data); - if (hisTxHash != txHash) { - // to be continued, add to evil list, but can not be duplicated - EvilTxData storage evilTx = rc.evilTxList.push(); - evilTx.hisNonce = nonce; - evilTx.oData.txData = _data; - evilTx.oData.timestamp = block.timestamp; - return VerifyResult.Malicious; - } - else { - return VerifyResult.Duplicated; - } - } - else { - revert("Nonce error"); - } - } -} \ No newline at end of file diff --git a/assets/eip-6358/src/migrations/2_deploy_contracts.js b/assets/eip-6358/src/migrations/2_deploy_contracts.js deleted file mode 100644 index 353b16c39e20c6..00000000000000 --- a/assets/eip-6358/src/migrations/2_deploy_contracts.js +++ /dev/null @@ -1,38 +0,0 @@ -const OmniverseProtocolHelper = artifacts.require("OmniverseProtocolHelper"); -const ERC6358FungibleExample = artifacts.require("ERC6358FungibleExample"); -const ERC6358NonFungibleExample = artifacts.require("ERC6358NonFungibleExample"); -// const fs = require("fs"); - -const CHAIN_IDS = { - GOERLI: 1, - BSCTEST: 2, - MOCK: 10000, -}; - -module.exports = async function (deployer, network) { - // const contractAddressFile = './config/default.json'; - // let data = fs.readFileSync(contractAddressFile, 'utf8'); - // let jsonData = JSON.parse(data); - if (network == 'development') { - return; - } - // else if(!jsonData[network]) { - // console.error('There is no config for: ', network, ', please add.'); - // return; - // } - - await deployer.deploy(OmniverseProtocolHelper); - await deployer.link(OmniverseProtocolHelper, ERC6358FungibleExample); - await deployer.link(OmniverseProtocolHelper, ERC6358NonFungibleExample); - await deployer.deploy(ERC6358FungibleExample, CHAIN_IDS[network], "X", "X"); - await deployer.deploy(ERC6358NonFungibleExample, CHAIN_IDS[network], "X", "X"); - - // Update config - if (network.indexOf('-fork') != -1 || network == 'test' || network == 'development') { - return; - } - - // jsonData[network].ERC6358FungibleExampleAddress = ERC6358FungibleExample.address; - // jsonData[network].ERC6358NonFungibleExampleAddress = ERC6358NonFungibleExample.address; - // fs.writeFileSync(contractAddressFile, JSON.stringify(jsonData, null, '\t')); -}; diff --git a/assets/eip-6358/src/package.json b/assets/eip-6358/src/package.json deleted file mode 100644 index 63c4f5029ae974..00000000000000 --- a/assets/eip-6358/src/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "contracts", - "version": "1.0.0", - "description": "", - "main": "truffle-config.js", - "directories": { - "test": "test" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.7.3", - "@truffle/hdwallet-provider": "^2.1.0", - "bn.js": "^5.2.1", - "commander": "^9.4.1", - "config": "^3.3.8", - "eccrypto": "^1.1.6", - "keccak256": "^1.0.6", - "secp256k1": "^4.0.3", - "web3": "^1.8.0" - }, - "devDependencies": {}, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC" -} diff --git a/assets/eip-6358/src/test/.gitkeep b/assets/eip-6358/src/test/.gitkeep deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/assets/eip-6358/src/test/ERC6358Fungible.test.js b/assets/eip-6358/src/test/ERC6358Fungible.test.js deleted file mode 100644 index 86a484ffeaadde..00000000000000 --- a/assets/eip-6358/src/test/ERC6358Fungible.test.js +++ /dev/null @@ -1,429 +0,0 @@ -const utils = require('./utils'); -const BN = require('bn.js'); -const secp256k1 = require('secp256k1'); -const keccak256 = require('keccak256'); -const Web3 = require('web3'); -const providerUrl = 'http://localhost:8545'; -const web3js = new Web3(providerUrl); -const assert = require('assert'); - -const CHAIN_ID = 0; -const ONE_TOKEN = '1000000000000000000'; -const TEN_TOKEN = '10000000000000000000'; -const TOKEN_SYMBOL = 'ERC6358Fungible'; -const COOL_DOWN = 2; - -const TRANSFER = 0; -const MINT = 1; -const BURN = 2; - -const Fungible = artifacts.require('./ERC6358FungibleExample.sol'); -const OmniverseProtocolHelper = artifacts.require('./OmniverseProtocolHelper.sol'); -Fungible.defaults({ - gas: 8000000, -}); - -Fungible.numberFormat = 'String'; - -const owner = '0xe092b1fa25DF5786D151246E492Eed3d15EA4dAA'; -const user1 = '0xc0d8F541Ab8B71F20c10261818F2F401e8194049'; -const user2 = '0xf1F8Ef6b4D4Ba31079E2263eC85c03fD5a0802bF'; - -const ownerPk = '0xb0c4ae6f28a5579cbeddbf40b2209a5296baf7a4dc818f909e801729ecb5e663dce22598685e985a6ed1a557cf2145deba5290418b3cc00680a90accc9b93522'; -const user1Pk = '0x99f5789b8b0d903a6e868c5fb9971eedde37da046e69d49c903a1b33167e0f76d1f1269628bfcff54e0581a0b019502394754e900dcbb69bf30010d51967d780'; -const user2Pk = '0x25607735c05d91b504425c25567154aea2fd07e9a515b7872c7f783aa58333942b9d6ac3afacdccfe2585d1a4617f23a802a32bb6abafe13aaba2d386d44f52d'; - -const ownerSk = Buffer.from('0cc0c2de7e8c30525b4ca3b9e0b9703fb29569060d403261055481df7014f7fa', 'hex'); -const user1Sk = Buffer.from('b97de1848f97378ee439b37e776ffe11a2fff415b2f93dc240b2d16e9c184ba9', 'hex'); -const user2Sk = Buffer.from('42f3b9b31fcaaa03ca71cab7d194979d0d1bedf16f8f4e9414f0ed4df699dd10', 'hex'); - -let signData = (hash, sk) => { - let signature = secp256k1.ecdsaSign(Uint8Array.from(hash), Uint8Array.from(sk)); - return '0x' + Buffer.from(signature.signature).toString('hex') + (signature.recid == 0 ? '1b' : '1c'); -} - -let getRawData = (txData, op, params) => { - let bData; - if (op == MINT) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == TRANSFER) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == BURN) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - let ret = Buffer.concat([Buffer.from(new BN(txData.nonce).toString('hex').padStart(32, '0'), 'hex'), Buffer.from(new BN(txData.chainId).toString('hex').padStart(8, '0'), 'hex'), - Buffer.from(txData.initiateSC.slice(2), 'hex'), Buffer.from(txData.from.slice(2), 'hex'), bData]); - return ret; -} - -let encodeMint = (from, toPk, amount, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: Fungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [MINT, toPk, amount]) - } - let bData = getRawData(txData, MINT, [toPk, amount]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeBurn = (from, toPk, amount, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: Fungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [BURN, toPk, amount]) - } - let bData = getRawData(txData, BURN, [toPk, amount]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeTransfer = (from, toPk, amount, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: Fungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [TRANSFER, toPk, amount]) - } - let bData = getRawData(txData, TRANSFER, [toPk, amount]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -contract('ERC6358Fungible', function() { - before(async function() { - await initContract(); - }); - - let fungible; - - let initContract = async function() { - let protocol = await OmniverseProtocolHelper.new(); - Fungible.link(protocol); - fungible = await Fungible.new(CHAIN_ID, TOKEN_SYMBOL, TOKEN_SYMBOL, {from: owner}); - Fungible.address = fungible.address; - await fungible.setMembers([[CHAIN_ID, Fungible.address]]); - await fungible.setCoolingDownTime(COOL_DOWN); - } - - const mintToken = async function(from, toPk, amount) { - let nonce = await fungible.getTransactionCount(from.pk); - let txData = encodeMint(from, toPk, amount, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await fungible.triggerExecution(); - } - - describe('Verify transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Signature error', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - txData.signature = txData.signature.slice(0, -2); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Signature error'); - }); - }); - - describe('Sender not signer', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - txData.from = ownerPk; - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Signature error'); - }); - }); - - describe('Nonce error', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk) + 20; - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Nonce error'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - let ret = await fungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionSent'); - let count = await fungible.getTransactionCount(ownerPk); - assert(count == 0, "The count should be zero"); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await fungible.triggerExecution(); - count = await fungible.getTransactionCount(ownerPk); - assert(count == 1, "The count should be one"); - }); - }); - - describe('Cooling down', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Transaction cached'); - }); - }); - - describe('Transaction duplicated', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - let ret = await fungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionExecuted'); - }); - }); - - describe('Cooled down', function() { - it('should succeed', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let nonce = await fungible.getTransactionCount(ownerPk); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await fungible.triggerExecution(); - let count = await fungible.getTransactionCount(ownerPk); - assert(count == 2); - }); - }); - - describe('Malicious', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - let malicious = await fungible.isMalicious(ownerPk); - assert(malicious, "It should be malicious"); - }); - }); - }); - - describe('Personal signing', function() { - before(async function() { - await initContract(); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - let bData = getRawData(txData, MINT, [user2Pk, TEN_TOKEN]); - let hash = keccak256(Buffer.concat([Buffer.from('\x19Ethereum Signed Message:\n' + bData.length), bData])); - txData.signature = signData(hash, ownerSk); - let ret = await fungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionSent'); - let count = await fungible.getTransactionCount(ownerPk); - assert(count == 0, "The count should be zero"); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await fungible.triggerExecution(); - count = await fungible.getTransactionCount(ownerPk); - assert(count == 1, "The count should be one"); - }); - }); - }); - - describe('Omniverse Transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Wrong initiate smart contract', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - txData.initiateSC = user1; - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Wrong initiateSC'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - let count = await fungible.getDelayedTxCount(); - assert(count == 1, 'The number of delayed txs should be one'); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await fungible.triggerExecution(); - }); - }); - - describe('Malicious transaction', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - let count = await fungible.getDelayedTxCount(); - assert(count == 0, 'The number of delayed txs should be zero'); - }); - }); - - describe('User is malicious', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'User malicious'); - }); - }); - }); - - describe('Get executable delayed transaction', function() { - before(async function() { - await initContract(); - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - }); - - describe('Cooling down', function() { - it('should be none', async () => { - let tx = await fungible.getExecutableDelayedTx(); - assert(tx.sender == '0x', 'There should be no transaction'); - }); - }); - - describe('Cooled down', function() { - it('should be one transaction', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let tx = await fungible.getExecutableDelayedTx(); - assert(tx.sender == ownerPk, 'There should be one transaction'); - }); - }); - }); - - describe('Trigger execution', function() { - before(async function() { - await initContract(); - }); - - describe('No delayed transaction', function() { - it('should fail', async () => { - await utils.expectThrow(fungible.triggerExecution(), 'No delayed tx'); - }); - }); - - describe('Not executable', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TEN_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.expectThrow(fungible.triggerExecution(), 'Not executable'); - }); - }); - }); - - describe('Mint', function() { - before(async function() { - await initContract(); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeMint({pk: user2Pk, sk: user2Sk}, user1Pk, ONE_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Is owner', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await fungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await fungible.omniverseBalanceOf(user1Pk); - assert(ONE_TOKEN == balance, 'Balance should be one'); - }); - }); - }); - - describe('Burn', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeBurn({pk: user2Pk, sk: user2Sk}, user1Pk, ONE_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Exceed balance', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), "Exceed balance"); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await fungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await fungible.omniverseBalanceOf(user1Pk); - assert(0 == balance, 'Balance should be zero'); - }); - }); - }); - - describe('Transfer', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, ONE_TOKEN); - }); - - describe('Exceed balance', function() { - it('should fail', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TEN_TOKEN, nonce); - await utils.expectThrow(fungible.sendOmniverseTransaction(txData), 'Exceed balance'); - }); - }); - - describe('Balance enough', function() { - it('should succeed', async () => { - let nonce = await fungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, ONE_TOKEN, nonce); - await fungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await fungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await fungible.omniverseBalanceOf(user1Pk); - assert('0' == balance, 'Balance should be zero'); - balance = await fungible.omniverseBalanceOf(user2Pk); - assert(ONE_TOKEN == balance, 'Balance should be one'); - }); - }); - }); -}); \ No newline at end of file diff --git a/assets/eip-6358/src/test/ERC6358NonFungible.test.js b/assets/eip-6358/src/test/ERC6358NonFungible.test.js deleted file mode 100644 index b78b2e675ec00a..00000000000000 --- a/assets/eip-6358/src/test/ERC6358NonFungible.test.js +++ /dev/null @@ -1,433 +0,0 @@ -const utils = require('./utils'); -const BN = require('bn.js'); -const secp256k1 = require('secp256k1'); -const keccak256 = require('keccak256'); -const Web3 = require('web3'); -const providerUrl = 'http://localhost:8545'; -const web3js = new Web3(providerUrl); -const assert = require('assert'); - -const CHAIN_ID = 0; -const TOKEN_ID = 1; -const TOKEN_SYMBOL = 'ERC6358NonFungible'; -const COOL_DOWN = 2; - -const TRANSFER = 0; -const MINT = 1; -const BURN = 2; - -const NonFungible = artifacts.require('./ERC6358NonFungibleExample.sol'); -const OmniverseProtocolHelper = artifacts.require('./OmniverseProtocolHelper.sol'); -NonFungible.defaults({ - gas: 8000000, -}); - -NonFungible.numberFormat = 'String'; - -const owner = '0xe092b1fa25DF5786D151246E492Eed3d15EA4dAA'; -const user1 = '0xc0d8F541Ab8B71F20c10261818F2F401e8194049'; -const user2 = '0xf1F8Ef6b4D4Ba31079E2263eC85c03fD5a0802bF'; - -const ownerPk = '0xb0c4ae6f28a5579cbeddbf40b2209a5296baf7a4dc818f909e801729ecb5e663dce22598685e985a6ed1a557cf2145deba5290418b3cc00680a90accc9b93522'; -const user1Pk = '0x99f5789b8b0d903a6e868c5fb9971eedde37da046e69d49c903a1b33167e0f76d1f1269628bfcff54e0581a0b019502394754e900dcbb69bf30010d51967d780'; -const user2Pk = '0x25607735c05d91b504425c25567154aea2fd07e9a515b7872c7f783aa58333942b9d6ac3afacdccfe2585d1a4617f23a802a32bb6abafe13aaba2d386d44f52d'; - -const ownerSk = Buffer.from('0cc0c2de7e8c30525b4ca3b9e0b9703fb29569060d403261055481df7014f7fa', 'hex'); -const user1Sk = Buffer.from('b97de1848f97378ee439b37e776ffe11a2fff415b2f93dc240b2d16e9c184ba9', 'hex'); -const user2Sk = Buffer.from('42f3b9b31fcaaa03ca71cab7d194979d0d1bedf16f8f4e9414f0ed4df699dd10', 'hex'); - -let signData = (hash, sk) => { - let signature = secp256k1.ecdsaSign(Uint8Array.from(hash), Uint8Array.from(sk)); - return '0x' + Buffer.from(signature.signature).toString('hex') + (signature.recid == 0 ? '1b' : '1c'); -} - -let getRawData = (txData, op, params) => { - let bData; - if (op == MINT) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == TRANSFER) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - else if (op == BURN) { - bData = Buffer.concat([Buffer.from(new BN(op).toString('hex').padStart(2, '0'), 'hex'), Buffer.from(params[0].slice(2), 'hex'), Buffer.from(new BN(params[1]).toString('hex').padStart(32, '0'), 'hex')]); - } - let ret = Buffer.concat([Buffer.from(new BN(txData.nonce).toString('hex').padStart(32, '0'), 'hex'), Buffer.from(new BN(txData.chainId).toString('hex').padStart(8, '0'), 'hex'), - Buffer.from(txData.initiateSC.slice(2), 'hex'), Buffer.from(txData.from.slice(2), 'hex'), bData]); - return ret; -} - -let encodeMint = (from, toPk, tokenId, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: NonFungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [MINT, toPk, tokenId]) - } - let bData = getRawData(txData, MINT, [toPk, tokenId]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeBurn = (from, toPk, tokenId, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: NonFungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [BURN, toPk, tokenId]) - } - let bData = getRawData(txData, BURN, [toPk, tokenId]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -let encodeTransfer = (from, toPk, tokenId, nonce) => { - let txData = { - nonce: nonce, - chainId: CHAIN_ID, - initiateSC: NonFungible.address, - from: from.pk, - payload: web3js.eth.abi.encodeParameters(['uint8', 'bytes', 'uint256'], [TRANSFER, toPk, tokenId]) - } - let bData = getRawData(txData, TRANSFER, [toPk, tokenId]); - let hash = keccak256(bData); - txData.signature = signData(hash, from.sk); - return txData; -} - -contract('ERC6358NonFungible', function() { - before(async function() { - await initContract(); - }); - - let nonFungible; - - let initContract = async function() { - let protocol = await OmniverseProtocolHelper.new(); - NonFungible.link(protocol); - nonFungible = await NonFungible.new(CHAIN_ID, TOKEN_SYMBOL, TOKEN_SYMBOL, {from: owner}); - NonFungible.address = nonFungible.address; - await nonFungible.setMembers([[CHAIN_ID, NonFungible.address]]); - await nonFungible.setCoolingDownTime(COOL_DOWN); - } - - const mintToken = async function(from, toPk, tokenId) { - let nonce = await nonFungible.getTransactionCount(from.pk); - let txData = encodeMint(from, toPk, tokenId, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await nonFungible.triggerExecution(); - } - - describe('Verify transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Signature error', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - txData.signature = txData.signature.slice(0, -2); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Signature error'); - }); - }); - - describe('Sender not signer', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - txData.from = ownerPk; - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Signature error'); - }); - }); - - describe('Nonce error', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk) + 20; - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Nonce error'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - let ret = await nonFungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionSent'); - let count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 0, "The count should be zero"); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await nonFungible.triggerExecution(); - count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 1, "The count should be one"); - }); - }); - - describe('Cooling down', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID + 1, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Transaction cached'); - }); - }); - - describe('Transaction duplicated', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - let ret = await nonFungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionExecuted'); - }); - }); - - describe('Cooled down', function() { - it('should succeed', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let nonce = await nonFungible.getTransactionCount(ownerPk); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await nonFungible.triggerExecution(); - let count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 2); - }); - }); - - describe('Malicious', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID + 1, nonce); - await nonFungible.sendOmniverseTransaction(txData); - let malicious = await nonFungible.isMalicious(ownerPk); - assert(malicious, "It should be malicious"); - }); - }); - }); - - describe('Personal signing', function() { - before(async function() { - await initContract(); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - let bData = getRawData(txData, MINT, [user2Pk, TOKEN_ID]); - let hash = keccak256(Buffer.concat([Buffer.from('\x19Ethereum Signed Message:\n' + bData.length), bData])); - txData.signature = signData(hash, ownerSk); - let ret = await nonFungible.sendOmniverseTransaction(txData); - assert(ret.logs[0].event == 'TransactionSent'); - let count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 0, "The count should be zero"); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await nonFungible.triggerExecution(); - count = await nonFungible.getTransactionCount(ownerPk); - assert(count == 1, "The count should be one"); - }); - }); - }); - - describe('Omniverse Transaction', function() { - before(async function() { - await initContract(); - }); - - describe('Wrong initiate smart contract', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - txData.initiateSC = user1; - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Wrong initiateSC'); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - let count = await nonFungible.getDelayedTxCount(); - assert(count == 1, 'The number of delayed txs should be one'); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - ret = await nonFungible.triggerExecution(); - }); - }); - - describe('Malicious transaction', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk) - 1; - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - let count = await nonFungible.getDelayedTxCount(); - assert(count == 0, 'The number of delayed txs should be zero'); - }); - }); - - describe('User is malicious', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'User malicious'); - }); - }); - }); - - describe('Get executable delayed transaction', function() { - before(async function() { - await initContract(); - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - }); - - describe('Cooling down', function() { - it('should be none', async () => { - let tx = await nonFungible.getExecutableDelayedTx(); - assert(tx.sender == '0x', 'There should be no transaction'); - }); - }); - - describe('Cooled down', function() { - it('should be one transaction', async () => { - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let tx = await nonFungible.getExecutableDelayedTx(); - assert(tx.sender == ownerPk, 'There should be one transaction'); - }); - }); - }); - - describe('Trigger execution', function() { - before(async function() { - await initContract(); - }); - - describe('No delayed transaction', function() { - it('should fail', async () => { - await utils.expectThrow(nonFungible.triggerExecution(), 'No delayed tx'); - }); - }); - - describe('Not executable', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.expectThrow(nonFungible.triggerExecution(), 'Not executable'); - }); - }); - }); - - describe('Mint', function() { - before(async function() { - await initContract(); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeMint({pk: user2Pk, sk: user2Sk}, user1Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Is owner', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeMint({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await nonFungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let tokenOwner = await nonFungible.omniverseOwnerOf(TOKEN_ID); - assert(user1Pk == tokenOwner, 'Owner should be user1'); - let balance = await nonFungible.omniverseBalanceOf(user1Pk); - assert(TOKEN_ID == balance, 'Balance should be one'); - }); - }); - }); - - describe('Burn', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID); - }); - - describe('Not owner', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeBurn({pk: user2Pk, sk: user2Sk}, user1Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), "Not owner"); - }); - }); - - describe('Token not exist', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID + 1, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), "Token not exist"); - }); - }); - - describe('All conditions satisfied', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(ownerPk); - let txData = encodeBurn({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await nonFungible.triggerExecution(); - await utils.expectThrow(nonFungible.omniverseOwnerOf(TOKEN_ID), "Token not exist"); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let balance = await nonFungible.omniverseBalanceOf(user1Pk); - assert(0 == balance, 'Balance should be zero'); - }); - }); - }); - - describe('Transfer', function() { - before(async function() { - await initContract(); - await mintToken({pk: ownerPk, sk: ownerSk}, user1Pk, TOKEN_ID); - }); - - describe('Not token owner', function() { - it('should fail', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user2Pk, sk: user2Sk}, user2Pk, TOKEN_ID, nonce); - await utils.expectThrow(nonFungible.sendOmniverseTransaction(txData), 'Not owner'); - }); - }); - - describe('Balance enough', function() { - it('should succeed', async () => { - let nonce = await nonFungible.getTransactionCount(user1Pk); - let txData = encodeTransfer({pk: user1Pk, sk: user1Sk}, user2Pk, TOKEN_ID, nonce); - await nonFungible.sendOmniverseTransaction(txData); - await utils.sleep(COOL_DOWN); - await utils.evmMine(1, web3js.currentProvider); - let ret = await nonFungible.triggerExecution(); - assert(ret.logs[0].event == 'OmniverseTokenTransfer'); - let tokenOwner = await nonFungible.omniverseOwnerOf(TOKEN_ID); - assert(user2Pk == tokenOwner, 'Token owner should be user2'); - let balance = await nonFungible.omniverseBalanceOf(user1Pk); - assert('0' == balance, 'Balance should be zero'); - balance = await nonFungible.omniverseBalanceOf(user2Pk); - assert(TOKEN_ID == balance, 'Balance should be one'); - }); - }); - }); -}); \ No newline at end of file diff --git a/assets/eip-6358/src/test/utils.js b/assets/eip-6358/src/test/utils.js deleted file mode 100644 index 260ac7a25854a8..00000000000000 --- a/assets/eip-6358/src/test/utils.js +++ /dev/null @@ -1,85 +0,0 @@ -const Web3 = require('web3'); - -const expectThrow = async (promise, message) => { - try { - await promise; - } - catch (err) { - if (!message) { - const outOfGas = err.message.includes("out of gas"); - const invalidOpcode = err.message.includes("invalid opcode"); - assert( - outOfGas || invalidOpcode, - "Expected throw, got `" + err + "` instead" - ); - } - else { - const expectedException = err.message.includes(message); - assert(expectedException, - "Expected throw, got `" + err + "` instead") - } - return; - } - assert.fail("Expected throw not received"); -}; - -// Convert normal string to u8 array -function stringToByteArray(str) { - return Array.from(str, function(byte) { - return byte.charCodeAt(0); - }); -} - -// Convert u8 array to hex string -function toHexString(byteArray) { - return '0x' + Array.from(byteArray, function(byte) { - return ('0' + (byte & 0xFF).toString(16)).slice(-2); - }).join('') -} - -// Mine one block -async function evmMineOneBlock (web3js) { - await new Promise((resolve, reject) => { - web3js.send({ - jsonrpc: "2.0", - method: "evm_mine", - id: new Date().getTime() - }, (error, result) => { - if (error) { - return reject(error); - } - return resolve(result); - }); - }); -}; - -async function sleep(seconds) { - await new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, seconds * 1000); - }); -} - -// Mine blocks -async function evmMine (num) { - for (let i = 0; i < num; i++) { - await evmMineOneBlock(Web3.givenProvider); - } -}; - -// Returns the latest block -async function getBlock() { - const web3js = new Web3(Web3.givenProvider); - let block = await web3js.eth.getBlock("latest"); - return block; -} - -module.exports = { - stringToByteArray: stringToByteArray, - toHexString: toHexString, - expectThrow: expectThrow, - evmMine: evmMine, - getBlock: getBlock, - sleep: sleep -} \ No newline at end of file diff --git a/assets/eip-6366/contracts/EIP6366Core.sol b/assets/eip-6366/contracts/EIP6366Core.sol deleted file mode 100644 index 91e54d64a954f7..00000000000000 --- a/assets/eip-6366/contracts/EIP6366Core.sol +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; -import "./interfaces/IEIP6366Error.sol"; -import "./interfaces/IEIP6366Core.sol"; - -/** - * @dev Implement the core of EIP-6366 - */ -contract EIP6366Core is IEIP6366Core { - /** - * @dev Stored permission of an address - */ - mapping(address => uint256) private permissions; - - /** - * @dev Stored delegation information - */ - mapping(bytes32 => uint256) private delegations; - - /** - * @dev Transfer a subset of owning permission to a target address - * @param _to New permission owner's address - * @param _permission A subset of owning permission - */ - function transfer( - address _to, - uint256 _permission - ) external virtual override returns (bool success) { - return _transfer(_to, _permission); - } - - /** - * @dev Allowed a delegatee to act for permission owner's behalf - * @param _delegatee Delegatee address - * @param _permission A subset of permission - */ - function approve( - address _delegatee, - uint256 _permission - ) external virtual override returns (bool success) { - return _approve(_delegatee, _permission); - } - - /** - * @dev Get all owning permission of an address - * @param _owner Permission owner's address - */ - function permissionOf( - address _owner - ) external view virtual override returns (uint256 permission) { - return _permissionOf(_owner); - } - - /** - * @dev Checking the existance of required permission on a given permission set - * @param _required Required permission set - * @param _permission Checking permission set - */ - function permissionRequire( - uint256 _permission, - uint256 _required - ) external view virtual override returns (bool isPermissioned) { - return _permissionRequire(_permission, _required); - } - - /** - * @dev Checking if an actor has sufficient permission, by himself or from a delegation, on a given permission set - * @param _owner Permission owner's address - * @param _actor Actor's address - * @param _required Required permission set - */ - function hasPermission( - address _owner, - address _actor, - uint256 _required - ) external view override returns (bool isPermissioned) { - return _hasPermission(_owner, _actor, _required); - } - - /** - * @dev Get delegated permission that owner approved to delegatee - * @param _owner Permission owner's address - * @param _delegatee Delegatee's address - */ - function delegated( - address _owner, - address _delegatee - ) external view virtual override returns (uint256 permission) { - return _delegated(_owner, _delegatee); - } - - /** - * @dev Mint a new set of permission to a new owner - * @param _owner New permission owner - * @param _permission Permission - */ - function _mint( - address _owner, - uint256 _permission - ) internal returns (bool) { - permissions[_owner] = _permission; - emit Transfer(address(0x0), _owner, _permission); - return true; - } - - /** - * @dev Burn all permission of the owner - * @param _owner New permission owner - */ - function _burn(address _owner) internal returns (bool) { - emit Transfer(_owner, address(0x0), permissions[_owner]); - permissions[_owner] = 0; - return true; - } - - /** - * @dev Create an unique key that linked permission owner and delegatee - * @param _owner Permission owner's address - * @param _delegatee Delegate's address - */ - function _uniqueKey( - address _owner, - address _delegatee - ) private pure returns (bytes32) { - return keccak256(abi.encodePacked(_owner, _delegatee)); - } - - function _transfer( - address _to, - uint256 _permission - ) internal returns (bool success) { - address owner = msg.sender; - // Prevent permission to be burnt - if (permissions[_to] & _permission > 0) { - revert IEIP6366Error.DuplicatedPermission(_permission); - } - // Clean subset of permission from owner - permissions[owner] = permissions[owner] ^ _permission; - // Set subset of permission to new owner - permissions[_to] = permissions[_to] | _permission; - emit Transfer(owner, _to, _permission); - return true; - } - - function _approve( - address _delegatee, - uint256 _permission - ) internal returns (bool success) { - address owner = msg.sender; - delegations[_uniqueKey(owner, _delegatee)] = _permission; - emit Approval(owner, _delegatee, _permission); - return true; - } - - function _permissionOf( - address _owner - ) internal view returns (uint256 permission) { - return permissions[_owner]; - } - - function _permissionRequire( - uint256 _permission, - uint256 _required - ) internal pure returns (bool isPermissioned) { - return _required == _permission & _required; - } - - function _hasPermission( - address _owner, - address _actor, - uint256 _required - ) internal view returns (bool isPermissioned) { - return - _permissionRequire( - _permissionOf(_actor) | _delegated(_owner, _actor), - _required - ); - } - - function _delegated( - address _owner, - address _delegatee - ) internal view returns (uint256 permission) { - // Delegated permission can't be the superset of owner's permission - return - delegations[_uniqueKey(_owner, _delegatee)] & permissions[_owner]; - } -} diff --git a/assets/eip-6366/contracts/interfaces/IEIP6366Core.sol b/assets/eip-6366/contracts/interfaces/IEIP6366Core.sol deleted file mode 100644 index c9abc6a8533076..00000000000000 --- a/assets/eip-6366/contracts/interfaces/IEIP6366Core.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; - -/** - * @dev Defined the interface of the core of EIP6366 that MUST to be implemented - */ -interface IEIP6366Core { - event Transfer( - address indexed _from, - address indexed _to, - uint256 indexed _permission - ); - - event Approval( - address indexed _owner, - address indexed _delegatee, - uint256 indexed _permission - ); - - function transfer( - address _to, - uint256 _permission - ) external returns (bool success); - - function approve( - address _delegatee, - uint256 _permission - ) external returns (bool success); - - function permissionOf( - address _owner - ) external view returns (uint256 permission); - - function permissionRequire( - uint256 _permission, - uint256 _required - ) external view returns (bool isPermissioned); - - function hasPermission( - address _owner, - address _actor, - uint256 _required - ) external view returns (bool isPermissioned); - - function delegated( - address _owner, - address _delegatee - ) external view returns (uint256 permission); -} diff --git a/assets/eip-6366/contracts/interfaces/IEIP6366Error.sol b/assets/eip-6366/contracts/interfaces/IEIP6366Error.sol deleted file mode 100644 index 7c9d53413917b5..00000000000000 --- a/assets/eip-6366/contracts/interfaces/IEIP6366Error.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.7; - -/** - * @dev Define all possible errors that's RECOMMENDED to be implemented - */ -interface IEIP6366Error { - error AccessDenied(address _owner, address _actor, uint256 _permission); - - error DuplicatedPermission(uint256 _permission); - - error OutOfRange(); -} diff --git a/assets/eip-6381/contracts/EmotableRepository.sol b/assets/eip-6381/contracts/EmotableRepository.sol deleted file mode 100644 index 445ae2454f4fad..00000000000000 --- a/assets/eip-6381/contracts/EmotableRepository.sol +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "./IERC6381.sol"; -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -error BulkParametersOfUnequalLength(); -error ExpiredPresignedEmote(); -error InvalidSignature(); - -contract EmotableRepository is IERC6381 { - bytes32 public immutable DOMAIN_SEPARATOR = keccak256( - abi.encode( - "ERC-6381: Public Non-Fungible Token Emote Repository", - "1", - block.chainid, - address(this) - ) - ); - - // Used to avoid double emoting and control undoing - mapping(address => mapping(address => mapping(uint256 => mapping(bytes4 => uint256)))) - private _emotesUsedByEmoter; // Cheaper than using a bool - mapping(address => mapping(uint256 => mapping(bytes4 => uint256))) - private _emotesPerToken; - - function emoteCountOf( - address collection, - uint256 tokenId, - bytes4 emoji - ) public view returns (uint256) { - return _emotesPerToken[collection][tokenId][emoji]; - } - - function bulkEmoteCountOf( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis - ) public view returns (uint256[] memory) { - if( - collections.length != tokenIds.length || - collections.length != emojis.length - ){ - revert BulkParametersOfUnequalLength(); - } - - uint256[] memory counts = new uint256[](collections.length); - for (uint256 i; i < collections.length; ) { - counts[i] = _emotesPerToken[collections[i]][tokenIds[i]][emojis[i]]; - unchecked { - ++i; - } - } - return counts; - } - - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji - ) public view returns (bool) { - return _emotesUsedByEmoter[emoter][collection][tokenId][emoji] == 1; - } - - function haveEmotersUsedEmotes( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis - ) public view returns (bool[] memory) { - if( - emoters.length != collections.length || - emoters.length != tokenIds.length || - emoters.length != emojis.length - ){ - revert BulkParametersOfUnequalLength(); - } - - bool[] memory states = new bool[](collections.length); - for (uint256 i; i < collections.length; ) { - states[i] = _emotesUsedByEmoter[emoters[i]][collections[i]][tokenIds[i]][emojis[i]] == 1; - unchecked { - ++i; - } - } - return states; - } - - function emote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state - ) public { - bool currentVal = _emotesUsedByEmoter[msg.sender][collection][tokenId][ - emoji - ] == 1; - if (currentVal != state) { - if (state) { - _emotesPerToken[collection][tokenId][emoji] += 1; - } else { - _emotesPerToken[collection][tokenId][emoji] -= 1; - } - _emotesUsedByEmoter[msg.sender][collection][tokenId][emoji] = state - ? 1 - : 0; - emit Emoted(msg.sender, collection, tokenId, emoji, state); - } - } - - function bulkEmote( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states - ) public { - if( - collections.length != tokenIds.length || - collections.length != emojis.length || - collections.length != states.length - ){ - revert BulkParametersOfUnequalLength(); - } - - bool currentVal; - for (uint256 i; i < collections.length; ) { - currentVal = _emotesUsedByEmoter[msg.sender][collections[i]][tokenIds[i]][ - emojis[i] - ] == 1; - if (currentVal != states[i]) { - if (states[i]) { - _emotesPerToken[collections[i]][tokenIds[i]][emojis[i]] += 1; - } else { - _emotesPerToken[collections[i]][tokenIds[i]][emojis[i]] -= 1; - } - _emotesUsedByEmoter[msg.sender][collections[i]][tokenIds[i]][emojis[i]] = states[i] - ? 1 - : 0; - emit Emoted(msg.sender, collections[i], tokenIds[i], emojis[i], states[i]); - } - unchecked { - ++i; - } - } - } - - function prepareMessageToPresignEmote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state, - uint256 deadline - ) public view returns (bytes32) { - return keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collection, - tokenId, - emoji, - state, - deadline - ) - ); - } - - function bulkPrepareMessagesToPresignEmote( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states, - uint256[] memory deadlines - ) public view returns (bytes32[] memory) { - if( - collections.length != tokenIds.length || - collections.length != emojis.length || - collections.length != states.length || - collections.length != deadlines.length - ){ - revert BulkParametersOfUnequalLength(); - } - - bytes32[] memory messages = new bytes32[](collections.length); - for (uint256 i; i < collections.length; ) { - messages[i] = keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collections[i], - tokenIds[i], - emojis[i], - states[i], - deadlines[i] - ) - ); - unchecked { - ++i; - } - } - - return messages; - } - - function presignedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji, - bool state, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public { - if(block.timestamp > deadline){ - revert ExpiredPresignedEmote(); - } - bytes32 digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collection, - tokenId, - emoji, - state, - deadline - ) - ) - ) - ); - address signer = ecrecover(digest, v, r, s); - if(signer != emoter){ - revert InvalidSignature(); - } - - bool currentVal = _emotesUsedByEmoter[signer][collection][tokenId][ - emoji - ] == 1; - if (currentVal != state) { - if (state) { - _emotesPerToken[collection][tokenId][emoji] += 1; - } else { - _emotesPerToken[collection][tokenId][emoji] -= 1; - } - _emotesUsedByEmoter[signer][collection][tokenId][emoji] = state - ? 1 - : 0; - emit Emoted(signer, collection, tokenId, emoji, state); - } - } - - function bulkPresignedEmote( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states, - uint256[] memory deadlines, - uint8[] memory v, - bytes32[] memory r, - bytes32[] memory s - ) public { - if( - emoters.length != collections.length || - emoters.length != tokenIds.length || - emoters.length != emojis.length || - emoters.length != states.length || - emoters.length != deadlines.length || - emoters.length != v.length || - emoters.length != r.length || - emoters.length != s.length - ){ - revert BulkParametersOfUnequalLength(); - } - - bytes32 digest; - address signer; - bool currentVal; - for (uint256 i; i < collections.length; ) { - if (block.timestamp > deadlines[i]){ - revert ExpiredPresignedEmote(); - } - digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collections[i], - tokenIds[i], - emojis[i], - states[i], - deadlines[i] - ) - ) - ) - ); - signer = ecrecover(digest, v[i], r[i], s[i]); - if(signer != emoters[i]){ - revert InvalidSignature(); - } - - currentVal = _emotesUsedByEmoter[signer][collections[i]][tokenIds[i]][ - emojis[i] - ] == 1; - if (currentVal != states[i]) { - if (states[i]) { - _emotesPerToken[collections[i]][tokenIds[i]][emojis[i]] += 1; - } else { - _emotesPerToken[collections[i]][tokenIds[i]][emojis[i]] -= 1; - } - _emotesUsedByEmoter[signer][collections[i]][tokenIds[i]][emojis[i]] = states[i] - ? 1 - : 0; - emit Emoted(signer, collections[i], tokenIds[i], emojis[i], states[i]); - } - unchecked { - ++i; - } - } - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC6381).interfaceId || - interfaceId == type(IERC165).interfaceId; - } -} \ No newline at end of file diff --git a/assets/eip-6381/contracts/IERC6381.sol b/assets/eip-6381/contracts/IERC6381.sol deleted file mode 100644 index a579c5825d9620..00000000000000 --- a/assets/eip-6381/contracts/IERC6381.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -interface IERC6381 { - event Emoted( - address indexed emoter, - address indexed collection, - uint256 indexed tokenId, - bytes4 emoji, - bool on - ); - - function emoteCountOf( - address collection, - uint256 tokenId, - bytes4 emoji - ) external view returns (uint256); - - function bulkEmoteCountOf( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis - ) external view returns (uint256[] memory); - - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji - ) external view returns (bool); - - function haveEmotersUsedEmotes( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis - ) external view returns (bool[] memory); - - function prepareMessageToPresignEmote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state, - uint256 deadline - ) external view returns (bytes32); - - function bulkPrepareMessagesToPresignEmote( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states, - uint256[] memory deadlines - ) external view returns (bytes32[] memory); - - function emote( - address collection, - uint256 tokenId, - bytes4 emoji, - bool state - ) external; - - function bulkEmote( - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states - ) external; - - function presignedEmote( - address emoter, - address collection, - uint256 tokenId, - bytes4 emoji, - bool state, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function bulkPresignedEmote( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - bytes4[] memory emojis, - bool[] memory states, - uint256[] memory deadlines, - uint8[] memory v, - bytes32[] memory r, - bytes32[] memory s - ) external; -} \ No newline at end of file diff --git a/assets/eip-6381/contracts/mocks/ERC721Mock.sol b/assets/eip-6381/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index 1d450dddd0f330..00000000000000 --- a/assets/eip-6381/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} - - function mint(address to, uint256 amount) public { - _mint(to, amount); - } -} diff --git a/assets/eip-6381/hardhat.config.ts b/assets/eip-6381/hardhat.config.ts deleted file mode 100644 index f1b6fdec4582da..00000000000000 --- a/assets/eip-6381/hardhat.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.16", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6381/package.json b/assets/eip-6381/package.json deleted file mode 100644 index e4c6281c5af3d5..00000000000000 --- a/assets/eip-6381/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "emotable-repository", - "scripts": { - "test": "yarn typechain && hardhat test", - "typechain": "hardhat typechain", - "prettier": "prettier --write ." - }, - "engines": { - "node": ">=16.0.0" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4" - } -} diff --git a/assets/eip-6381/test/emotableRepository.ts b/assets/eip-6381/test/emotableRepository.ts deleted file mode 100644 index 18345bf4032d6a..00000000000000 --- a/assets/eip-6381/test/emotableRepository.ts +++ /dev/null @@ -1,492 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { BigNumber, Contract } from "ethers"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ERC721Mock, EmotableRepository } from "../typechain-types"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -async function tokenFixture() { - const factory = await ethers.getContractFactory("ERC721Mock"); - const token = await factory.deploy("Chunky", "CHNK"); - await token.deployed(); - - return token; -} - -async function emotableRepositoryFixture() { - const factory = await ethers.getContractFactory("EmotableRepository"); - const repository = await factory.deploy(); - await repository.deployed(); - - return repository; -} - -describe("RMRKEmotableRepositoryMock", async function () { - let token: ERC721Mock; - let repository: EmotableRepository; - let owner: SignerWithAddress; - let addrs: SignerWithAddress[]; - const tokenId = bn(1); - const emoji1 = Buffer.from("😎"); - const emoji2 = Buffer.from("😁"); - - beforeEach(async function () { - [owner, ...addrs] = await ethers.getSigners(); - token = await loadFixture(tokenFixture); - repository = await loadFixture(emotableRepositoryFixture); - }); - - it("can support IERC6381", async function () { - expect(await repository.supportsInterface("0xd9fac55a")).to.equal(true); - }); - - it("can support IERC165", async function () { - expect(await repository.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("does not support other interfaces", async function () { - expect(await repository.supportsInterface("0xffffffff")).to.equal(false); - }); - - describe("With minted tokens", async function () { - beforeEach(async function () { - await token.mint(owner.address, tokenId); - }); - - it("can emote", async function () { - await expect( - repository.connect(addrs[0]).emote(token.address, tokenId, emoji1, true) - ) - .to.emit(repository, "Emoted") - .withArgs( - addrs[0].address, - token.address, - tokenId.toNumber(), - emoji1, - true - ); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - }); - - it("can undo emote", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - - await expect(repository.emote(token.address, tokenId, emoji1, false)) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - false - ); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(0)); - }); - - it("can be emoted from different accounts", async function () { - await repository - .connect(addrs[0]) - .emote(token.address, tokenId, emoji1, true); - await repository - .connect(addrs[1]) - .emote(token.address, tokenId, emoji1, true); - await repository - .connect(addrs[2]) - .emote(token.address, tokenId, emoji2, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(2)); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(1)); - }); - - it("can add multiple emojis to same NFT", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - await repository.emote(token.address, tokenId, emoji2, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(1)); - }); - - it("does nothing if new state is the same as old state", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - await repository.emote(token.address, tokenId, emoji1, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - - await repository.emote(token.address, tokenId, emoji2, false); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(0)); - }); - - it("can bulk emote", async function () { - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(0), bn(0)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([false, false]); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - true - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(1), bn(1)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([true, true]); - }); - - it("can bulk undo emote", async function () { - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - true - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(1), bn(1)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([true, true]); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [false, false] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - false - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - false - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(0), bn(0)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([false, false]); - }); - - it("can bulk emote and unemote at the same time", async function () { - await repository.emote(token.address, tokenId, emoji2, true); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(0), bn(1)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([false, true]); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, false] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - false - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(1), bn(0)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([true, false]); - }); - - it("can not bulk emote if passing arrays of different length", async function () { - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - - await expect( - repository.bulkEmote( - [token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId], - [emoji1, emoji2], - [true, true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1], - [true, true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - }); - - it("can use presigned emote to react to token", async function () { - const message = await repository.prepareMessageToPresignEmote( - token.address, - tokenId, - emoji1, - true, - bn(9999999999) - ); - - const signature = await owner.signMessage(ethers.utils.arrayify(message)); - - const r: string = signature.substring(0, 66); - const s: string = "0x" + signature.substring(66, 130); - const v: number = parseInt(signature.substring(130, 132), 16); - - await expect( - repository - .connect(addrs[0]) - .presignedEmote( - owner.address, - token.address, - tokenId, - emoji1, - true, - bn(9999999999), - v, - r, - s - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ); - }); - - it("can use presigned emotes to bulk react to token", async function () { - const messages = await repository.bulkPrepareMessagesToPresignEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true], - [bn(9999999999), bn(9999999999)] - ); - - const signature1 = await owner.signMessage( - ethers.utils.arrayify(messages[0]) - ); - const signature2 = await owner.signMessage( - ethers.utils.arrayify(messages[1]) - ); - - const r1: string = signature1.substring(0, 66); - const s1: string = "0x" + signature1.substring(66, 130); - const v1: number = parseInt(signature1.substring(130, 132), 16); - const r2: string = signature2.substring(0, 66); - const s2: string = "0x" + signature2.substring(66, 130); - const v2: number = parseInt(signature2.substring(130, 132), 16); - - await expect( - repository - .connect(addrs[0]) - .bulkPresignedEmote( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true], - [bn(9999999999), bn(9999999999)], - [v1, v2], - [r1, r2], - [s1, s2] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - true - ); - }); - }); -}); diff --git a/assets/eip-6384/implementation/src/IEvalEIP712Buffer.sol b/assets/eip-6384/implementation/src/IEvalEIP712Buffer.sol deleted file mode 100644 index 2fcaf2917fffd6..00000000000000 --- a/assets/eip-6384/implementation/src/IEvalEIP712Buffer.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -interface IEvalEIP712Buffer { - function evalEIP712Buffer(bytes32 domainHash, string memory primaryType, bytes memory typedDataBuffer) - external - view - returns (string[] memory); -} diff --git a/assets/eip-6384/implementation/src/MyToken/MyToken.sol b/assets/eip-6384/implementation/src/MyToken/MyToken.sol deleted file mode 100644 index f3d479b34c5c31..00000000000000 --- a/assets/eip-6384/implementation/src/MyToken/MyToken.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {IEvalEIP712Buffer} from "../IEvalEIP712Buffer.sol"; -import {MyToken712ParserHelper} from "./MyToken712ParserHelper.sol"; -import {TransferParameters} from "./MyTokenStructs.sol"; - -contract MyToken is ERC20, EIP712, IEvalEIP712Buffer { - mapping(address => uint256) private _nonces; - address public eip712TransalatorContract; - - bytes32 private constant TYPE_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - bytes32 private constant TRANSFER_TYPEHASH = - keccak256("Transfer(address from,address to,uint256 amount,uint256 nonce,uint256 deadline)"); - - constructor(address _eip712Transaltor) ERC20("MyToken", "MT") EIP712("MyToken", "1") { - eip712TransalatorContract = _eip712Transaltor; - _mint(msg.sender, 1e18); - } - - function mintToCaller() public { - _mint(msg.sender, 1e18); - } - - function nonces(address owner) public view returns (uint256) { - return _nonces[owner]; - } - - function transferWithSig(address from, address to, uint256 amount, uint256 deadline, uint8 r, bytes32 v, bytes32 s) - public - { - require(block.timestamp <= deadline, "TransferSig: expired deadline"); - bytes32 structHash = keccak256(abi.encode(TRANSFER_TYPEHASH, from, to, amount, _nonces[from]++, deadline)); - // _hashTypedDataV4 is a helper function from EIP712.sol that gets the strcutHash and uses the domain separator in order to hash the message - bytes32 hash = _hashTypedDataV4(structHash); - address signer = ECDSA.recover(hash, r, v, s); - require(signer == from, "TransferSig: unauthorized"); - _transfer(from, to, amount); - } - - function DOMAIN_SEPARATOR() public view returns (bytes32) { - return _domainSeparatorV4(); - } - - function evalEIP712Buffer( - bytes32 domainSeparator, - string memory primaryType, - bytes memory typedDataBuffer - ) public view override returns (string[] memory) { - require( - keccak256(abi.encodePacked(primaryType)) == keccak256(abi.encodePacked("Transfer")), - "MyToken: invalid primary type" - ); - require(domainSeparator == _domainSeparatorV4(), "MyToken: Invalid domain"); - return MyToken712ParserHelper(eip712TransalatorContract).parseSig(encodedData); - } -} diff --git a/assets/eip-6384/implementation/src/MyToken/MyToken712ParserHelper.sol b/assets/eip-6384/implementation/src/MyToken/MyToken712ParserHelper.sol deleted file mode 100644 index 8b808455199a17..00000000000000 --- a/assets/eip-6384/implementation/src/MyToken/MyToken712ParserHelper.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "src/MyToken/MyToken.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -contract MyToken712ParserHelper { - string sigMessage = - "This is MyToken transferWithSig message, by signing this message you are authorizing the transfer of MyToken from your account to the recipient account."; - - struct Transfer { - address from; - address to; - uint256 amount; - uint256 nonce; - uint256 deadline; - } - - function parseSig(bytes memory signature) public view returns (string[] memory sigTranslatedMessage) { - Transfer memory transfer = abi.decode(signature, (Transfer)); - sigTranslatedMessage = new string[](3); - sigTranslatedMessage[0] = sigMessage; - sigTranslatedMessage[1] = Strings.toString(transfer.deadline); - sigTranslatedMessage[2] = string( - abi.encodePacked( - "By signing this message you allow ", - Strings.toHexString(transfer.to), - " to transfer ", - Strings.toString(transfer.amount), - " of MyToken from your account." - ) - ); - return sigTranslatedMessage; - } -} diff --git a/assets/eip-6384/implementation/src/MyToken/MyTokenStructs.sol b/assets/eip-6384/implementation/src/MyToken/MyTokenStructs.sol deleted file mode 100644 index a78f3a05dc7967..00000000000000 --- a/assets/eip-6384/implementation/src/MyToken/MyTokenStructs.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -struct TransferParameters { - address from; - address to; - uint256 amount; - uint256 nonce; - uint256 deadline; -} diff --git a/assets/eip-6384/implementation/src/SeaPort/SeaPort712ParserHelper.sol b/assets/eip-6384/implementation/src/SeaPort/SeaPort712ParserHelper.sol deleted file mode 100644 index f5487613e76db8..00000000000000 --- a/assets/eip-6384/implementation/src/SeaPort/SeaPort712ParserHelper.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {ItemType, OrderType, OfferItem, ConsiderationItem, OrderComponents} from "src/SeaPort/SeaPortStructs.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -contract SeaPort712ParserHelper { - bytes32 private domainSeperator = keccak256( - abi.encodePacked("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") - ); - - string sigMessage = - "This is a Seaport listing message, mostly used by OpenSea Dapp, be aware of the potential balance changes"; - - struct BalanceOut { - uint256 amount; - address token; - } - - struct BalanceIn { - uint256 amount; - address token; - } - - function getTokenNameByAddress(address _token) private view returns (string memory) { - if (_token == address(0)) { - return "ETH"; - } else { - (bool success, bytes memory returnData) = _token.staticcall(abi.encodeWithSignature("name()")); - if (success && returnData.length > 0) { - return string(returnData); - } else { - return "Unknown"; - } - } - } - - // need to manage array length because of the fact that default array values are 0x0 which represents 'native token' - function getElementIndexInArray(address addressToSearch, uint256 arrayLength, address[] memory visitedAddresses) - private - pure - returns (uint256) - { - for (uint256 i; i < arrayLength; i++) { - if (addressToSearch == visitedAddresses[i]) { - return i; - } - } - return visitedAddresses.length + 1; - } - - function parseSig(bytes memory signature) public view returns (string[] memory sigTranslatedMessage) { - OrderComponents memory order = abi.decode(signature, (OrderComponents)); - BalanceOut[] memory tempBalanceOut = new BalanceOut[](order.offer.length); - BalanceIn[] memory tempBalanceIn = new BalanceIn[](order.consideration.length); - address[] memory outTokenAddresses = new address[](order.offer.length); - address[] memory inTokenAddresses = new address[](order.consideration.length); - - uint256 outLength; - for (uint256 i; i < order.offer.length; i++) { - uint256 index = getElementIndexInArray(order.offer[i].token, outLength, outTokenAddresses); - if (index != outTokenAddresses.length + 1) { - tempBalanceOut[index].amount += order.offer[i].startAmount; - } else { - outTokenAddresses[outLength] = order.offer[i].token; - tempBalanceOut[outLength] = BalanceOut(order.offer[i].startAmount, order.offer[i].token); - outLength++; - } - } - - uint256 inLength; - for (uint256 i; i < order.consideration.length; i++) { - if (order.offerer == order.consideration[i].recipient) { - uint256 index = getElementIndexInArray(order.consideration[i].token, inLength, inTokenAddresses); - if (index != inTokenAddresses.length + 1) { - tempBalanceIn[index].amount += order.consideration[i].startAmount; - } else { - inTokenAddresses[inLength] = order.consideration[i].token; - tempBalanceIn[inLength] = - BalanceIn(order.consideration[i].startAmount, order.consideration[i].token); - inLength++; - } - } - } - - sigTranslatedMessage = new string[](outLength + inLength + 2); - sigTranslatedMessage[0] = sigMessage; - sigTranslatedMessage[1] = - string(abi.encodePacked("The signature is valid until ", Strings.toString(order.endTime))); - for (uint256 i; i < inLength; i++) { - sigTranslatedMessage[i + 2] = string( - abi.encodePacked( - "You will receive ", - Strings.toString(tempBalanceIn[i].amount), - " of ", - getTokenNameByAddress(tempBalanceIn[i].token) - ) - ); - } - - for (uint256 i; i < outLength; i++) { - sigTranslatedMessage[i + inLength + 2] = string( - abi.encodePacked( - "You will send ", - Strings.toString(tempBalanceOut[i].amount), - " of ", - getTokenNameByAddress(tempBalanceOut[i].token) - ) - ); - } - return (sigTranslatedMessage); - } -} diff --git a/assets/eip-6384/implementation/src/SeaPort/SeaPortMock.sol b/assets/eip-6384/implementation/src/SeaPort/SeaPortMock.sol deleted file mode 100644 index 73c374fcd38c6a..00000000000000 --- a/assets/eip-6384/implementation/src/SeaPort/SeaPortMock.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "src/IEvalEIP712Buffer.sol"; -import {ItemType, OrderType, OfferItem, ConsiderationItem, OrderComponents} from "src/SeaPort/SeaPortStructs.sol"; -import {SeaPort712ParserHelper} from "src/SeaPort/SeaPort712ParserHelper.sol"; - -contract SeaPortMock is IEvalEIP712Buffer { - address public immutable eip712TransalatorContract; - - bytes32 private constant TYPE_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - constructor(address _translator) { - eip712TransalatorContract = _translator; - } - - // SeaPort logic - - function evalEIP712Buffer( - bytes32 domainSeparator, - string memory primaryType, - bytes memory typedDataBuffer - ) public view override returns (string[] memory) { - require( - keccak256(abi.encodePacked(primaryType)) == keccak256(abi.encodePacked("OrderComponents")), - "SeaPortMock: Invalid primary type" - ); - require(domainSeparator == DOMAIN_SEPARATOR(), "SeaPortMock: Invalid domain"); - return SeaPort712ParserHelper(eip712TransalatorContract).parseSig(encodedSignature); - } - - function DOMAIN_SEPARATOR() public view returns (bytes32) { - // prettier-ignore - return keccak256( - abi.encode(TYPE_HASH, keccak256(bytes("Seaport")), keccak256(bytes("1.1")), block.chainid, address(this)) - ); - } -} diff --git a/assets/eip-6384/implementation/src/SeaPort/SeaPortStructs.sol b/assets/eip-6384/implementation/src/SeaPort/SeaPortStructs.sol deleted file mode 100644 index 3d19f6b8fd107f..00000000000000 --- a/assets/eip-6384/implementation/src/SeaPort/SeaPortStructs.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -enum ItemType -// 0: ETH on mainnet, MATIC on polygon, etc. -{ - NATIVE, - // 1: ERC20 items (ERC777 and ERC20 analogues could also technically work) - ERC20, - // 2: ERC721 items - ERC721, - // 3: ERC1155 items - ERC1155, - // 4: ERC721 items where a number of tokenIds are supported - ERC721_WITH_CRITERIA, - // 5: ERC1155 items where a number of ids are supported - ERC1155_WITH_CRITERIA -} - -enum OrderType -// 0: no partial fills, anyone can execute -{ - FULL_OPEN, - // 1: partial fills supported, anyone can execute - PARTIAL_OPEN, - // 2: no partial fills, only offerer or zone can execute - FULL_RESTRICTED, - // 3: partial fills supported, only offerer or zone can execute - PARTIAL_RESTRICTED -} - -struct OfferItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; -} - -struct ConsiderationItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; - address payable recipient; -} - -struct OrderComponents { - address offerer; - address zone; - OfferItem[] offer; - ConsiderationItem[] consideration; - OrderType orderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 conduitKey; - uint256 counter; -} diff --git a/assets/eip-6384/implementation/test/MyToken.t.sol b/assets/eip-6384/implementation/test/MyToken.t.sol deleted file mode 100644 index 2d6d750ba24cda..00000000000000 --- a/assets/eip-6384/implementation/test/MyToken.t.sol +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import "forge-std/Test.sol"; -import {MyToken, IEvalEIP712Buffer} from "src/MyToken/MyToken.sol"; -import {TransferParameters} from "src/MyToken/MyTokenStructs.sol"; -import {MyToken712ParserHelper} from "src/MyToken/MyToken712ParserHelper.sol"; -import {SigUtils} from "./SigUtils.sol"; - -contract MyTokenTest is Test { - MyToken myToken; - MyToken712ParserHelper myToken712ParserHelper; - SigUtils sigUtils; - uint256 internal ownerPrivateKey; - uint256 internal toPrivateKey; - - address internal owner; - address internal to; - - bytes32 private constant TYPE_HASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - function setUp() public { - myToken712ParserHelper = new MyToken712ParserHelper(); - myToken = new MyToken(address(myToken712ParserHelper)); - sigUtils = new SigUtils(myToken.DOMAIN_SEPARATOR()); - ownerPrivateKey = 0xA11CE; - toPrivateKey = 0xB0B; - owner = vm.addr(ownerPrivateKey); - to = vm.addr(toPrivateKey); - vm.prank(owner); - myToken.mintToCaller(); - } - - function testNonce() public { - uint256 currentNonce = myToken.nonces(address(this)); - } - - function test_Transfer() public { - TransferParameters memory transfer = generateSigPayload(); - bytes32 digest = sigUtils.getTypedDataHash(transfer); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest); - console.log(myToken.balanceOf(owner)); - - myToken.transferWithSig(transfer.from, transfer.to, transfer.amount, transfer.deadline, v, r, s); - console.log(myToken.balanceOf(owner)); - assertEq(myToken.balanceOf(owner), 0); - } - - function testEvalEIP712BufferTransfer() public view { - //SigUtils.Transfer memory transferPayload = generateSigPayload(); - TransferParameters memory transferPayload = generateSigPayload(); - bytes memory encodedTransfer = abi.encode(transferPayload); - string[] memory translatedSig = myToken.evalEIP712Buffer(myToken.DOMAIN_SEPARATOR(), "Transfer", encodedTransfer); - for (uint256 i = 0; i < translatedSig.length; i++) { - console.log(translatedSig[i]); - } - } - - function generateSigPayload() public view returns (TransferParameters memory transfer) { - transfer = TransferParameters({ - from: owner, - to: to, - amount: myToken.balanceOf(owner), - nonce: myToken.nonces(owner), - deadline: block.timestamp + 1000 - }); - //transfer = My - return transfer; - } -} diff --git a/assets/eip-6384/implementation/test/OrderGenerator.sol b/assets/eip-6384/implementation/test/OrderGenerator.sol deleted file mode 100644 index bb31a20cf17457..00000000000000 --- a/assets/eip-6384/implementation/test/OrderGenerator.sol +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {ItemType, OrderType, OfferItem, ConsiderationItem, OrderComponents} from "src/SeaPort/SeaPortStructs.sol"; - -contract OrderGenerator { - function generateOrder() public view returns (OrderComponents memory) { - OrderComponents memory order; - order.orderType = OrderType.FULL_OPEN; - order.offerer = msg.sender; - order.zone = address(0); - order.startTime = block.timestamp; - order.endTime = block.timestamp + 1000; - order.salt = 100; - order.conduitKey = bytes32(0); - order.counter = 1; - - order.offer = new OfferItem[](1); - order.offer[0].token = 0x696383fc9C5C8568C2E7aF8731279b58B9201394; - order.offer[0].itemType = ItemType.ERC721; - order.offer[0].startAmount = 1; - order.offer[0].endAmount = 1; - order.offer[0].identifierOrCriteria = 9243; - - order.consideration = new ConsiderationItem[](1); - order.consideration[0].itemType = ItemType.NATIVE; - order.consideration[0].token = address(0); - order.consideration[0].identifierOrCriteria = 0; - order.consideration[0].startAmount = 0; - order.consideration[0].endAmount = 0; - order.consideration[0].recipient = payable(msg.sender); - return order; - } -} diff --git a/assets/eip-6384/implementation/test/SeaPort712Parser.t.sol b/assets/eip-6384/implementation/test/SeaPort712Parser.t.sol deleted file mode 100644 index 390feccca84171..00000000000000 --- a/assets/eip-6384/implementation/test/SeaPort712Parser.t.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {SeaPort712ParserHelper} from "src/SeaPort/SeaPort712ParserHelper.sol"; -import {IEvalEIP712Buffer, SeaPortMock} from "src/SeaPort/SeaPortMock.sol"; -import "test/OrderGenerator.sol"; -import "forge-std/Test.sol"; - -contract SeaPort712ParserTest is Test, OrderGenerator { - SeaPortMock seaPortMock; - SeaPort712ParserHelper seaPort712ParserHelper; - - function setUp() public { - seaPort712ParserHelper = new SeaPort712ParserHelper(); - seaPortMock = new SeaPortMock(address(seaPort712ParserHelper)); - } - - function testEvalEIP712BufferSeaport() public view { - OrderComponents memory order = generateOrder(); - bytes memory encodedOrder = abi.encode(order); - string[] memory translatedSig = seaPortMock.evalEIP712Buffer(seaPortMock.DOMAIN_SEPARATOR(), "OrderComponents", encodedOrder); - for (uint256 i = 0; i < translatedSig.length; i++) { - console.log(translatedSig[i]); - } - } -} diff --git a/assets/eip-6384/implementation/test/SigUtils.sol b/assets/eip-6384/implementation/test/SigUtils.sol deleted file mode 100644 index 970e215b6f857c..00000000000000 --- a/assets/eip-6384/implementation/test/SigUtils.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {TransferParameters} from "src/MyToken/MyTokenStructs.sol"; - -contract SigUtils { - bytes32 internal DOMAIN_SEPARATOR; - - constructor(bytes32 _DOMAIN_SEPARATOR) { - DOMAIN_SEPARATOR = _DOMAIN_SEPARATOR; - } - - // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - //bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; - bytes32 public constant TRANSFER_TYPEHASH = - keccak256("Transfer(address from,address to,uint256 amount,uint256 nonce,uint256 deadline)"); - - // computes the hash of a permit - function getStructHash(TransferParameters memory _transfer) internal pure returns (bytes32) { - return keccak256( - abi.encode( - TRANSFER_TYPEHASH, _transfer.from, _transfer.to, _transfer.amount, _transfer.nonce, _transfer.deadline - ) - ); - } - - // computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer - function getTypedDataHash(TransferParameters memory _transfer) public view returns (bytes32) { - return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, getStructHash(_transfer))); - } -} diff --git a/assets/eip-6384/media/MiceyMask-non-compliant.png b/assets/eip-6384/media/MiceyMask-non-compliant.png deleted file mode 100644 index b1cc4bba9608be..00000000000000 Binary files a/assets/eip-6384/media/MiceyMask-non-compliant.png and /dev/null differ diff --git a/assets/eip-6384/media/ZenGo-EIP-compliant-warning.png b/assets/eip-6384/media/ZenGo-EIP-compliant-warning.png deleted file mode 100644 index fd3c5b165f8cec..00000000000000 Binary files a/assets/eip-6384/media/ZenGo-EIP-compliant-warning.png and /dev/null differ diff --git a/assets/eip-6454/contracts/IERC6454.sol b/assets/eip-6454/contracts/IERC6454.sol deleted file mode 100644 index 2080d5e5957867..00000000000000 --- a/assets/eip-6454/contracts/IERC6454.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -interface IERC6454 { - /** - * @notice Used to check whether the given token is transferable or not. - * @dev If this function returns `false`, the transfer of the token MUST revert execution. - * @dev If the tokenId does not exist, this method MUST revert execution, unless the token is being checked for - * minting. - * @param tokenId ID of the token being checked - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @return Boolean value indicating whether the given token is transferable - */ - function isTransferable(uint256 tokenId, address from, address to) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-6454/contracts/mocks/ERC721TransferableMock.sol b/assets/eip-6454/contracts/mocks/ERC721TransferableMock.sol deleted file mode 100644 index c4e92686bc9ab9..00000000000000 --- a/assets/eip-6454/contracts/mocks/ERC721TransferableMock.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "../IERC6454.sol"; -import "hardhat/console.sol"; - -error CannotTransferNonTransferable(); - -/** - * @title ERC721TransferableMock - * Used for tests - */ -contract ERC721TransferableMock is IERC6454, ERC721 { - address public owner; - - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) { - owner = msg.sender; - } - - function mint(address to, uint256 amount) public { - _mint(to, amount); - } - - function burn(uint256 tokenId) public { - _burn(tokenId); - } - - function isTransferable(uint256 tokenId, address from, address to) public view returns (bool) { - if (from == address(0x0) && to == address(0x0)){ - return false; - } - // Only allow minting and burning - if (from == address(0x0) || to == address(0x0)){ - return true; - } - _requireMinted(tokenId); - return false; - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 firstTokenId, - uint256 batchSize - ) internal virtual override { - super._beforeTokenTransfer(from, to, firstTokenId, batchSize); - - uint256 lastTokenId = firstTokenId + batchSize; - for (uint256 i = firstTokenId; i < lastTokenId; ) { - if (!isTransferable(i, from, to)) { - revert CannotTransferNonTransferable(); - } - unchecked { - i++; - } - } - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC721) returns (bool) { - return interfaceId == type(IERC6454).interfaceId - || super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-6454/hardhat.config.ts b/assets/eip-6454/hardhat.config.ts deleted file mode 100644 index f1b6fdec4582da..00000000000000 --- a/assets/eip-6454/hardhat.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.16", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-6454/package.json b/assets/eip-6454/package.json deleted file mode 100644 index 82b43d23a5818d..00000000000000 --- a/assets/eip-6454/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "transferable-tokens", - "scripts": { - "test": "yarn typechain && hardhat test", - "typechain": "hardhat typechain", - "prettier": "prettier --write ." - }, - "engines": { - "node": ">=16.0.0" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4" - } -} diff --git a/assets/eip-6454/test/transferable.ts b/assets/eip-6454/test/transferable.ts deleted file mode 100644 index ce218e476dfae6..00000000000000 --- a/assets/eip-6454/test/transferable.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { BigNumber } from "ethers"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ERC721TransferableMock } from "../typechain-types"; - -async function transferableTokenFixture(): Promise { - const factory = await ethers.getContractFactory("ERC721TransferableMock"); - const token = await factory.deploy("Chunky", "CHNK"); - await token.deployed(); - - return token; -} - -describe("Transferable", async function () { - let nonTransferable: ERC721TransferableMock; - let owner: SignerWithAddress; - let otherOwner: SignerWithAddress; - const tokenId = 1; - - beforeEach(async function () { - const signers = await ethers.getSigners(); - owner = signers[0]; - otherOwner = signers[1]; - nonTransferable = await loadFixture(transferableTokenFixture); - - await nonTransferable.mint(owner.address, 1); - await nonTransferable.mint(otherOwner.address, 2); - }); - - it("can support IRMRKNonTransferable", async function () { - expect(await nonTransferable.supportsInterface("0x91a6262f")).to.equal(true); - }); - - it("does not support other interfaces", async function () { - expect(await nonTransferable.supportsInterface("0xffffffff")).to.equal(false); - }); - - it("cannot transfer", async function () { - expect( - nonTransferable - .connect(owner) - ["safeTransferFrom(address,address,uint256)"]( - owner.address, - otherOwner.address, - tokenId + 1 - ) - ).to.be.revertedWithCustomError(nonTransferable, "CannotTransferNonTransferable"); - }); - - it("returns the expected transferability state", async function () { - expect(await nonTransferable['isTransferable(uint256,address,address)'](tokenId, ethers.constants.AddressZero, ethers.constants.AddressZero)).to.equal(false); - expect(await nonTransferable['isTransferable(uint256,address,address)'](tokenId, ethers.constants.AddressZero, otherOwner.address)).to.equal(true); - }) - - it("reverts if token does not exist", async function () { - await expect(nonTransferable['isTransferable(uint256,address,address)'](10, owner.address, otherOwner.address)).to.be.revertedWith("ERC721: invalid token ID"); - }); - - it("can burn", async function () { - await nonTransferable.connect(owner).burn(tokenId); - await expect(nonTransferable.ownerOf(tokenId)).to.be.revertedWith( - "ERC721: invalid token ID" - ); - }); -}); diff --git a/assets/eip-6464/contracts/IERC165.sol b/assets/eip-6464/contracts/IERC165.sol deleted file mode 100644 index 9f87593359b939..00000000000000 --- a/assets/eip-6464/contracts/IERC165.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface ERC165 { - /// @notice Query if a contract implements an interface - /// @param interfaceID The interface identifier, as specified in ERC-165 - /// @dev Interface identification is specified in ERC-165. This function - /// uses less than 30,000 gas. - /// @return `true` if the contract implements `interfaceID` and - /// `interfaceID` is not 0xffffffff, `false` otherwise - function supportsInterface(bytes4 interfaceID) external view returns (bool); -} diff --git a/assets/eip-6464/contracts/IERC6464.sol b/assets/eip-6464/contracts/IERC6464.sol deleted file mode 100644 index da0a1250011331..00000000000000 --- a/assets/eip-6464/contracts/IERC6464.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC721.sol"; - -/** - * @notice Extends ERC-721 to include per-token approval for multiple operators. - * @dev Off-chain indexers of approvals SHOULD assume that an operator is approved if either of `ERC721.Approval(…)` or - * `ERC721.ApprovalForAll(…, true)` events are witnessed without the corresponding revocation(s), even if an - * `ExplicitApprovalFor(…, false)` is emitted. - * @dev TODO: the ERC-165 identifier for this interface is TBD. - */ -interface IERC6464 is ERC721 { - /** - * @notice Emitted when approval is explicitly granted or revoked for a token. - */ - event ExplicitApprovalFor( - address indexed operator, - uint256 indexed tokenId, - bool approved - ); - - /** - * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are - * revoked for all tokens. - * @dev MUST be emitted upon calls to `revokeAllExplicitApprovals()`. - */ - event AllExplicitApprovalsRevoked(address indexed owner); - - /** - * @notice Emitted when all explicit approvals, as granted by either `setExplicitApprovalFor()` function, are - * revoked for the specific token. - * @param owner MUST be `ownerOf(tokenId)` as per ERC721; in the case of revocation due to transfer, this MUST be - * the `from` address expected to be emitted in the respective `ERC721.Transfer()` event. - */ - event AllExplicitApprovalsRevoked( - address indexed owner, - uint256 indexed tokenId - ); - - /** - * @notice Approves the operator to manage the asset on behalf of its owner. - * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner. - * @dev Approvals set via this method MUST be revoked upon transfer of the token to a new owner; equivalent to - * calling `revokeAllExplicitApprovals(tokenId)`, including associated events. - * @dev MUST emit `ApprovalFor(operator, tokenId, approved)`. - * @dev MUST NOT have an effect on any standard ERC721 approval setters / getters. - */ - function setExplicitApproval( - address operator, - uint256 tokenId, - bool approved - ) external; - - /** - * @notice Approves the operator to manage the token(s) on behalf of their owner. - * @dev MUST be equivalent to calling `setExplicitApprovalFor(operator, tokenId, approved)` for each `tokenId` in - * the array. - */ - function setExplicitApproval( - address operator, - uint256[] memory tokenIds, - bool approved - ) external; - - /** - * @notice Revokes all explicit approvals granted by `msg.sender`. - * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender)`. - */ - function revokeAllExplicitApprovals() external; - - /** - * @notice Revokes all excplicit approvals granted for the specified token. - * @dev Throws if `msg.sender` is not the current NFT owner, or an authorised operator of the current owner. - * @dev MUST emit `AllExplicitApprovalsRevoked(msg.sender, tokenId)`. - */ - function revokeAllExplicitApprovals(uint256 tokenId) external; - - /** - * @notice Query whether an address is an approved operator for a token. - */ - function isExplicitlyApprovedFor(address operator, uint256 tokenId) - external - view - returns (bool); -} - -interface IERC6464AnyApproval is ERC721 { - /** - * @notice Returns true if any of the following criteria are met: - * 1. `isExplicitlyApprovedFor(operator, tokenId) == true`; OR - * 2. `isApprovedForAll(ownerOf(tokenId), operator) == true`; OR - * 3. `getApproved(tokenId) == operator`. - * @dev The criteria MUST be extended if other mechanism(s) for approving operators are introduced. The criteria - * MUST include all approval approaches. - */ - function isApprovedFor(address operator, uint256 tokenId) - external - view - returns (bool); -} diff --git a/assets/eip-6464/contracts/IERC721.sol b/assets/eip-6464/contracts/IERC721.sol deleted file mode 100644 index cca04190cabfa8..00000000000000 --- a/assets/eip-6464/contracts/IERC721.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC165.sol"; - -/// @title ERC-721 Non-Fungible Token Standard -/// @dev See https://eips.ethereum.org/EIPS/eip-721 -/// Note: the ERC-165 identifier for this interface is 0x80ac58cd. -interface ERC721 is ERC165 { - /// @dev This emits when ownership of any NFT changes by any mechanism. - /// This event emits when NFTs are created (`from` == 0) and destroyed - /// (`to` == 0). Exception: during contract creation, any number of NFTs - /// may be created and assigned without emitting Transfer. At the time of - /// any transfer, the approved address for that NFT (if any) is reset to none. - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - /// @dev This emits when the approved address for an NFT is changed or - /// reaffirmed. The zero address indicates there is no approved address. - /// When a Transfer event emits, this also indicates that the approved - /// address for that NFT (if any) is reset to none. - event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); - - /// @dev This emits when an operator is enabled or disabled for an owner. - /// The operator can manage all NFTs of the owner. - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - - /// @notice Count all NFTs assigned to an owner - /// @dev NFTs assigned to the zero address are considered invalid, and this - /// function throws for queries about the zero address. - /// @param _owner An address for whom to query the balance - /// @return The number of NFTs owned by `_owner`, possibly zero - function balanceOf(address _owner) external view returns (uint256); - - /// @notice Find the owner of an NFT - /// @dev NFTs assigned to zero address are considered invalid, and queries - /// about them do throw. - /// @param _tokenId The identifier for an NFT - /// @return The address of the owner of the NFT - function ownerOf(uint256 _tokenId) external view returns (address); - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. When transfer is complete, this function - /// checks if `_to` is a smart contract (code size > 0). If so, it calls - /// `onERC721Received` on `_to` and throws if the return value is not - /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - /// @param data Additional data with no specified format, sent in call to `_to` - function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes memory data) external payable; - - /// @notice Transfers the ownership of an NFT from one address to another address - /// @dev This works identically to the other function with an extra data parameter, - /// except this function just sets data to "". - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE - /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE - /// THEY MAY BE PERMANENTLY LOST - /// @dev Throws unless `msg.sender` is the current owner, an authorized - /// operator, or the approved address for this NFT. Throws if `_from` is - /// not the current owner. Throws if `_to` is the zero address. Throws if - /// `_tokenId` is not a valid NFT. - /// @param _from The current owner of the NFT - /// @param _to The new owner - /// @param _tokenId The NFT to transfer - function transferFrom(address _from, address _to, uint256 _tokenId) external payable; - - /// @notice Change or reaffirm the approved address for an NFT - /// @dev The zero address indicates there is no approved address. - /// Throws unless `msg.sender` is the current NFT owner, or an authorized - /// operator of the current owner. - /// @param _approved The new approved NFT controller - /// @param _tokenId The NFT to approve - function approve(address _approved, uint256 _tokenId) external payable; - - /// @notice Enable or disable approval for a third party ("operator") to manage - /// all of `msg.sender`'s assets - /// @dev Emits the ApprovalForAll event. The contract MUST allow - /// multiple operators per owner. - /// @param _operator Address to add to the set of authorized operators - /// @param _approved True if the operator is approved, false to revoke approval - function setApprovalForAll(address _operator, bool _approved) external; - - /// @notice Get the approved address for a single NFT - /// @dev Throws if `_tokenId` is not a valid NFT. - /// @param _tokenId The NFT to find the approved address for - /// @return The approved address for this NFT, or the zero address if there is none - function getApproved(uint256 _tokenId) external view returns (address); - - /// @notice Query if an address is an authorized operator for another address - /// @param _owner The address that owns the NFTs - /// @param _operator The address that acts on behalf of the owner - /// @return True if `_operator` is an approved operator for `_owner`, false otherwise - function isApprovedForAll(address _owner, address _operator) external view returns (bool); -} \ No newline at end of file diff --git a/assets/eip-6551/diagram.png b/assets/eip-6551/diagram.png deleted file mode 100644 index 38d746d52718e7..00000000000000 Binary files a/assets/eip-6551/diagram.png and /dev/null differ diff --git a/assets/eip-6604/README.md b/assets/eip-6604/README.md deleted file mode 100644 index 662b651efc7e0c..00000000000000 --- a/assets/eip-6604/README.md +++ /dev/null @@ -1,7 +0,0 @@ -ERC-6604 -======== - - * [`AbstractERC20.sol`](./contracts/AbstractERC20.sol) - * [`AbstractToken.sol`](./contracts/AbstractToken.sol) - * [`GenericEIP712.sol`](./contracts/GenericEIP712.sol) - * [`IAbstractToken.sol`](./contracts/IAbstractToken.sol) diff --git a/assets/eip-6604/contracts/AbstractERC20.sol b/assets/eip-6604/contracts/AbstractERC20.sol deleted file mode 100644 index 2b938c2ed0c4a9..00000000000000 --- a/assets/eip-6604/contracts/AbstractERC20.sol +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.16; - -import { IERC165 } from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; -import { ERC20 } from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; -import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import { IAbstractToken, IAbstractERC20, AbstractTokenMessage, AbstractTokenMessageStatus } from './IAbstractToken.sol'; -import { AbstractToken } from './AbstractToken.sol'; - -contract AbstractERC20 is IERC165, AbstractToken, IAbstractERC20, ERC20 { - constructor( - string memory _name, - string memory _symbol, - uint256 supply, - address _signer - ) ERC20(_name, _symbol) AbstractToken(_signer) { - _mint(msg.sender, supply); - } - - function _validMeta(bytes calldata metadata) internal pure override returns (bool) { - /** ERC20 metadata - - id: 0 - not applicable - - amount: uint256 representing the number of tokens to mint on-chain - - uri: empty string '' - not applicable - */ - return metadata.length >= 32; - } - - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return - type(IERC165).interfaceId == interfaceId || - type(IERC20).interfaceId == interfaceId || - type(IAbstractERC20).interfaceId == interfaceId || - type(IAbstractToken).interfaceId == interfaceId; - } - - function id(AbstractTokenMessage calldata message) public pure returns (uint256) { - require(_validMeta(message.meta), 'invalid metadata'); - revert('ERC20s have no id'); - } - - function amount(AbstractTokenMessage calldata message) public pure returns (uint256) { - require(_validMeta(message.meta), 'invalid metadata'); - // the only metadata for ERC20 tokens is the amount - return uint256(bytes32(message.meta)); - } - - function uri(AbstractTokenMessage calldata message) public pure returns (string calldata) { - require(_validMeta(message.meta), 'invalid metadata'); - revert('ERC20s have no uri'); - } - - // transforms token(s) from message to contract - function _reify(AbstractTokenMessage calldata message) internal override(AbstractToken) { - // no permission checking! Callers must validate the message! - _mint(message.owner, amount(message)); - } - - // transforms token(s) from contract to message - function _dereify(AbstractTokenMessage calldata message) internal override(AbstractToken) { - // no permission checking! Callers must validate the message! - _burn(message.owner, amount(message)); - } - - function transfer( - address to, - uint256 _amount, - AbstractTokenMessage calldata message - ) external override returns (bool) { - reify(message); - return transfer(to, _amount); - } - - // reify the message and then transferFrom tokens - function transferFrom( - address from, - address to, - uint256 _amount, - AbstractTokenMessage calldata message - ) external override returns (bool) { - reify(message); - return transferFrom(from, to, _amount); - } -} diff --git a/assets/eip-6604/contracts/AbstractToken.sol b/assets/eip-6604/contracts/AbstractToken.sol deleted file mode 100644 index 689b42f681d45b..00000000000000 --- a/assets/eip-6604/contracts/AbstractToken.sol +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.16; - -import "hardhat/console.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import {IAbstractToken, IAbstractERC20, AbstractTokenMessage, AbstractTokenMessageStatus} from "./IAbstractToken.sol"; -import {GenericEIP712} from "./GenericEIP712.sol"; - -abstract contract AbstractToken is IAbstractToken, Ownable, GenericEIP712 { - event SetSigner(address signer); - mapping(bytes32 => bool) used; - address private signer; - - // The type of content signed in the EIP-712 signature - bytes32 internal constant TYPE_HASH = keccak256("AbstractTokenMessage(uint256 chainId,address implementation,address owner,uint256 nonce)"); - // bytes32 public constant TYPE_HASH = keccak256("AbstractTokenMessage(uint256 chainId,address implementation,address owner,bytes meta,uint256 nonce)"); - - modifier validMessage(AbstractTokenMessage calldata message) { - AbstractTokenMessageStatus s = status(message); - require(s != AbstractTokenMessageStatus.used, "message used"); - require(s != AbstractTokenMessageStatus.invalid, "message invalid"); - _; - } - - constructor(address _signer) GenericEIP712('AbstractToken', '1') { - _setSigner(_signer); - } - - // the actual mechanics of reifying the token depend on the type of token - function _reify(AbstractTokenMessage calldata message) internal virtual; - - // transforms token(s) from message to contract - function reify(AbstractTokenMessage calldata message) public validMessage(message) { - console.log("*** message status"); - console.log(uint256(status(message))); - // checks - require(message.chainId == block.chainid, "for other chain"); - require(message.implementation == address(this), "for other contract"); - - // effects - bytes32 id = messageId(message); - used[id] = true; - - // interactions - // the actual mechanics of creating the token depends on implementation - _reify(message); - emit Reify(message); - } - - // the actual mechanics of dereifying the token depend on the type of token - function _dereify(AbstractTokenMessage calldata message) internal virtual; - - // transforms token(s) from contract to message - function dereify(AbstractTokenMessage calldata message) public validMessage(message) { - // checks - require(message.chainId != block.chainid || message.implementation != address(this), "same contract"); - - // effects - bytes32 id = messageId(message); - used[id] = true; - - // interactions - _dereify(message); - emit Dereify(message); - } - - // check metadata - depends on implementation - function _validMeta(bytes calldata metadata) virtual internal view returns (bool); - - // check abstract token message validity: an abstract token message can only be reified if valid - function status(AbstractTokenMessage calldata message) - public - view - returns (AbstractTokenMessageStatus) - { - bytes32 id = messageId(message); - - // this message was once valid but now it is reified - if (used[id]) return AbstractTokenMessageStatus.used; - - // the metadata is not valid - if (!_validMeta(message.meta)) return AbstractTokenMessageStatus.invalid; - - // message must include a valid proof (EIP-712 signature) - // - // note that the message chainId and implementation may be different than this contract's! (It could be intended for another chain but still valid for dereification) - bytes32 digest = _hashTypedDataV4(id, message.chainId, message.implementation); - if(!SignatureChecker.isValidSignatureNow(signer, digest, message.proof)) return AbstractTokenMessageStatus.invalid; - - // all checks pass: the message is valid - return AbstractTokenMessageStatus.valid; - } - - function messageId(AbstractTokenMessage calldata message) - public - pure - returns (bytes32) - { - // Note that the message ID is also the EIP-712 hash of the message struct - the fields must match the contents of TYPE_HASH - return keccak256( - abi.encode( - TYPE_HASH, - message.chainId, - message.implementation, - message.owner, - // keccak256(message.meta), - message.nonce - ) - ); - } - - function _setSigner (address _signer) internal { - signer = _signer; - emit SetSigner(signer); - } - - // admin functions - function setSigner(address _signer) external onlyOwner { - _setSigner(_signer); - } - - function getSigner () external view returns (address) { - return signer; - } - - function _equal(string memory a, string memory b) internal pure returns (bool) { - return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)); - } -} diff --git a/assets/eip-6604/contracts/GenericEIP712.sol b/assets/eip-6604/contracts/GenericEIP712.sol deleted file mode 100644 index 3ff8dd78d53369..00000000000000 --- a/assets/eip-6604/contracts/GenericEIP712.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity =0.8.16; - -import { ECDSA } from '@openzeppelin/contracts/utils/cryptography/ECDSA.sol'; - -// based on open zeppelin implementation - allows verifying signatures intended for other chains -abstract contract GenericEIP712 { - bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; - uint256 private immutable _CACHED_CHAIN_ID; - address private immutable _CACHED_THIS; - - bytes32 private immutable _HASHED_NAME; - bytes32 private immutable _HASHED_VERSION; - bytes32 private immutable _TYPE_HASH; - - constructor(string memory name, string memory version) { - bytes32 hashedName = keccak256(bytes(name)); - bytes32 hashedVersion = keccak256(bytes(version)); - bytes32 typeHash = keccak256( - 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' - ); - _HASHED_NAME = hashedName; - _HASHED_VERSION = hashedVersion; - _CACHED_CHAIN_ID = block.chainid; - _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator( - typeHash, - hashedName, - hashedVersion, - block.chainid, - address(this) - ); - _CACHED_THIS = address(this); - _TYPE_HASH = typeHash; - } - - function _domainSeparatorV4(uint256 chainId, address implementation) internal view returns (bytes32) { - // return the cached domain separator for the original chain - if (implementation == _CACHED_THIS && chainId == _CACHED_CHAIN_ID) { - return _CACHED_DOMAIN_SEPARATOR; - } else { - // build a domain separator for another chain - return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION, chainId, implementation); - } - } - - function _buildDomainSeparator( - bytes32 typeHash, - bytes32 nameHash, - bytes32 versionHash, - uint256 chainId, - address implementation - ) private view returns (bytes32) { - return keccak256(abi.encode(typeHash, nameHash, versionHash, chainId, implementation)); - } - - function _hashTypedDataV4(bytes32 structHash, uint256 chainId, address implementation) - internal - view - virtual - returns (bytes32) - { - return ECDSA.toTypedDataHash(_domainSeparatorV4(chainId, implementation), structHash); - } -} diff --git a/assets/eip-6604/contracts/IAbstractToken.sol b/assets/eip-6604/contracts/IAbstractToken.sol deleted file mode 100644 index 4ba16ad2b5fa28..00000000000000 --- a/assets/eip-6604/contracts/IAbstractToken.sol +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8; - -import { IERC165 } from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; -import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import { IERC721 } from '@openzeppelin/contracts/token/ERC721/IERC721.sol'; -import { IERC1155 } from '@openzeppelin/contracts/token/ERC1155/IERC1155.sol'; - -struct AbstractTokenMessage { - uint256 chainId; // the chain where the token(s) can be reified - address implementation; // the contract by which the token(s) can be reified - address owner; // the address that owns the token(s) - bytes meta; // application-specific information defining the token(s) - uint256 nonce; // counter to allow otherwise duplicate messages - bytes proof; // application-specific information authorizing the creation of the token(s) -} - -enum AbstractTokenMessageStatus { - invalid, // the token message is rejected by the contract - valid, // the token message is valid and has not already been used - used // the token message has already been used to reify or dereify tokens -} - -interface IAbstractToken { - event Reify(AbstractTokenMessage); - event Dereify(AbstractTokenMessage); - - // transforms token(s) from message to contract - function reify(AbstractTokenMessage calldata message) external; - - // transforms token(s) from contract to message - function dereify(AbstractTokenMessage calldata message) external; - - // check abstract token message status: an abstract token message can only be reified if valid and not already reified - function status(AbstractTokenMessage calldata message) - external - view - returns (AbstractTokenMessageStatus status); - - // id of token in message - function id(AbstractTokenMessage calldata message) external view returns (uint256); - - // quantity of tokens in the message - function amount(AbstractTokenMessage calldata message) external view returns (uint256); - - // reference to further information on the tokens - function uri(AbstractTokenMessage calldata message) external view returns (string memory); -} - -// example abstract token interfaces -interface IAbstractERC20 is IAbstractToken, IERC20, IERC165 { - // reify the message and then transfer tokens - function transfer( - address to, - uint256 amount, - AbstractTokenMessage calldata message - ) external returns (bool); - - // reify the message and then transferFrom tokens - function transferFrom( - address from, - address to, - uint256 amount, - AbstractTokenMessage calldata message - ) external returns (bool); -} - -interface IAbstractERC721 is IAbstractToken, IERC721 { - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes calldata _data, - AbstractTokenMessage calldata message - ) external; - - function transferFrom( - address from, - address to, - uint256 tokenId, - AbstractTokenMessage calldata message - ) external; -} - -interface IAbstractERC1155 is IAbstractToken, IERC1155 { - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes calldata data, - AbstractTokenMessage calldata message - ) external; - - function safeBatchTransferFrom( - address from, - address to, - uint256[] calldata ids, - uint256[] calldata amounts, - bytes calldata data, - AbstractTokenMessage[] calldata messages - ) external; -} diff --git a/assets/eip-6617/contracts/EIP6617.sol b/assets/eip-6617/contracts/EIP6617.sol deleted file mode 100644 index f3def850250d89..00000000000000 --- a/assets/eip-6617/contracts/EIP6617.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: CC0 -pragma solidity ^0.8.7; - -import "./interfaces/IEIP6617.sol"; -import "./interfaces/IEIP6617Meta.sol"; - -contract EIP6617 is IEIP6617, IEIP6617Meta { - mapping (address => uint256) private _permissions; - mapping (uint256 => PermissionDescription) private _metadata; - - function hasPermission(address _user, uint256 _requiredPermission) - external - view - returns (bool) { - return _permissions[_user] & _requiredPermission == _requiredPermission; - } - - function grantPermission(address _user, uint256 _permissionToAdd) - external - returns (bool) { - _permissions[_user] |= _permissionToAdd; - - emit PermissionGranted(msg.sender, _permissionToAdd, _user); - - return true; - } - - function revokePermission(address _user, uint256 _permissionToRevoke) - external - returns (bool) { - _permissions[_user] = (_permissions[_user] | _permissionToRevoke) ^ _permissionToRevoke; - - emit PermissionRevoked(msg.sender, _permissionToRevoke, _user); - - return true; - } - - function getPermissionDescription(uint256 _permission) external view returns (PermissionDescription memory description) { - return _metadata[_permission]; - } - - function setPermissionDescription(uint256 _permission, string memory _name, string memory _description) - external - returns (bool success) { - _metadata[_permission] = PermissionDescription(_permission, _name,_description); - - emit UpdatePermissionDescription(_permission, _name, _description); - - return true; - } -} \ No newline at end of file diff --git a/assets/eip-6617/contracts/interfaces/IEIP6617.sol b/assets/eip-6617/contracts/interfaces/IEIP6617.sol deleted file mode 100644 index 632b482ef97636..00000000000000 --- a/assets/eip-6617/contracts/interfaces/IEIP6617.sol +++ /dev/null @@ -1,22 +0,0 @@ -//SPDX-License-Identifier: CC0 - -pragma solidity ^0.8.7; - -interface IEIP6617 { - - event PermissionGranted(address indexed _grantor, uint256 indexed _permission, address indexed _user); - event PermissionRevoked(address indexed _revoker, uint256 indexed _permission, address indexed _user); - - function hasPermission(address _user, uint256 _requiredPermission) - external - view - returns (bool); - - function grantPermission(address _user, uint256 _permissionToAdd) - external - returns (bool); - - function revokePermission(address _user, uint256 _permissionToRemove) - external - returns (bool); -} \ No newline at end of file diff --git a/assets/eip-6617/contracts/interfaces/IEIP6617Meta.sol b/assets/eip-6617/contracts/interfaces/IEIP6617Meta.sol deleted file mode 100644 index 37d25f754db28f..00000000000000 --- a/assets/eip-6617/contracts/interfaces/IEIP6617Meta.sol +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-License-Identifier: CC0 -pragma solidity ^0.8.7; - -interface IEIP6617Meta { - struct PermissionDescription { - uint256 permission; - string name; - string description; - } - - event UpdatePermissionDescription(uint256 indexed _permission, string indexed _name, string indexed _description); - - function getPermissionDescription(uint256 _permission) external view returns (PermissionDescription memory description); - - function setPermissionDescription( - uint256 _permission, - string memory _name, - string memory _description - ) external returns (bool success); -} \ No newline at end of file diff --git a/assets/eip-6662/auth-flow.png b/assets/eip-6662/auth-flow.png deleted file mode 100644 index b21206c6f7da7c..00000000000000 Binary files a/assets/eip-6662/auth-flow.png and /dev/null differ diff --git a/assets/eip-6672/contracts/ERC6672.sol b/assets/eip-6672/contracts/ERC6672.sol deleted file mode 100644 index f10425bea08603..00000000000000 --- a/assets/eip-6672/contracts/ERC6672.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "./interfaces/IERC6672.sol"; - -abstract contract ERC6672 is ERC721, IERC6672 { - using EnumerableSet for EnumerableSet.Bytes32Set; - - bytes4 public constant IERC6672_ID = type(IERC6672).interfaceId; - - mapping(address => mapping(uint256 => mapping(bytes32 => bool))) redemptionStatus; - mapping(address => mapping(uint256 => mapping(bytes32 => string))) - public memos; - mapping(address => mapping(uint256 => EnumerableSet.Bytes32Set)) redemptions; - - constructor() ERC721("Multiple RedeemableNFT", "mrNFT") {} - - function isRedeemed( - address _operator, - bytes32 _redemptionId, - uint256 _tokenId - ) external view returns (bool) { - return _isRedeemed(_operator, _redemptionId, _tokenId); - } - - function getRedemptionIds( - address _operator, - uint256 _tokenId - ) external view returns (bytes32[] memory) { - require( - redemptions[_operator][_tokenId].length() > 0, - "ERC6672: token doesn't have any redemptions." - ); - return redemptions[_operator][_tokenId].values(); - } - - function redeem( - bytes32 _redemptionId, - uint256 _tokenId, - string memory _memo - ) external { - address _operator = msg.sender; - require( - !_isRedeemed(_operator, _redemptionId, _tokenId), - "ERC6672: token already redeemed." - ); - _update(_operator, _redemptionId, _tokenId, _memo, true); - redemptions[_operator][_tokenId].add(_redemptionId); - emit Redeem( - _operator, - _tokenId, - ownerOf(_tokenId), - _redemptionId, - _memo - ); - } - - function cancel( - bytes32 _redemptionId, - uint256 _tokenId, - string memory _memo - ) external { - address _operator = msg.sender; - require( - _isRedeemed(_operator, _redemptionId, _tokenId), - "ERC6672: token doesn't redeemed." - ); - _update(_operator, _redemptionId, _tokenId, _memo, false); - _removeRedemption(_operator, _redemptionId, _tokenId); - emit Cancel(_operator, _tokenId, _redemptionId, _memo); - } - - function _isRedeemed( - address _operator, - bytes32 _redemptionId, - uint256 _tokenId - ) internal view returns (bool) { - require(_exists(_tokenId), "ERC6672: token doesn't exists."); - return redemptionStatus[_operator][_tokenId][_redemptionId]; - } - - function _update( - address _operator, - bytes32 _redemptionId, - uint256 _tokenId, - string memory _memo, - bool isRedeemed_ - ) internal { - redemptionStatus[_operator][_tokenId][_redemptionId] = isRedeemed_; - memos[_operator][_tokenId][_redemptionId] = _memo; - if (isRedeemed_) { - emit Redeem( - _operator, - _tokenId, - ownerOf(_tokenId), - _redemptionId, - _memo - ); - } else { - emit Cancel(_operator, _tokenId, _redemptionId, _memo); - } - } - - function _removeRedemption( - address _operator, - bytes32 _redemptionId, - uint256 _tokenId - ) internal { - redemptions[_operator][_tokenId].remove(_redemptionId); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC721, IERC165) returns (bool) { - return - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC6672).interfaceId || - super.supportsInterface(interfaceId); - } -} \ No newline at end of file diff --git a/assets/eip-6672/contracts/interfaces/IERC6672.sol b/assets/eip-6672/contracts/interfaces/IERC6672.sol deleted file mode 100644 index ff38a5df9df45f..00000000000000 --- a/assets/eip-6672/contracts/interfaces/IERC6672.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.16; - -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -interface IERC6672 is IERC721 { - event Redeem( - address indexed _operator, - uint256 indexed _tokenId, - address redeemer, - bytes32 _redemptionId, - string _memo - ); - - event Cancel( - address indexed _operator, - uint256 indexed _tokenId, - bytes32 _redemptionId, - string _memo - ); - - function isRedeemed( - address _operator, - bytes32 _redemptionId, - uint256 _tokenId - ) external view returns (bool); - - function getRedemptionIds( - address _operator, - uint256 _tokenId - ) external view returns (bytes32[] memory); - - function redeem( - bytes32 _redemptionId, - uint256 _tokenId, - string memory _memo - ) external; - - function cancel( - bytes32 _redemptionId, - uint256 _tokenId, - string memory _memo - ) external; -} \ No newline at end of file diff --git a/assets/eip-6735/address-aliasing-root-leaf-design.png b/assets/eip-6735/address-aliasing-root-leaf-design.png deleted file mode 100644 index dee65913a4d277..00000000000000 Binary files a/assets/eip-6735/address-aliasing-root-leaf-design.png and /dev/null differ diff --git a/assets/eip-6735/visual-Highlight-Path-Red-evm-based-aliasing..png b/assets/eip-6735/visual-Highlight-Path-Red-evm-based-aliasing..png deleted file mode 100644 index 48747294230c78..00000000000000 Binary files a/assets/eip-6735/visual-Highlight-Path-Red-evm-based-aliasing..png and /dev/null differ diff --git a/assets/eip-6785/contracts/ERC6785.sol b/assets/eip-6785/contracts/ERC6785.sol deleted file mode 100644 index 7dfbc98e8750f6..00000000000000 --- a/assets/eip-6785/contracts/ERC6785.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./IERC6785.sol"; - -contract ERC6785 is ERC721, Ownable, IERC6785 { - - /* - * bytes4(keccak256('setUtilityUri(uint256,string)')) = 0x4a048176 - * bytes4(keccak256('utilityUriOf(uint256)')) = 0x5e470cbc - * bytes4(keccak256('utilityHistoryOf(uint256)')) = 0xf96090b9 - * - * => 0x4a048176 ^ 0x5e470cbc ^ 0xf96090b9 == 0xed231d73 - */ - bytes4 public constant _INTERFACE_ID_ERC6785 = 0xed231d73; - - mapping(uint => string[]) private utilities; - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return - interfaceId == type(IERC6785).interfaceId || super.supportsInterface(interfaceId); - } - - function setUtilityUri(uint256 tokenId, string calldata utilityUri) override external onlyOwner { - utilities[tokenId].push(utilityUri); - emit UpdateUtility(tokenId, utilityUri); - } - - function utilityUriOf(uint256 tokenId) override external view returns (string memory) { - uint last = utilities[tokenId].length - 1; - return utilities[tokenId][last]; - } - - function utilityHistoryOf(uint256 tokenId) override external view returns (string[] memory){ - return utilities[tokenId]; - } -} diff --git a/assets/eip-6785/contracts/IERC6785.sol b/assets/eip-6785/contracts/IERC6785.sol deleted file mode 100644 index fc933dba28bfc2..00000000000000 --- a/assets/eip-6785/contracts/IERC6785.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC6785 { - - // Logged when the utility description URL of an NFT is changed - /// @notice Emitted when the utilityURL of an NFT is changed - /// The empty string for `utilityUri` indicates that there is no utility associated - event UpdateUtility(uint256 indexed tokenId, string utilityUri); - - /// @notice set the new utilityUri - remember the date it was set on - /// @dev The empty string indicates there is no utility - /// Throws if `tokenId` is not valid NFT - /// @param utilityUri The new utility description of the NFT - function setUtilityUri(uint256 tokenId, string calldata utilityUri) external; - - /// @notice Get the utilityUri of an NFT - /// @dev The empty string for `utilityUri` indicates that there is no utility associated - /// @param tokenId The NFT to get the user address for - /// @return The utility uri for this NFT - function utilityUriOf(uint256 tokenId) external view returns (string memory); - - /// @notice Get the changes made to utilityUri - /// @param tokenId The NFT to get the user address for - /// @return The history of changes to `utilityUri` for this NFT - function utilityHistoryOf(uint256 tokenId) external view returns(string[] memory); -} diff --git a/assets/eip-6785/test/ERC6785.test.js b/assets/eip-6785/test/ERC6785.test.js deleted file mode 100644 index d585bfb4e9fc3c..00000000000000 --- a/assets/eip-6785/test/ERC6785.test.js +++ /dev/null @@ -1,56 +0,0 @@ -const {deployContracts} = require("../scripts/deploy_contracts"); -const {expect} = require('chai'); - -describe('ERC6785', () => { - let erc6785; - const tokenId = 123; - const utilityUrl1 = 'https://utility.url'; - - beforeEach(async () => { - erc6785 = await deployContracts(); - }); - - it('should support ERC6785 interface', async () => { - await expect(await erc6785.supportsInterface( - await erc6785._INTERFACE_ID_ERC6785(), - )).to.be.true; - }); - - it('should allow setting first utility NFT', async () => { - await expect(erc6785.setUtilityUri( - tokenId, - utilityUrl1, - )).to.emit(erc6785, 'UpdateUtility').withArgs(tokenId, utilityUrl1); - }); - - it('should allow retrieving initial royalties amount for a NFT', - async () => { - await expect(erc6785.setUtilityUri( - tokenId, - utilityUrl1, - )).to.emit(erc6785, 'UpdateUtility').withArgs(tokenId, utilityUrl1); - - await expect(await erc6785.utilityUriOf( - tokenId, - )).to.equal(utilityUrl1); - }) - - it('should allow retrieving utility history for the NFT', async () => { - await expect(erc6785.setUtilityUri( - tokenId, - utilityUrl1, - )).to.emit(erc6785, 'UpdateUtility').withArgs(tokenId, utilityUrl1); - let utilityUrl2 = utilityUrl1 + '_2'; - await expect(erc6785.setUtilityUri( - tokenId, - utilityUrl2, - )).to.emit(erc6785, 'UpdateUtility').withArgs(tokenId, utilityUrl2); - - let history = await erc6785.utilityHistoryOf( - tokenId, - ); - await expect(history.length).to.equal(2); - await expect(history[0]).to.equal(utilityUrl1); - await expect(history[1]).to.equal(utilityUrl2); - }) -}); diff --git a/assets/eip-6786/README.md b/assets/eip-6786/README.md deleted file mode 100644 index cc3a5481a726d1..00000000000000 --- a/assets/eip-6786/README.md +++ /dev/null @@ -1,27 +0,0 @@ -
- -# ERC6786 Royalty Debt Registry - -
- -This project provides a reference implementation of the proposed `ERC-6786 Royalty Debt Registry`. - -## Install - -In order to install the required dependencies you need to execute: -```shell -npm install -``` - -## Compile - -In order to compile the solidity contracts you need to execute: -```shell -npx hardhat compile -``` - -## Tests - -```shell -npx hardhat test -``` \ No newline at end of file diff --git a/assets/eip-6786/contracts/ERC6786.sol b/assets/eip-6786/contracts/ERC6786.sol deleted file mode 100644 index 8910fe39930d8b..00000000000000 --- a/assets/eip-6786/contracts/ERC6786.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "./IERC6786.sol"; -import "./utils/IERC2981.sol"; - -contract ERC6786 is IERC6786, ERC165 { - - //Mapping from token (address and id) to the amount of paid royalties - mapping(address => mapping(uint256 => uint256)) private _paidRoyalties; - - /* - * bytes4(keccak256('payRoyalties(address,uint256)')) == 0xf511f0e9 - * bytes4(keccak256('getPaidRoyalties(address,uint256)')) == 0xd02ad759 - * - * => 0xf511f0e9 ^ 0xd02ad759 == 0x253b27b0 - */ - bytes4 private constant _INTERFACE_ID_ERC6786 = 0x253b27b0; - - /* - * bytes4(keccak256('royaltyInfo(uint256,uint256)')) == 0x2a55205a - */ - bytes4 private constant _INTERFACE_ID_ERC2981 = 0x2a55205a; - - /// @notice This is thrown when there is no creator related information - /// @param tokenAddress -> the address of the contract - /// @param tokenId -> the id of the NFT - error CreatorError(address tokenAddress, uint256 tokenId); - - /// @notice This is thrown when the payment fails - /// @param creator -> the address of the creator - /// @param amount -> the amount to pay - error PaymentError(address creator, uint256 amount); - - function checkRoyalties(address _contract) internal view returns (bool) { - (bool success) = IERC165(_contract).supportsInterface(_INTERFACE_ID_ERC2981); - return success; - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return - interfaceId == type(IERC6786).interfaceId || - super.supportsInterface(interfaceId); - } - - function payRoyalties(address tokenAddress, uint256 tokenId) external override payable { - if (!checkRoyalties(tokenAddress)) { - revert CreatorError(tokenAddress, tokenId); - } - (address creator,) = IERC2981(tokenAddress).royaltyInfo(tokenId, 0); - (bool success,) = payable(creator).call{value : msg.value}(""); - if(!success) { - revert PaymentError(creator, msg.value); - } - _paidRoyalties[tokenAddress][tokenId] += msg.value; - - emit RoyaltiesPaid(tokenAddress, tokenId, msg.value); - } - - function getPaidRoyalties(address tokenAddress, uint256 tokenId) external view override returns (uint256) { - return _paidRoyalties[tokenAddress][tokenId]; - } -} diff --git a/assets/eip-6786/contracts/IERC6786.sol b/assets/eip-6786/contracts/IERC6786.sol deleted file mode 100644 index 9ed6d35b9e9f75..00000000000000 --- a/assets/eip-6786/contracts/IERC6786.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -interface IERC6786 { - - /// @dev Logged when royalties were paid for a NFT - /// @notice Emitted when royalties are paid for the NFT with address tokenAddress and id tokenId - event RoyaltiesPaid(address indexed tokenAddress, uint256 indexed tokenId, uint256 amount); - - /// @notice sends msg.value to the creator for a NFT - /// @dev Throws if there is no on-chain information about the creator - /// @param tokenAddress The address of NFT contract - /// @param tokenId The NFT id - function payRoyalties(address tokenAddress, uint256 tokenId) external payable; - - /// @notice Get the amount of royalties which was paid for - /// @param tokenAddress The address of NFT contract - /// @param tokenId The NFT id - /// @return The amount of royalties paid for the NFT in paymentToken - function getPaidRoyalties(address tokenAddress, uint256 tokenId) external view returns (uint256); -} diff --git a/assets/eip-6786/contracts/token/ERC721Royalty.sol b/assets/eip-6786/contracts/token/ERC721Royalty.sol deleted file mode 100644 index 94a7d652ab0f19..00000000000000 --- a/assets/eip-6786/contracts/token/ERC721Royalty.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "../utils/IERC2981.sol"; - -contract ERC721Royalty is ERC721, IERC2981 { - - address public constant DEFAULT_CREATOR_ADDRESS = 0x4fF5DDB196A32e3dC604abD5422805ecAD22c468; - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165) returns (bool) { - return - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC2981).interfaceId || - super.supportsInterface(interfaceId); - } - - function royaltyInfo( - uint256 _tokenId, - uint256 _salePrice - ) external view override returns ( - address receiver, - uint256 royaltyAmount - ) { - receiver = DEFAULT_CREATOR_ADDRESS; - royaltyAmount = _salePrice / 10000; - } -} diff --git a/assets/eip-6786/contracts/utils/IERC2981.sol b/assets/eip-6786/contracts/utils/IERC2981.sol deleted file mode 100644 index 406d2a3ea1673e..00000000000000 --- a/assets/eip-6786/contracts/utils/IERC2981.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -interface IERC2981 is IERC165 { - function royaltyInfo( - uint256 _tokenId, - uint256 _salePrice - ) external view returns ( - address receiver, - uint256 royaltyAmount - ); -} diff --git a/assets/eip-6786/test/ERC6786.test.js b/assets/eip-6786/test/ERC6786.test.js deleted file mode 100644 index 07a03e33bdb865..00000000000000 --- a/assets/eip-6786/test/ERC6786.test.js +++ /dev/null @@ -1,65 +0,0 @@ -const {deployContracts} = require("../scripts/deploy_contracts"); -const {expect} = require('chai'); - -describe("ERC6786", () => { - - let erc721Royalty; - let erc721; - let royaltyDebtRegistry; - const tokenId = 666; - - beforeEach(async () => { - const contracts = await deployContracts(); - erc721Royalty = contracts.erc721Royalty; - erc721 = contracts.erc721; - royaltyDebtRegistry = contracts.royaltyDebtRegistry; - }) - - it('should support ERC6786 interface', async () => { - await expect(await royaltyDebtRegistry.supportsInterface("0x253b27b0")).to.be.true; - }) - - it('should allow paying royalties for a ERC2981 NFT', async () => { - await expect(royaltyDebtRegistry.payRoyalties( - erc721Royalty.address, - tokenId, - {value: 1000} - )).to.emit(royaltyDebtRegistry, 'RoyaltiesPaid') - .withArgs(erc721Royalty.address, tokenId, 1000); - }) - - it('should not allow paying royalties for a non-ERC2981 NFT', async () => { - await expect(royaltyDebtRegistry.payRoyalties( - erc721.address, - tokenId, - {value: 1000} - )).to.be.revertedWithCustomError(royaltyDebtRegistry,'CreatorError') - .withArgs(erc721.address, tokenId); - }) - - it('should allow retrieving initial royalties amount for a NFT', async () => { - await expect(await royaltyDebtRegistry.getPaidRoyalties( - erc721Royalty.address, - tokenId - )).to.equal(0); - }) - - it('should allow retrieving royalties amount after payments for a NFT', async () => { - await royaltyDebtRegistry.payRoyalties( - erc721Royalty.address, - tokenId, - {value: 2000} - ); - - await royaltyDebtRegistry.payRoyalties( - erc721Royalty.address, - tokenId, - {value: 3666} - ) - - await expect(await royaltyDebtRegistry.getPaidRoyalties( - erc721Royalty.address, - tokenId - )).to.equal(5666); - }) -}); diff --git a/assets/eip-6787/image1.png b/assets/eip-6787/image1.png deleted file mode 100644 index e940ea3d2054ad..00000000000000 Binary files a/assets/eip-6787/image1.png and /dev/null differ diff --git a/assets/eip-6808/.gitignore b/assets/eip-6808/.gitignore deleted file mode 100644 index 2351d1ed111591..00000000000000 --- a/assets/eip-6808/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -build/ -.env -.vscode -package-lock.json \ No newline at end of file diff --git a/assets/eip-6808/Contract Interactions diagram.svg b/assets/eip-6808/Contract Interactions diagram.svg deleted file mode 100644 index 49181d7bd0a86b..00000000000000 --- a/assets/eip-6808/Contract Interactions diagram.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/eip-6808/README.md b/assets/eip-6808/README.md deleted file mode 100644 index 292095ae487da6..00000000000000 --- a/assets/eip-6808/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# EIP-6808 implementation - -This project is a reference implementation of EIP-6808. - -Try running some of the following tasks: - -```shell -npm i -truffle compile -truffle test -``` diff --git a/assets/eip-6808/contracts/IKBT20.sol b/assets/eip-6808/contracts/IKBT20.sol deleted file mode 100644 index 7a02608c41d40a..00000000000000 --- a/assets/eip-6808/contracts/IKBT20.sol +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -interface IKBT20 { - event AccountSecured(address _account, uint256 _amount); - event AccountResetBinding(address _account); - event SafeFallbackActivated(address _account); - event AccountEnabledTransfer( - address _account, - uint256 _amount, - uint256 _time, - address _to, - bool _allFunds - ); - event AccountEnabledApproval( - address _account, - uint256 _time, - uint256 _numberOfTransfers - ); - event Ingress(address _account, uint256 _amount); - event Egress(address _account, uint256 _amount); - - struct AccountHolderBindings { - address firstWallet; - address secondWallet; - } - - struct FirstAccountBindings { - address accountHolderWallet; - address secondWallet; - } - - struct SecondAccountBindings { - address accountHolderWallet; - address firstWallet; - } - - struct TransferConditions { - uint256 amount; - uint256 time; - address to; - bool allFunds; - } - - struct ApprovalConditions { - uint256 time; - uint256 numberOfTransfers; - } - - function addBindings( - address _keyWallet1, - address _keyWallet2 - ) external returns (bool); - - function getBindings( - address _account - ) external view returns (AccountHolderBindings memory); - - function resetBindings() external returns (bool); - - function safeFallback() external returns (bool); - - function allowTransfer( - uint256 _amount, - uint256 _time, - address _to, - bool _allFunds - ) external returns (bool); - - function getTransferableFunds( - address _account - ) external view returns (TransferConditions memory); - - function allowApproval( - uint256 _time, - uint256 _numberOfTransfers - ) external returns (bool); - - function getApprovalConditions( - address account - ) external view returns (ApprovalConditions memory); - - function getNumberOfTransfersAllowed( - address _account, - address _spender - ) external view returns (uint256); - - function isSecureWallet(address _account) external view returns (bool); -} diff --git a/assets/eip-6808/contracts/KBT20.sol b/assets/eip-6808/contracts/KBT20.sol deleted file mode 100644 index 750d877a0eebf6..00000000000000 --- a/assets/eip-6808/contracts/KBT20.sol +++ /dev/null @@ -1,391 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "./IKBT20.sol"; - -import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "../node_modules/@openzeppelin/contracts/access/Ownable.sol"; - -contract KBT20 is IKBT20, ERC20, Ownable { - mapping(address => AccountHolderBindings) private _holderAccounts; - mapping(address => FirstAccountBindings) private _firstAccounts; - mapping(address => SecondAccountBindings) private _secondAccounts; - - mapping(address => TransferConditions) private _transferConditions; - mapping(address => ApprovalConditions) private _approvalConditions; - - mapping(address => mapping(address => uint256)) - private _numberOfTransfersAllowed; - - constructor( - string memory _name, - string memory _symbol - ) ERC20(_name, _symbol) {} - - function addBindings( - address _keyWallet1, - address _keyWallet2 - ) external returns (bool) { - address sender = _msgSender(); - require(balanceOf(sender) > 0, "[200] KBT20: Wallet is not a holder"); - require( - _holderAccounts[sender].firstWallet == address(0) && - _holderAccounts[sender].secondWallet == address(0), - "[201] KBT20: Key wallets are already filled" - ); - require( - _keyWallet1 != address(0) && _keyWallet2 != address(0), - "[202] KBT20: Does not follow 0x standard" - ); - require( - _keyWallet1 != _keyWallet2, - "[205] KBT20: Key wallet 1 must be different than key wallet 2" - ); - require( - _keyWallet1 != sender, - "[206] KBT20: Key wallet 1 must be different than the sender" - ); - require( - sender != _keyWallet2, - "[207] KBT20: Key wallet 2 must be different than the sender" - ); - require( - _firstAccounts[_keyWallet1].accountHolderWallet == address(0), - "[203] KBT20: Key wallet 1 is already registered" - ); - require( - _secondAccounts[_keyWallet2].accountHolderWallet == address(0), - "[204] KBT20: Key wallet 2 is already registered" - ); - - _holderAccounts[sender] = AccountHolderBindings({ - firstWallet: _keyWallet1, - secondWallet: _keyWallet2 - }); - - _firstAccounts[_keyWallet1] = FirstAccountBindings({ - accountHolderWallet: sender, - secondWallet: _keyWallet2 - }); - - _secondAccounts[_keyWallet2] = SecondAccountBindings({ - accountHolderWallet: sender, - firstWallet: _keyWallet1 - }); - - emit AccountSecured(sender, balanceOf(sender)); - - return true; - } - - function getBindings( - address _account - ) external view returns (AccountHolderBindings memory) { - return _holderAccounts[_account]; - } - - function resetBindings() external returns (bool) { - address accountHolder = _getAccountHolder(); - require( - accountHolder != address(0), - "[300] KBT20: Key authorization failure" - ); - - delete _firstAccounts[_holderAccounts[accountHolder].firstWallet]; - delete _secondAccounts[_holderAccounts[accountHolder].secondWallet]; - delete _holderAccounts[accountHolder]; - - emit AccountResetBinding(accountHolder); - - return true; - } - - function safeFallback() external returns (bool) { - address accountHolder = _getAccountHolder(); - address otherSecureWallet = _getOtherSecureWallet(); - require( - accountHolder != address(0), - "[400] KBT20: Key authorization failure" - ); - - _transfer(accountHolder, otherSecureWallet, balanceOf(accountHolder)); - - emit SafeFallbackActivated(accountHolder); - - return true; - } - - function allowTransfer( - uint256 _amount, - uint256 _time, - address _to, - bool _allFunds - ) external virtual returns (bool) { - address accountHolder = _getAccountHolder(); - - require( - accountHolder != address(0), - "[500] KBT20: Key authorization failure" - ); - require( - balanceOf(accountHolder) >= _amount, - "[501] KBT20: Not enough tokens" - ); - - _time = _time > 0 ? (block.timestamp + _time) : 0; - - _transferConditions[accountHolder] = TransferConditions({ - amount: _amount, - time: _time, - to: _to, - allFunds: _allFunds - }); - - emit AccountEnabledTransfer( - accountHolder, - _amount, - _time, - _to, - _allFunds - ); - - return true; - } - - function getTransferableFunds( - address _account - ) external view returns (TransferConditions memory) { - return _transferConditions[_account]; - } - - function allowApproval( - uint256 _time, - uint256 _numberOfTransfers - ) external virtual override returns (bool) { - address accountHolder = _getAccountHolder(); - require( - accountHolder != address(0), - "[600] KBT20: Key authorization failure" - ); - - _time = block.timestamp + _time; - - _approvalConditions[accountHolder].time = _time; - _approvalConditions[accountHolder] - .numberOfTransfers = _numberOfTransfers; - - emit AccountEnabledApproval(accountHolder, _time, _numberOfTransfers); - - return true; - } - - function getApprovalConditions( - address _account - ) external view returns (ApprovalConditions memory) { - return _approvalConditions[_account]; - } - - function getNumberOfTransfersAllowed( - address _account, - address _spender - ) external view returns (uint256) { - return _numberOfTransfersAllowed[_account][_spender]; - } - - function isSecureWallet(address _account) public view returns (bool) { - return - _holderAccounts[_account].firstWallet != address(0) && - _holderAccounts[_account].secondWallet != address(0); - } - - //region ERC20 overrides - - function transfer( - address _to, - uint256 _amount - ) public virtual override returns (bool) { - address _owner = _msgSender(); - - if (isSecureWallet(_owner)) { - require( - _hasAllowedTransfer(_owner, _amount, _to), - "[100] KBT20: Sender is a secure wallet and doesn't have approval for the amount" - ); - } - - _transfer(_owner, _to, _amount); - - delete _transferConditions[_owner]; - - return true; - } - - function approve( - address _spender, - uint256 _amount - ) public virtual override returns (bool) { - address _owner = _msgSender(); - if (isSecureWallet(_owner)) { - require( - _approvalConditions[_owner].time > 0, - "[101] KBT20: Spending of funds is not authorized." - ); - require( - _isApprovalAllowed(_owner), - "[102] KBT20: Time has expired for the spending of funds" - ); - } - - _approve(_owner, _spender, _amount); - - _numberOfTransfersAllowed[_owner][_spender] = _approvalConditions[ - _owner - ].numberOfTransfers; - - delete _approvalConditions[_owner]; - - return true; - } - - function transferFrom( - address _from, - address _to, - uint256 _amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(_from, spender, _amount); - _transfer(_from, _to, _amount); - - if (_numberOfTransfersAllowed[_from][spender] != 0) { - if (_numberOfTransfersAllowed[_from][spender] == 1) { - _approve(_from, spender, 0); - } - _numberOfTransfersAllowed[_from][spender] -= 1; - } - return true; - } - - function increaseAllowance( - address _spender, - uint256 _addedValue - ) public virtual override returns (bool) { - address _owner = _msgSender(); - require( - _approvalConditions[_owner].time > 0, - "[101] KBT20: Spending of funds is not authorized." - ); - require( - _isApprovalAllowed(_owner), - "[102] KBT20: Time has expired for the spending of funds" - ); - - _approve(_owner, _spender, allowance(_owner, _spender) + _addedValue); - - delete _approvalConditions[_owner]; - - return true; - } - - function decreaseAllowance( - address _spender, - uint256 _subtractedValue - ) public virtual override returns (bool) { - address _owner = _msgSender(); - require( - _approvalConditions[_owner].time > 0, - "[101] KBT20: Spending of funds is not authorized." - ); - require( - _isApprovalAllowed(_owner), - "[102] KBT20: Time has expired for the spending of funds" - ); - - uint256 currentAllowance = allowance(_owner, _spender); - require( - currentAllowance >= _subtractedValue, - "ERC20: decreased allowance below zero" - ); - unchecked { - _approve(_owner, _spender, currentAllowance - _subtractedValue); - } - - delete _approvalConditions[_owner]; - - return true; - } - - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) internal virtual override { - // region update secureAccounts - if (isSecureWallet(from) && balanceOf(from) == 0) { - delete _firstAccounts[_holderAccounts[from].firstWallet]; - delete _secondAccounts[_holderAccounts[from].secondWallet]; - delete _holderAccounts[from]; - } - // endregion - - if (balanceOf(from) == 0) { - emit Egress(from, amount); - } - - if (balanceOf(to) == amount) { - emit Ingress(to, amount); - } - } - - // endregion - - function _hasAllowedTransfer( - address _account, - uint256 _amount, - address _to - ) internal view returns (bool) { - TransferConditions memory conditions = _transferConditions[_account]; - - if (conditions.allFunds) { - return true; - } - - if ( - (conditions.amount == 0 && - conditions.time == 0 && - conditions.to == address(0)) || - (conditions.amount > 0 && conditions.amount < _amount) || - (conditions.time > 0 && conditions.time < block.timestamp) || - (conditions.to != address(0) && conditions.to != _to) - ) { - return false; - } - - return true; - } - - function _isApprovalAllowed(address account) internal view returns (bool) { - return _approvalConditions[account].time >= block.timestamp; - } - - function _getAccountHolder() internal view returns (address) { - address sender = _msgSender(); - return - _firstAccounts[sender].accountHolderWallet != address(0) - ? _firstAccounts[sender].accountHolderWallet - : ( - _secondAccounts[sender].accountHolderWallet != address(0) - ? _secondAccounts[sender].accountHolderWallet - : address(0) - ); - } - - function _getOtherSecureWallet() internal view returns (address) { - address sender = _msgSender(); - address accountHolder = _getAccountHolder(); - - return - _holderAccounts[accountHolder].firstWallet == sender - ? _holderAccounts[accountHolder].secondWallet - : _holderAccounts[accountHolder].firstWallet; - } -} diff --git a/assets/eip-6808/contracts/MyFirstKBT.sol b/assets/eip-6808/contracts/MyFirstKBT.sol deleted file mode 100644 index 3d7c911a55db06..00000000000000 --- a/assets/eip-6808/contracts/MyFirstKBT.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "./KBT20.sol"; - -contract MyFirstKBT is KBT20 { - uint256 private _secureAccounts = 0; - uint256 private _secureAmount = 0; - - constructor() KBT20("MyFirstKBT", "FirstKBT") { - _mint(msg.sender, 100_000_000 * 10 ** 18); - } -} diff --git a/assets/eip-6808/migrations/1_deploy_contracts.js b/assets/eip-6808/migrations/1_deploy_contracts.js deleted file mode 100644 index f70c4f14e0aca3..00000000000000 --- a/assets/eip-6808/migrations/1_deploy_contracts.js +++ /dev/null @@ -1,5 +0,0 @@ -const FirstKBT = artifacts.require("MyFirstKBT"); - -module.exports = function (deployer) { - deployer.deploy(FirstKBT); -}; diff --git a/assets/eip-6808/package.json b/assets/eip-6808/package.json deleted file mode 100644 index 6c5339980ffba5..00000000000000 --- a/assets/eip-6808/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "dependencies": { - "@openzeppelin/test-helpers": "^0.5.16", - "@truffle/hdwallet-provider": "^2.1.7", - "dotenv": "^16.0.3" - }, - "devDependencies": { - "@openzeppelin/contracts": "^4.8.2" - } -} diff --git a/assets/eip-6808/test/kbt20.js b/assets/eip-6808/test/kbt20.js deleted file mode 100644 index 02d1c341ec2910..00000000000000 --- a/assets/eip-6808/test/kbt20.js +++ /dev/null @@ -1,229 +0,0 @@ -const { - BN, // Big Number support - constants, // Common constants, like the zero address and largest integers - expectEvent, // Assertions for emitted events - expectRevert, // Assertions for transactions that should fail - time, -} = require('@openzeppelin/test-helpers'); -const { web3 } = require('@openzeppelin/test-helpers/src/setup'); - -const FirstKBT = artifacts.require("MyFirstKBT"); - -contract('FirstKBT', function (accounts) { - - let instance; - - before(async () => { - instance = await FirstKBT.new(); - }); - - const [deploymentAccount, accountHolder, secondAccountHolder, firstAccount, secondAccount, - thirdAccount, fourthAccount, spenderAccount] = accounts; - - it("1. deploy: should check the totalSupply to be 100_000_000", async function () { - balance = await instance.totalSupply(); - assert.equal(balance.valueOf(), 100_000_000 * 10 ** 18, "100_000_000 * 10 ** 18 wasn't in the first account"); - }); - - it("2. deploy: owner should have full amount", async function () { - balance = await instance.balanceOf(deploymentAccount); - assert.equal(balance.valueOf(), 100_000_000 * 10 ** 18, "100_000_000 * 10 ** 18 wasn't in the first account"); - }); - - it("3. deploy: accountHolder should have 0 amount", async function () { - balance = await instance.balanceOf(accountHolder); - assert.equal(balance.valueOf(), 0, "accountHolder has more than 0"); - }); - - it("4. addBindings : CANNOT add secure wallets if ballance is 0", async function () { - await expectRevert( - instance.addBindings(firstAccount, secondAccount, { from: accountHolder }), - "[200] KBT20: Wallet is not a holder" - ); - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, constants.ZERO_ADDRESS, "First account is not empty"); - assert.equal(accountHolderBindings.secondWallet, constants.ZERO_ADDRESS, "Second account is not empty"); - }); - - it("5. transfer: CAN 10 tokens be transferred to accountHolder", async function () { - value = 10; - emitted = await instance.transfer(accountHolder, value, { from: deploymentAccount }); - printGasUsed(emitted, 'transfer'); - balance = await instance.balanceOf(accountHolder); - assert.equal(balance.toNumber(), value, "transfer failed"); - }); - - it("6. addBindings : CAN add secure wallets | EVENT: AccountSecured was emitted", async function () { - emitted = await instance.addBindings(firstAccount, secondAccount, { from: accountHolder }); - printGasUsed(emitted, 'addBindings'); - balance = await instance.balanceOf(accountHolder); - expectEvent(emitted, "AccountSecured", { _account: accountHolder, _amount: balance }); - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, firstAccount, "First account was not set as a secure wallet"); - assert.equal(accountHolderBindings.secondWallet, secondAccount, "Second account was not set as a secure wallet"); - }); - - it("7. addBindings : CANNOT add secure wallets a second time", async function () { - expectRevert( - instance.addBindings(thirdAccount, fourthAccount, { from: accountHolder }), - "[201] KBT20: Key wallets are already filled" - ); - }); - - it("8. addBindings : CANNOT add secure wallets that are already secure wallets to another account", async function () { - value = 10; - await instance.transfer(secondAccountHolder, value, { from: deploymentAccount }); - expectRevert( - instance.addBindings(firstAccount, fourthAccount, { from: secondAccountHolder }), - "[203] KBT20: Key wallet 1 is already registered" - ); - expectRevert( - instance.addBindings(thirdAccount, secondAccount, { from: secondAccountHolder }), - "[204] KBT20: Key wallet 2 is already registered" - ); - }); - - it("9. addBindings : accountHolder is a secure wallet", async function () { - result = await instance.isSecureWallet(accountHolder); - assert.equal(result, true, "accountHolder is not a secure wallet"); - }); - - it("10. addBindings : firstAccount is NOT a secure wallet", async function () { - result = await instance.isSecureWallet(firstAccount); - assert.equal(result, false, "firstAccount is a secure wallet"); - }); - - it("11. transfer: accountHolder CANNOT transfer 3 tokens before allowTransfer", async function () { - value = 3; - await expectRevert(instance.transfer(secondAccountHolder, value, { from: accountHolder }), "[100] KBT20: Sender is a secure wallet and doesn't have approval for the amount"); - }); - - it("12. allowTransfer: firstAccount CAN Unlock 10 tokens | EVENT: AccountEnabledTransfer", async function () { - value = 10; - emitted = await instance.allowTransfer(value, 0, constants.ZERO_ADDRESS, false, { from: firstAccount }); - printGasUsed(emitted, 'allowTransfer'); - expectEvent(emitted, "AccountEnabledTransfer", { _account: accountHolder, _amount: new BN(value), _time: new BN(0), _to: constants.ZERO_ADDRESS, _allFunds: false }); - await instance.getTransferableFunds(accountHolder).then(function (result) { - assert.equal(result.amount, value, "accountHolder does not have " + value + " tokens unlocked"); - }); - expectRevert(instance.allowTransfer(11, 0, constants.ZERO_ADDRESS, false, { from: firstAccount }), "[501] KBT20: Not enough tokens"); - }); - - it("13. transfer: accountHolder CAN transfer 3 tokens to secondAccountHolder", async function () { - value = 3; - initialBalance = (await instance.balanceOf(secondAccountHolder)).toNumber(); - await instance.transfer(secondAccountHolder, value, { from: accountHolder }); - balance = await instance.balanceOf(secondAccountHolder); - assert.equal(balance.toNumber(), initialBalance + value, "transfer failed"); - }); - - it("14. approve: account holder CAN'T approve (without AllowApproval)", async function () { - token = instance; - - expectRevert(instance.approve(spenderAccount, 10, { from: accountHolder }), "[101] KBT20: Spending of funds is not authorized."); - }); - - it("15. allowApproval: firstAccount CAN allowApproval for a time and numberOfTransfers | EVENT: AccountEnabledApproval ", async function () { - tempTime = 100; - numberOfTransfers = 2; - - emitted = await instance.allowApproval(tempTime, numberOfTransfers, { from: firstAccount }); - printGasUsed(emitted, 'allowApproval'); - forTime = new BN(await time.latest()); - forTime = forTime.add(new BN(tempTime)); - forNumberOfTokens = new BN(numberOfTransfers); - expectEvent(emitted, "AccountEnabledApproval", { _account: accountHolder, _time: forTime, _numberOfTransfers: forNumberOfTokens }); - await instance.getApprovalConditions(accountHolder).then(function (result) { - assert.isAbove(Number(result.time), 0, "accountHolder does not have allowApproval"); - assert.equal(Number(result.numberOfTransfers), numberOfTransfers, "accountHolder does not have allowApproval"); - }); - }); - - it("16. transferFrom: spenderAccount CANNOT transferFrom accountHolder without Approval", async function () { - value = 3; - expectRevert(instance.transferFrom(accountHolder, secondAccountHolder, value, { from: spenderAccount }), "ERC20: insufficient allowance"); - }); - - it("17. approve: account holder CAN approve spenderAccount", async function () { - value = 3; - emitted = await instance.approve(spenderAccount, value, { from: accountHolder }); - printGasUsed(emitted, 'approve'); - allowance = await instance.allowance(accountHolder, spenderAccount); - assert.equal(value, allowance.toNumber(), "Approval didn't went as planned."); - - tempNumberOfTransfersAllowed = 2; - noOfTransfersAllowed = await instance.getNumberOfTransfersAllowed(accountHolder, spenderAccount); - assert.equal(tempNumberOfTransfersAllowed, noOfTransfersAllowed, "Approval didn't went as planned."); - }); - - it("18. transferFrom: spenderAccount CAN transferFrom accountHolder but no more than the numberOfTransfers", async function () { - value = 1; - emitted = await instance.transferFrom(accountHolder, secondAccountHolder, value, { from: spenderAccount }); - emitted = await instance.transferFrom(accountHolder, secondAccountHolder, value, { from: spenderAccount }); - printGasUsed(emitted, 'transferFrom'); - - expectRevert(instance.transferFrom(accountHolder, secondAccountHolder, value, { from: spenderAccount }), "ERC20: insufficient allowance"); - }); - - it("19. transferFrom: spenderAccount CAN transferFrom accountHolder as much as they want when numberOfTransfers is 0", async function () { - value = 10; - await instance.transfer(accountHolder, value, { from: deploymentAccount }); - await instance.allowApproval(100, 0, { from: firstAccount }); - await instance.approve(spenderAccount, value, { from: accountHolder }); - - while (value > 0) { - await instance.transferFrom(accountHolder, secondAccountHolder, 1, { from: spenderAccount }); - - value--; - } - }); - - it("20. transfer: when accountHolder transfers ALL funds, accountHolder becomes unsecure", async function () { - value = 5; - emitted = await instance.allowTransfer(value, 0, constants.ZERO_ADDRESS, false, { from: firstAccount }); - printGasUsed(emitted, 'allowTransfer'); - await instance.transfer(secondAccountHolder, value, { from: accountHolder }); - - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - assert.equal(accountHolderBindings.secondWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - }); - - it("21. safeFallback: accountHolder becomes unsecure, other 2FA wallet has at least accountHolder funds | EVENT: SafeFallbackActivated", async function () { - value = 100; - await instance.transfer(accountHolder, value, { from: deploymentAccount }); - expectRevert(instance.safeFallback({ from: firstAccount }), "[400] KBT20: Key authorization failure"); - await instance.addBindings(firstAccount, secondAccount, { from: accountHolder }); - - emitted = await instance.safeFallback({ from: firstAccount }); - printGasUsed(emitted, 'safeFallback'); - expectEvent(emitted, "SafeFallbackActivated", { _account: accountHolder }); - - secondAccountBalance = (await instance.balanceOf(secondAccount)).toNumber(); - assert.isAtLeast(secondAccountBalance, value, "second account doesn't have the full amount from account holder"); - - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - assert.equal(accountHolderBindings.secondWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - }); - - it("22. resetBindings: accountHolder becomes unsecure | EVENT: AccountResetBinding", async function () { - value = 100; - - await instance.transfer(accountHolder, value, { from: deploymentAccount }); - await instance.addBindings(firstAccount, secondAccount, { from: accountHolder }); - - emitted = await instance.resetBindings({ from: firstAccount }); - printGasUsed(emitted, 'resetBindings'); - expectEvent(emitted, "AccountResetBinding", { _account: accountHolder }); - - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - assert.equal(accountHolderBindings.secondWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - }); -}); - -function printGasUsed(event, methodName) { - const gasUsed = event.receipt.gasUsed; - console.log(`GasUsed: ${gasUsed.toLocaleString()} for '${methodName}'`); -} diff --git a/assets/eip-6808/truffle-config.js b/assets/eip-6808/truffle-config.js deleted file mode 100644 index 4f9fd7c50012a2..00000000000000 --- a/assets/eip-6808/truffle-config.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Use this file to configure your truffle project. It's seeded with some - * common settings for different networks and features like migrations, - * compilation, and testing. Uncomment the ones you need or modify - * them to suit your project as necessary. - * - * More information about configuration can be found at: - * - * https://trufflesuite.com/docs/truffle/reference/configuration - * - * Hands-off deployment with Infura - * -------------------------------- - * - * Do you have a complex application that requires lots of transactions to deploy? - * Use this approach to make deployment a breeze 🏖️: - * - * Infura deployment needs a wallet provider (like @truffle/hdwallet-provider) - * to sign transactions before they're sent to a remote public node. - * Infura accounts are available for free at 🔍: https://infura.io/register - * - * You'll need a mnemonic - the twelve word phrase the wallet uses to generate - * public/private key pairs. You can store your secrets 🤐 in a .env file. - * In your project root, run `$ npm install dotenv`. - * Create .env (which should be .gitignored) and declare your MNEMONIC - * and Infura PROJECT_ID variables inside. - * For example, your .env file will have the following structure: - * - * MNEMONIC = - * PROJECT_ID = - * - * Deployment with Truffle Dashboard (Recommended for best security practice) - * -------------------------------------------------------------------------- - * - * Are you concerned about security and minimizing rekt status 🤔? - * Use this method for best security: - * - * Truffle Dashboard lets you review transactions in detail, and leverages - * MetaMask for signing, so there's no need to copy-paste your mnemonic. - * More details can be found at 🔎: - * - * https://trufflesuite.com/docs/truffle/getting-started/using-the-truffle-dashboard/ - */ - -require('dotenv').config(); -// const { MNEMONIC, PROJECT_ID } = process.env; - -const HDWalletProvider = require('@truffle/hdwallet-provider'); - -//to fetch these keys from .env file -const privateKey = process.env.PRIVATE_KEY; -const infura_api_key = process.env.INFURA_API_KEY; -const etherscan_api_key = process.env.ETHERSCAN_API_KEY; - -module.exports = { - /** - * Networks define how you connect to your ethereum client and let you set the - * defaults web3 uses to send transactions. If you don't specify one truffle - * will spin up a managed Ganache instance for you on port 9545 when you - * run `develop` or `test`. You can ask a truffle command to use a specific - * network from the command line, e.g - * - * $ truffle test --network - */ - plugins: [ - 'truffle-plugin-verify' - ], - api_keys: { - etherscan: etherscan_api_key - }, - - networks: { - // Useful for testing. The `development` name is special - truffle uses it by default - // if it's defined here and no other network is specified at the command line. - // You should run a client (like ganache, geth, or parity) in a separate terminal - // tab if you use this network and you must also set the `host`, `port` and `network_id` - // options below to some value. - // - development: { - host: "127.0.0.1", // Localhost (default: none) - port: 7545, // Standard Ethereum port (default: none) - network_id: "*", // Any network (default: none) - }, - // - // An additional network, but with some advanced options… - // advanced: { - // port: 8777, // Custom port - // network_id: 1342, // Custom network - // gas: 8500000, // Gas sent with each transaction (default: ~6700000) - // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) - // from:
, // Account to send transactions from (default: accounts[0]) - // websocket: true // Enable EventEmitter interface for web3 (default: false) - // }, - // - // Useful for deploying to a public network. - // Note: It's important to wrap the provider as a function to ensure truffle uses a new provider every time. - goerli: { - provider: () => new HDWalletProvider(privateKey, `https://goerli.infura.io/v3/${infura_api_key}`), - network_id: 5, // Goerli's id - gas: 5000000, //gas limit - confirmations: 1, // # of confirmations to wait between deployments. (default: 0) - timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) - skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) - }, - // - // Useful for private networks - // private: { - // provider: () => new HDWalletProvider(MNEMONIC, `https://network.io`), - // network_id: 2111, // This network is yours, in the cloud. - // production: true // Treats this network as if it was a public net. (default: false) - // } - }, - - // Set default mocha options here, use special reporters, etc. - mocha: { - // timeout: 100000 - }, - - // Configure your compilers - compilers: { - solc: { - version: "0.8.17", // Fetch exact version from solc-bin (default: truffle's version) - // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) - settings: { // See the solidity docs for advice about optimization and evmVersion - optimizer: { - enabled: true, - runs: 200 - }, - // evmVersion: "byzantium" - } - } - } - - // Truffle DB is currently disabled by default; to enable it, change enabled: - // false to enabled: true. The default storage location can also be - // overridden by specifying the adapter settings, as shown in the commented code below. - // - // NOTE: It is not possible to migrate your contracts to truffle DB and you should - // make a backup of your artifacts to a safe location before enabling this feature. - // - // After you backed up your artifacts you can utilize db by running migrate as follows: - // $ truffle migrate --reset --compile-all - // - // db: { - // enabled: false, - // host: "127.0.0.1", - // adapter: { - // name: "indexeddb", - // settings: { - // directory: ".db" - // } - // } - // } -}; diff --git a/assets/eip-6809/.gitignore b/assets/eip-6809/.gitignore deleted file mode 100644 index 9bded852be91b4..00000000000000 --- a/assets/eip-6809/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -build/ -.env -.vscode -package-lock.json diff --git a/assets/eip-6809/Contract Interactions diagram.svg b/assets/eip-6809/Contract Interactions diagram.svg deleted file mode 100644 index 42ab31b03a3ba7..00000000000000 --- a/assets/eip-6809/Contract Interactions diagram.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/eip-6809/README.md b/assets/eip-6809/README.md deleted file mode 100644 index 79d4632748f2e7..00000000000000 --- a/assets/eip-6809/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# EIP 6809 implementation - -This project is a reference implementation of EIP-6809. - -Try running some of the following tasks: - -```shell -npm i -truffle compile -truffle test -``` diff --git a/assets/eip-6809/contracts/IKBT721.sol b/assets/eip-6809/contracts/IKBT721.sol deleted file mode 100644 index 10828e79dffb18..00000000000000 --- a/assets/eip-6809/contracts/IKBT721.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -interface IKBT721 { - event AccountSecured(address indexed _account, uint256 _noOfTokens); - event AccountResetBinding(address indexed _account); - event SafeFallbackActivated(address indexed _account); - event AccountEnabledTransfer( - address _account, - uint256 _tokenId, - uint256 _time, - address _to, - bool _anyToken - ); - event AccountEnabledApproval( - address _account, - uint256 _time, - uint256 _numberOfTransfers - ); - event Ingress(address _account, uint256 _tokenId); - event Egress(address _account, uint256 _tokenId); - - struct AccountHolderBindings { - address firstWallet; - address secondWallet; - } - - struct FirstAccountBindings { - address accountHolderWallet; - address secondWallet; - } - - struct SecondAccountBindings { - address accountHolderWallet; - address firstWallet; - } - - struct TransferConditions { - uint256 tokenId; - uint256 time; - address to; - bool anyToken; - } - - struct ApprovalConditions { - uint256 time; - uint256 numberOfTransfers; - } - - function addBindings( - address _keyWallet1, - address _keyWallet2 - ) external returns (bool); - - function getBindings( - address _account - ) external view returns (AccountHolderBindings memory); - - function resetBindings() external returns (bool); - - function safeFallback() external returns (bool); - - function allowTransfer( - uint256 _tokenId, - uint256 _time, - address _to, - bool _allTokens - ) external returns (bool); - - function getTransferableFunds( - address _account - ) external view returns (TransferConditions memory); - - function allowApproval( - uint256 _time, - uint256 _numberOfTransfers - ) external returns (bool); - - function getApprovalConditions( - address account - ) external view returns (ApprovalConditions memory); - - function getNumberOfTransfersAllowed( - address _account, - address _spender - ) external view returns (uint256); - - function isSecureWallet(address _account) external returns (bool); - - function isSecureToken(uint256 _tokenId) external returns (bool); -} diff --git a/assets/eip-6809/contracts/KBT721.sol b/assets/eip-6809/contracts/KBT721.sol deleted file mode 100644 index 760948a12bbd95..00000000000000 --- a/assets/eip-6809/contracts/KBT721.sol +++ /dev/null @@ -1,400 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "./IKBT721.sol"; -import "../node_modules/@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "../node_modules/@openzeppelin/contracts/access/Ownable.sol"; - -contract KBT721 is IKBT721, ERC721Enumerable, Ownable { - mapping(address => AccountHolderBindings) private _holderAccounts; - mapping(address => FirstAccountBindings) private _firstAccounts; - mapping(address => SecondAccountBindings) private _secondAccounts; - - mapping(address => TransferConditions) private _transferConditions; - mapping(address => ApprovalConditions) private _approvalConditions; - - mapping(address => mapping(address => uint256)) - private _numberOfTransfersAllowed; - - constructor( - string memory _name, - string memory _symbol - ) ERC721(_name, _symbol) {} - - function addBindings( - address _keyWallet1, - address _keyWallet2 - ) external virtual override returns (bool) { - address sender = _msgSender(); - require(balanceOf(sender) > 0, "[200] KBT721: Wallet is not a holder"); - require( - _holderAccounts[sender].firstWallet == address(0) && - _holderAccounts[sender].secondWallet == address(0), - "[201] KBT721: Key wallets are already filled" - ); - require( - _keyWallet1 != address(0) && _keyWallet2 != address(0), - "[202] KBT721: Does not follow 0x standard" - ); - require( - _keyWallet1 != _keyWallet2, - "[205] KBT721: Key wallet 1 must be different than key wallet 2" - ); - require( - _keyWallet1 != sender, - "[206] KBT721: Key wallet 1 must be different than the sender" - ); - require( - sender != _keyWallet2, - "[207] KBT721: Key wallet 2 must be different than the sender" - ); - require( - _firstAccounts[_keyWallet1].accountHolderWallet == address(0), - "[203] KBT721: Key wallet 1 is already registered" - ); - require( - _secondAccounts[_keyWallet2].accountHolderWallet == address(0), - "[204] KBT721: Key wallet 2 is already registered" - ); - _holderAccounts[sender] = AccountHolderBindings({ - firstWallet: _keyWallet1, - secondWallet: _keyWallet2 - }); - _firstAccounts[_keyWallet1] = FirstAccountBindings({ - accountHolderWallet: sender, - secondWallet: _keyWallet2 - }); - _secondAccounts[_keyWallet2] = SecondAccountBindings({ - accountHolderWallet: sender, - firstWallet: _keyWallet1 - }); - emit AccountSecured(sender, balanceOf(sender)); - return true; - } - - function getBindings( - address _account - ) external view virtual override returns (AccountHolderBindings memory) { - return _holderAccounts[_account]; - } - - function resetBindings() external virtual override returns (bool) { - address accountHolder = _getAccountHolder(); - require( - accountHolder != address(0), - "[300] KBT721: Key authorization failure" - ); - delete _firstAccounts[_holderAccounts[accountHolder].firstWallet]; - delete _secondAccounts[_holderAccounts[accountHolder].secondWallet]; - delete _holderAccounts[accountHolder]; - emit AccountResetBinding(accountHolder); - return true; - } - - function safeFallback() external virtual override returns (bool) { - address accountHolder = _getAccountHolder(); - address otherSecureWallet = _getOtherSecureWallet(); - require( - accountHolder != address(0), - "[400] KBT721: Key authorization failure" - ); - - uint256 noOfTokens = balanceOf(accountHolder); - uint256 i = 0; - while (i++ < noOfTokens) { - uint256 tempTokenId = tokenOfOwnerByIndex(accountHolder, 0); - _transfer(accountHolder, otherSecureWallet, tempTokenId); - } - - emit SafeFallbackActivated(accountHolder); - - return true; - } - - function allowTransfer( - uint256 _tokenId, - uint256 _time, - address _to, - bool _anyToken - ) external virtual returns (bool) { - address accountHolder = _getAccountHolder(); - - require( - accountHolder != address(0), - "[500] KBT721: Key authorization failure" - ); - if (_tokenId > 0) { - address _owner = ownerOf(_tokenId); - require(_owner == accountHolder, "[501] KBT721: Invalid tokenId."); - } - - _time = _time > 0 ? (block.timestamp + _time) : 0; - - _transferConditions[accountHolder] = TransferConditions({ - tokenId: _tokenId, - time: _time, - to: _to, - anyToken: _anyToken - }); - - emit AccountEnabledTransfer( - accountHolder, - _tokenId, - _time, - _to, - _anyToken - ); - - return true; - } - - function getTransferableFunds( - address _account - ) external view returns (TransferConditions memory) { - return _transferConditions[_account]; - } - - function allowApproval( - uint256 _time, - uint256 _numberOfTransfers - ) external virtual returns (bool) { - address accountHolder = _getAccountHolder(); - require( - accountHolder != address(0), - "[600] KBT721: Key authorization failure" - ); - - _time = block.timestamp + _time; - - _approvalConditions[accountHolder].time = _time; - _approvalConditions[accountHolder] - .numberOfTransfers = _numberOfTransfers; - - emit AccountEnabledApproval(accountHolder, _time, _numberOfTransfers); - - return true; - } - - function getApprovalConditions( - address _account - ) external view returns (ApprovalConditions memory) { - return _approvalConditions[_account]; - } - - function getNumberOfTransfersAllowed( - address _account, - address _spender - ) external view returns (uint256) { - return _numberOfTransfersAllowed[_account][_spender]; - } - - function isSecureWallet(address _account) public view returns (bool) { - return - _holderAccounts[_account].firstWallet != address(0) && - _holderAccounts[_account].secondWallet != address(0); - } - - function isSecureToken( - uint256 _tokenId - ) public view virtual override returns (bool) { - address _owner = ownerOf(_tokenId); - - return isSecureWallet(_owner); - } - - // region ERC721 overrides - - function transferFrom( - address _from, - address _to, - uint256 _tokenId - ) public virtual override(ERC721, IERC721) { - address _sender = _msgSender(); - address _owner = ownerOf(_tokenId); - - if (_sender == _owner && isSecureWallet(_owner)) { - require( - _hasAllowedTransfer(_owner, _tokenId, _to), - "[100] KBT721: Sender is a secure wallet and doesn't have approval for the token" - ); - } - - super.transferFrom(_from, _to, _tokenId); - - if (_sender == _owner) { - delete _transferConditions[_owner]; - } else { - if (_numberOfTransfersAllowed[_owner][_sender] != 0) { - if (_numberOfTransfersAllowed[_owner][_sender] == 1) { - _setApprovalForAll(_owner, _sender, false); - } - _numberOfTransfersAllowed[_owner][_sender] -= 1; - } - } - } - - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId - ) public virtual override(ERC721, IERC721) { - safeTransferFrom(_from, _to, _tokenId, ""); - } - - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes memory data - ) public virtual override(ERC721, IERC721) { - address _sender = _msgSender(); - address _owner = ownerOf(_tokenId); - - if (_sender == _owner && isSecureWallet(_owner)) { - require( - _hasAllowedTransfer(_owner, _tokenId, _to), - "[100] KBT721: Owner is a secure wallet and doesn't have approval for the token" - ); - } - - super.safeTransferFrom(_from, _to, _tokenId, data); - - if (_sender == _owner) { - delete _transferConditions[_owner]; - } else { - if (_numberOfTransfersAllowed[_owner][_sender] != 0) { - if (_numberOfTransfersAllowed[_owner][_sender] == 1) { - _setApprovalForAll(_owner, _sender, false); - } - _numberOfTransfersAllowed[_owner][_sender] -= 1; - } - } - } - - function approve( - address _to, - uint256 _tokenId - ) public virtual override(ERC721, IERC721) { - address _owner = ownerOf(_tokenId); - - if (isSecureWallet(_owner)) { - require( - _approvalConditions[_owner].time > 0, - "[101] KBT721: Spending of funds is not authorized." - ); - require( - _isApprovalAllowed(_owner), - "[102] KBT721: Time has expired for the spending of funds" - ); - } - - super.approve(_to, _tokenId); - - _numberOfTransfersAllowed[_owner][_to] = _approvalConditions[_owner] - .numberOfTransfers; - - delete _approvalConditions[_owner]; - } - - function setApprovalForAll( - address _operator, - bool _approved - ) public virtual override(ERC721, IERC721) { - address _sender = _msgSender(); - if (isSecureWallet(_sender)) { - require( - _approvalConditions[_sender].time > 0, - "[101] KBT721: Spending of funds is not authorized." - ); - require( - _isApprovalAllowed(_sender), - "[102] KBT721: Time has expired for the spending of funds" - ); - } - - super.setApprovalForAll(_operator, _approved); - - _numberOfTransfersAllowed[_sender][_operator] = _approvalConditions[ - _sender - ].numberOfTransfers; - - delete _approvalConditions[_sender]; - } - - function _afterTokenTransfer( - address _from, - address _to, - uint256 _firstTokenId, - uint256 _batchSize - ) internal virtual override { - if (_from != address(0)) { - // region update secureAccounts - if (isSecureWallet(_from) && balanceOf(_from) == 0) { - delete _firstAccounts[_holderAccounts[_from].firstWallet]; - delete _secondAccounts[_holderAccounts[_from].secondWallet]; - delete _holderAccounts[_from]; - } - // endregion - if (balanceOf(_from) == 0) { - emit Egress(_from, _firstTokenId); - } - } - - if (_to != address(0) && balanceOf(_to) == _batchSize) { - emit Ingress(_to, _firstTokenId); - } - } - - // endregion - - function _hasAllowedTransfer( - address _account, - uint256 _tokenId, - address _to - ) internal view returns (bool) { - TransferConditions memory conditions = _transferConditions[_account]; - - if (conditions.anyToken) { - return true; - } - - if ( - (conditions.tokenId == 0 && - conditions.time == 0 && - conditions.to == address(0)) || - (conditions.tokenId > 0 && conditions.tokenId != _tokenId) || - (conditions.time > 0 && conditions.time < block.timestamp) || - (conditions.to != address(0) && conditions.to != _to) - ) { - return false; - } - - return true; - } - - function _isApprovalAllowed(address account) internal view returns (bool) { - return _approvalConditions[account].time >= block.timestamp; - } - - function _getAccountHolder() internal view returns (address) { - address sender = _msgSender(); - return - _firstAccounts[sender].accountHolderWallet != address(0) - ? _firstAccounts[sender].accountHolderWallet - : ( - _secondAccounts[sender].accountHolderWallet != address(0) - ? _secondAccounts[sender].accountHolderWallet - : address(0) - ); - } - - function _getOtherSecureWallet() internal view returns (address) { - address sender = _msgSender(); - address accountHolder = _getAccountHolder(); - - return - _holderAccounts[accountHolder].firstWallet == sender - ? _holderAccounts[accountHolder].secondWallet - : _holderAccounts[accountHolder].firstWallet; - } -} diff --git a/assets/eip-6809/contracts/MyFirstKBT.sol b/assets/eip-6809/contracts/MyFirstKBT.sol deleted file mode 100644 index c8b0596ca03fed..00000000000000 --- a/assets/eip-6809/contracts/MyFirstKBT.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "./KBT721.sol"; - -contract MyFirstKBT is KBT721 { - constructor() KBT721("MyFirstKBT", "FirstKBT") {} - - function safeMint( - address to, - uint256 tokenId - ) external virtual onlyOwner returns (bool) { - _safeMint(to, tokenId); - - return true; - } -} diff --git a/assets/eip-6809/migrations/1_deploy_contracts.js b/assets/eip-6809/migrations/1_deploy_contracts.js deleted file mode 100644 index f70c4f14e0aca3..00000000000000 --- a/assets/eip-6809/migrations/1_deploy_contracts.js +++ /dev/null @@ -1,5 +0,0 @@ -const FirstKBT = artifacts.require("MyFirstKBT"); - -module.exports = function (deployer) { - deployer.deploy(FirstKBT); -}; diff --git a/assets/eip-6809/package.json b/assets/eip-6809/package.json deleted file mode 100644 index 6c5339980ffba5..00000000000000 --- a/assets/eip-6809/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "dependencies": { - "@openzeppelin/test-helpers": "^0.5.16", - "@truffle/hdwallet-provider": "^2.1.7", - "dotenv": "^16.0.3" - }, - "devDependencies": { - "@openzeppelin/contracts": "^4.8.2" - } -} diff --git a/assets/eip-6809/test/kbt721.js b/assets/eip-6809/test/kbt721.js deleted file mode 100644 index add32a5a178018..00000000000000 --- a/assets/eip-6809/test/kbt721.js +++ /dev/null @@ -1,321 +0,0 @@ -const { - BN, // Big Number support - constants, // Common constants, like the zero address and largest integers - expectEvent, // Assertions for emitted events - expectRevert, // Assertions for transactions that should fail - time, -} = require('@openzeppelin/test-helpers'); -const { web3 } = require('@openzeppelin/test-helpers/src/setup'); - -const FirstKBT = artifacts.require("MyFirstKBT"); - -contract('FirstKBT', (accounts) => { - - let instance; - - before(async () => { - instance = await FirstKBT.new(); - }); - - const [deploymentAccount, accountHolder, secondAccountHolder, firstAccount, secondAccount, - thirdAccount, fourthAccount, spenderAccount] = accounts; - - const tokenIds = [11, 22, 33, 34, 35]; - const secondTokenIds = [44, 55, 66]; - - it('1. Check the MyFirstKBT after deployment', async () => { - const name = await instance.name(); - assert.equal(name, name, 'Name should be "MyFirstKBT" after deployment'); - }); - - it("2. accountHolder and secondAccountHolder should have 0 tokens", async function () { - let balance = await instance.balanceOf(accountHolder); - assert.equal(balance.toNumber(), 0, "accountHolder has more than 0"); - - balance = await instance.balanceOf(secondAccountHolder); - assert.equal(balance.toNumber(), 0, "secondAccountHolder has more than 0"); - }); - - it("3. addBindings : CANNOT add secure wallets if balance is 0", async function () { - await expectRevert( - instance.addBindings(firstAccount, secondAccount, { from: accountHolder }), - "[200] KBT721: Wallet is not a holder" - ); - const accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, constants.ZERO_ADDRESS, "First account is not empty"); - assert.equal(accountHolderBindings.secondWallet, constants.ZERO_ADDRESS, "Second account is not empty"); - }); - - it("4. mint: Owner should be able to mint tokens for accountHolder", async function () { - for (i = 0; i < tokenIds.length; i++) { - let tokenId = tokenIds[i]; - const event = await instance.safeMint(accountHolder, tokenId, { from: deploymentAccount }); - printGasUsed(event, 'safeMint'); - - let owner = await instance.ownerOf(tokenId); - assert.equal(owner, accountHolder, "Owner should be able to mint."); - - const token = await instance.tokenOfOwnerByIndex(accountHolder, i); - assert.equal(token.toNumber(), tokenId, "TokenId does not match"); - } - - const balance = await instance.balanceOf(accountHolder); - assert.equal(balance.toNumber(), tokenIds.length, "accountHolder should have tokens"); - }); - - it("5. transfer: CAN 1 token be transferred from accountHolder to secondAccountHolder", async function () { - const tokenId = 11; - const balanceAccountHolderOrig = await instance.balanceOf(accountHolder); - const balanceSecondAccountHolderOrig = await instance.balanceOf(secondAccountHolder); - - const event = await instance.safeTransferFrom(accountHolder, secondAccountHolder, tokenId, { from: accountHolder }); - printGasUsed(event, 'safeTransferFrom'); - - const balanceAccountHolder = await instance.balanceOf(accountHolder); - assert.equal(balanceAccountHolder.toNumber(), balanceAccountHolderOrig.toNumber() - 1, "transfer failed: accountHolder balance is wrong"); - - const balanceSecondAccountHolder = await instance.balanceOf(secondAccountHolder); - assert.equal(balanceSecondAccountHolder.toNumber(), balanceSecondAccountHolderOrig.toNumber() + 1, "transfer failed: secondAccountHolder balance is wrong"); - - const owner = await instance.ownerOf(tokenId); - assert.equal(owner, secondAccountHolder, "transfer failed: new owner is not secondAccountHolder"); - }); - - it("6. addBindings : CAN add secure wallets | EVENT: AccountSecured was emitted", async function () { - emitted = await instance.addBindings(firstAccount, secondAccount, { from: accountHolder }); - printGasUsed(emitted, 'addBindings'); - balance = await instance.balanceOf(accountHolder); - expectEvent(emitted, "AccountSecured", { _account: accountHolder, _noOfTokens: balance }); - - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, firstAccount, "First account was not set as a secure wallet"); - assert.equal(accountHolderBindings.secondWallet, secondAccount, "Second account was not set as a secure wallet"); - }); - - it("7. isSecureToken : Token 11 should not be secure, Token 22 should be secure", async function () { - isSecure = await instance.isSecureToken(11); - assert.isFalse(isSecure, "Token 11 is not a secure token"); - - isSecure = await instance.isSecureToken(22); - assert.isTrue(isSecure, "Token 22 is not a secure token"); - }); - - it("8. addBindings : CANNOT add secure wallets a second time", async function () { - expectRevert( - instance.addBindings(thirdAccount, fourthAccount, { from: accountHolder }), - "[201] KBT721: Key wallets are already filled" - ); - }); - - it("9. addBindings : CANNOT add secure wallets that are already secure wallets to another account", async function () { - await mintTokensTmp(secondTokenIds, instance, secondAccountHolder, deploymentAccount); - - expectRevert( - instance.addBindings(firstAccount, fourthAccount, { from: secondAccountHolder }), - "[203] KBT721: Key wallet 1 is already registered" - ); - expectRevert( - instance.addBindings(thirdAccount, secondAccount, { from: secondAccountHolder }), - "[204] KBT721: Key wallet 2 is already registered" - ); - }); - - it("10. addBindings : accountHolder is a secure wallet", function () { - instance.isSecureWallet(accountHolder).then(function (result) { - assert.equal(result, true, "accountHolder is not a secure wallet"); - }); - }); - - it("11. addBindings : firstAccount is NOT a secure wallet", function () { - instance.isSecureWallet(firstAccount).then(function (result) { - assert.equal(result, false, "firstAccount is a secure wallet"); - }); - }); - - it("12. transfer : accountHolder CANNOT transfer token 22 before allowTransfer", async function () { - tokenId = 22; - await expectRevert(instance.safeTransferFrom(accountHolder, secondAccountHolder, tokenId, { from: accountHolder }), "[100] KBT721: Owner is a secure wallet and doesn't have approval for the token"); - }); - - it("13. allowTransfer : firstAccount CAN Unlock token 22 | EVENT: AccountEnabledTransfer", async function () { - tokenId = 22; - emitted = await instance.allowTransfer(tokenId, 0, constants.ZERO_ADDRESS, false, { from: firstAccount }); - printGasUsed(emitted, 'allowTransfer'); - expectEvent(emitted, "AccountEnabledTransfer", { _account: accountHolder, _tokenId: new BN(tokenId), _time: new BN(0), _to: constants.ZERO_ADDRESS, _anyToken: false }); - result = await instance.getTransferableFunds(accountHolder); - assert.equal(result.tokenId, tokenId, "accountHolder does not have " + tokenId + " token unlocked"); - expectRevert(instance.allowTransfer(44, 0, constants.ZERO_ADDRESS, false, { from: firstAccount }), "[501] KBT721: Invalid tokenId."); - }); - - it("14. transfer : accountHolder CAN transfer token 22 to secondAccountHolder", async function () { - tokenId = 22; - initialBalance = (await instance.balanceOf(secondAccountHolder)).toNumber(); - - await instance.safeTransferFrom(accountHolder, secondAccountHolder, tokenId, { from: accountHolder }); - - balance = (await instance.balanceOf(secondAccountHolder)).toNumber(); - assert.equal(balance, initialBalance + 1, "transfer failed"); - }); - - it("15. approve : account holder CAN'T approve (without Authorize Spending UNLOCKED)", async function () { - tokenId = 34; - expectRevert(instance.approve(spenderAccount, tokenId, { from: accountHolder }), "[101] KBT721: Spending of funds is not authorized."); - }); - - it("16. allowApproval : firstAccount CAN Authorize Spending | EVENT: AccountEnabledApproval ", async function () { - tokenId = 34; - numberOfTransfers = 1; - - emitted = await instance.allowApproval(tokenId, numberOfTransfers, { from: firstAccount }); - printGasUsed(emitted, 'allowApproval'); - forTime = new BN(await time.latest()); - forTime = forTime.add(new BN(tokenId)); - forNumberOfTransfers = new BN(numberOfTransfers); - expectEvent(emitted, "AccountEnabledApproval", { _account: accountHolder, _time: forTime, _numberOfTransfers: forNumberOfTransfers }); - result = await instance.getApprovalConditions(accountHolder); - assert.isAbove(Number(result.time), 0, "accountHolder does not have Authorize Spending"); - }); - - it("17. transferFrom : spenderAccount CANNOT transferFrom accountHolder without Approval", function () { - tokenId = 34; - expectRevert(instance.transferFrom(accountHolder, secondAccountHolder, tokenId, { from: spenderAccount }), "ERC721: caller is not token owner or approved"); - }); - - it("18. approve : account holder CAN approve spenderAccount", async function () { - tokenId = 34; - owner = await instance.ownerOf(tokenId); - emitted = await instance.approve(spenderAccount, tokenId, { from: accountHolder }); - printGasUsed(emitted, 'approve'); - spender = await instance.getApproved(tokenId); - assert.equal(spender, spenderAccount, "Approval didn't went as planned.") - }); - - it("19. transferFrom : spenderAccount CAN transferFrom accountHolder", async function () { - tokenId = 34; - emitted = await instance.transferFrom(accountHolder, secondAccountHolder, tokenId, { from: spenderAccount }); - printGasUsed(emitted, 'transferFrom'); - }); - - it("20. transferFrom: spenderAccount CAN transferFrom accountHolder as much as they want", async function () { - tempTokenIds = [101, 102, 103]; - i = 0; - while (i < tempTokenIds.length) { - await instance.safeMint(accountHolder, tempTokenIds[i], { from: deploymentAccount }); - i++; - } - - await instance.allowApproval(100, tempTokenIds.length, { from: firstAccount }); - await instance.setApprovalForAll(spenderAccount, true, { from: accountHolder }); - i = 0; - while (i < tempTokenIds.length) { - emitted = await instance.transferFrom(accountHolder, secondAccountHolder, tempTokenIds[i], { from: spenderAccount }); - i++; - } - }); - - it("21. transferFrom: spenderAccount CAN transferFrom accountHolder but no more than he's allowed", async function () { - - tempTokenIds = [104, 105, 106]; - i = 0; - while (i < tempTokenIds.length) { - await instance.safeMint(accountHolder, tempTokenIds[i], { from: deploymentAccount }); - i++; - } - - await instance.allowApproval(100, tempTokenIds.length - 1, { from: firstAccount }); - await instance.setApprovalForAll(spenderAccount, true, { from: accountHolder }); - i = 0; - while (i < tempTokenIds.length - 1) { - emitted = await instance.transferFrom(accountHolder, secondAccountHolder, tempTokenIds[i], { from: spenderAccount }); - i++; - } - - expectRevert(instance.transferFrom(accountHolder, secondAccountHolder, tempTokenIds[i], { from: spenderAccount }), "ERC721: caller is not token owner or approved"); - }); - - it("22. transferFrom: when spenderAccount transfers ALL funds accountHolder becomes unsecure", async function () { - tempTokenIds = await getTokenIds(instance, accountHolder); - i = 0; - while (i < tempTokenIds.length) { - tempTokenId = tempTokenIds[i]; - - await instance.allowTransfer(tempTokenId, 0, constants.ZERO_ADDRESS, false, { from: firstAccount }); - await instance.transferFrom(accountHolder, secondAccountHolder, tempTokenId, { from: accountHolder }); - i++; - } - const binding = await instance.getBindings(accountHolder); - - assert(binding.firstWallet === constants.ZERO_ADDRESS && - binding.secondWallet === constants.ZERO_ADDRESS, - "accountHolder is still secure"); - - }); - - it("23. safeFallback : accountHolder becomes unsecure, other 2FA wallet has at least accountHolder funds | EVENT: SafeFallbackActivated", async function () { - const tokenId = 7; - await instance.safeMint(accountHolder, tokenId, { from: deploymentAccount }); - expectRevert(instance.safeFallback({ from: firstAccount }), "[400] KBT721: Key authorization failure"); - await instance.addBindings(firstAccount, secondAccount, { from: accountHolder }); - - balance = (await instance.balanceOf(accountHolder)).toNumber(); - emitted = await instance.safeFallback({ from: firstAccount }); - printGasUsed(emitted, 'safeFallback'); - expectEvent(emitted, "SafeFallbackActivated", { _account: accountHolder }); - - secondAccountBalance = (await instance.balanceOf(secondAccount)).toNumber(); - assert.isAtLeast(secondAccountBalance, balance, "second account doesn't have the full amount from account holder"); - - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - assert.equal(accountHolderBindings.secondWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - }); - - it("24. resetBindings : accountHolder becomes unsecure | EVENT: AccountResetBinding", async function () { - await instance.safeMint(accountHolder, 100, { from: deploymentAccount }); - await instance.addBindings(firstAccount, secondAccount, { from: accountHolder }); - - emitted = await instance.resetBindings({ from: firstAccount }); - printGasUsed(emitted, 'resetBindings'); - expectEvent(emitted, "AccountResetBinding", { _account: accountHolder }); - - accountHolderBindings = await instance.getBindings(accountHolder); - assert.equal(accountHolderBindings.firstWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - assert.equal(accountHolderBindings.secondWallet, constants.ZERO_ADDRESS, "accountHolder is still secure"); - }); - -}); - -async function mintTokensTmp(tokenIds, instance, accountHolder, deploymentAccount) { - for (i = 0; i < tokenIds.length; i++) { - let tokenId = tokenIds[i]; - await instance.safeMint(accountHolder, tokenId, { from: deploymentAccount }); - } -} - -async function listTokens(instance, accountHolder) { - const noOfTokens = (await instance.balanceOf(accountHolder)).toNumber(); - let i = 0; - while (i < noOfTokens) { - let tempToken = (await instance.tokenOfOwnerByIndex(accountHolder, i)).toNumber(); - console.log(i + ": " + tempToken); - i++; - } -} - -async function getTokenIds(instance, accountHolder) { - const noOfTokens = (await instance.balanceOf(accountHolder)).toNumber(); - let tokenIds = []; - let i = 0; - while (i < noOfTokens) { - let tempToken = (await instance.tokenOfOwnerByIndex(accountHolder, i)).toNumber(); - tokenIds.push(tempToken); - i++; - } - - return tokenIds; -} - -function printGasUsed(event, methodName) { - const gasUsed = event.receipt.gasUsed; - console.log(`GasUsed: ${gasUsed.toLocaleString()} for '${methodName}'`); -} diff --git a/assets/eip-6809/truffle-config.js b/assets/eip-6809/truffle-config.js deleted file mode 100644 index 4f9fd7c50012a2..00000000000000 --- a/assets/eip-6809/truffle-config.js +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Use this file to configure your truffle project. It's seeded with some - * common settings for different networks and features like migrations, - * compilation, and testing. Uncomment the ones you need or modify - * them to suit your project as necessary. - * - * More information about configuration can be found at: - * - * https://trufflesuite.com/docs/truffle/reference/configuration - * - * Hands-off deployment with Infura - * -------------------------------- - * - * Do you have a complex application that requires lots of transactions to deploy? - * Use this approach to make deployment a breeze 🏖️: - * - * Infura deployment needs a wallet provider (like @truffle/hdwallet-provider) - * to sign transactions before they're sent to a remote public node. - * Infura accounts are available for free at 🔍: https://infura.io/register - * - * You'll need a mnemonic - the twelve word phrase the wallet uses to generate - * public/private key pairs. You can store your secrets 🤐 in a .env file. - * In your project root, run `$ npm install dotenv`. - * Create .env (which should be .gitignored) and declare your MNEMONIC - * and Infura PROJECT_ID variables inside. - * For example, your .env file will have the following structure: - * - * MNEMONIC = - * PROJECT_ID = - * - * Deployment with Truffle Dashboard (Recommended for best security practice) - * -------------------------------------------------------------------------- - * - * Are you concerned about security and minimizing rekt status 🤔? - * Use this method for best security: - * - * Truffle Dashboard lets you review transactions in detail, and leverages - * MetaMask for signing, so there's no need to copy-paste your mnemonic. - * More details can be found at 🔎: - * - * https://trufflesuite.com/docs/truffle/getting-started/using-the-truffle-dashboard/ - */ - -require('dotenv').config(); -// const { MNEMONIC, PROJECT_ID } = process.env; - -const HDWalletProvider = require('@truffle/hdwallet-provider'); - -//to fetch these keys from .env file -const privateKey = process.env.PRIVATE_KEY; -const infura_api_key = process.env.INFURA_API_KEY; -const etherscan_api_key = process.env.ETHERSCAN_API_KEY; - -module.exports = { - /** - * Networks define how you connect to your ethereum client and let you set the - * defaults web3 uses to send transactions. If you don't specify one truffle - * will spin up a managed Ganache instance for you on port 9545 when you - * run `develop` or `test`. You can ask a truffle command to use a specific - * network from the command line, e.g - * - * $ truffle test --network - */ - plugins: [ - 'truffle-plugin-verify' - ], - api_keys: { - etherscan: etherscan_api_key - }, - - networks: { - // Useful for testing. The `development` name is special - truffle uses it by default - // if it's defined here and no other network is specified at the command line. - // You should run a client (like ganache, geth, or parity) in a separate terminal - // tab if you use this network and you must also set the `host`, `port` and `network_id` - // options below to some value. - // - development: { - host: "127.0.0.1", // Localhost (default: none) - port: 7545, // Standard Ethereum port (default: none) - network_id: "*", // Any network (default: none) - }, - // - // An additional network, but with some advanced options… - // advanced: { - // port: 8777, // Custom port - // network_id: 1342, // Custom network - // gas: 8500000, // Gas sent with each transaction (default: ~6700000) - // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) - // from:
, // Account to send transactions from (default: accounts[0]) - // websocket: true // Enable EventEmitter interface for web3 (default: false) - // }, - // - // Useful for deploying to a public network. - // Note: It's important to wrap the provider as a function to ensure truffle uses a new provider every time. - goerli: { - provider: () => new HDWalletProvider(privateKey, `https://goerli.infura.io/v3/${infura_api_key}`), - network_id: 5, // Goerli's id - gas: 5000000, //gas limit - confirmations: 1, // # of confirmations to wait between deployments. (default: 0) - timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) - skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) - }, - // - // Useful for private networks - // private: { - // provider: () => new HDWalletProvider(MNEMONIC, `https://network.io`), - // network_id: 2111, // This network is yours, in the cloud. - // production: true // Treats this network as if it was a public net. (default: false) - // } - }, - - // Set default mocha options here, use special reporters, etc. - mocha: { - // timeout: 100000 - }, - - // Configure your compilers - compilers: { - solc: { - version: "0.8.17", // Fetch exact version from solc-bin (default: truffle's version) - // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) - settings: { // See the solidity docs for advice about optimization and evmVersion - optimizer: { - enabled: true, - runs: 200 - }, - // evmVersion: "byzantium" - } - } - } - - // Truffle DB is currently disabled by default; to enable it, change enabled: - // false to enabled: true. The default storage location can also be - // overridden by specifying the adapter settings, as shown in the commented code below. - // - // NOTE: It is not possible to migrate your contracts to truffle DB and you should - // make a backup of your artifacts to a safe location before enabling this feature. - // - // After you backed up your artifacts you can utilize db by running migrate as follows: - // $ truffle migrate --reset --compile-all - // - // db: { - // enabled: false, - // host: "127.0.0.1", - // adapter: { - // name: "indexeddb", - // settings: { - // directory: ".db" - // } - // } - // } -}; diff --git a/assets/eip-6865/contracts/IEIP712Visualizer.sol b/assets/eip-6865/contracts/IEIP712Visualizer.sol deleted file mode 100644 index 63e2f4a27f390c..00000000000000 --- a/assets/eip-6865/contracts/IEIP712Visualizer.sol +++ /dev/null @@ -1,43 +0,0 @@ -pragma solidity ^0.8.0; - -interface IEIP712Visualizer { - struct Liveness { - uint256 from; - uint256 to; - } - - struct UserAssetMovement { - address assetTokenAddress; - uint256 id; - uint256[] amounts; - } - - struct Result { - UserAssetMovement[] assetsIn; - UserAssetMovement[] assetsOut; - Liveness liveness; - } - - /** - * @notice This function processes an EIP-712 payload message and returns a structured data format emphasizing the potential impact on users' assets. - * @dev The function returns assetsOut (assets the user is offering), assetsIn (assets the user would receive), and liveness (validity duration of the EIP-712 message). - * - * - MUST revert if the domainHash identifier is not supported (require(domainHash == DOMAIN_SEPARATOR, "message")). - * - MUST NOT revert if there are no assetsIn, assetsOut, or liveness values; returns nullish values instead. - * - assetsIn MUST include only assets for which the user is the recipient. - * - assetsOut MUST include only assets for which the user is the sender. - * - MUST returns liveness.to as type(uint256).max if the message never expires. - * - MUST returns liveness.from as block.timestamp if the message does not have a validity starting date. - * - MUST returns a set (array) of amounts in assetsIn.amounts and assetsOut.amount where items define the amount per time curve, with time defined within liveness boundaries. - * - amounts items MUST include the minimum amount. - * - MUST returns the minimum amount if amounts set contains only one item - * - * @param encodedMessage The ABI-encoded EIP-712 message (abi.encode(types, params)). - * @param domainHash The hash of the EIP-712 domain separator as defined in the EIP-712 proposal; see https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator. - * @return Result struct containing the user's assets impact and message liveness. - */ - function visualizeEIP712Message( - bytes memory encodedMessage, - bytes32 domainHash - ) external view returns (Result memory); -} diff --git a/assets/eip-6865/contracts/SeaPortEIP712Visualizer.sol b/assets/eip-6865/contracts/SeaPortEIP712Visualizer.sol deleted file mode 100644 index 43144c633901c6..00000000000000 --- a/assets/eip-6865/contracts/SeaPortEIP712Visualizer.sol +++ /dev/null @@ -1,192 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {IEIP712Visualizer} from "./IEIP712Visualizer.sol"; - -contract SeaPortEIP712Visualizer is IEIP712Visualizer { - bytes32 public constant DOMAIN_SEPARATOR = - 0xb50c8913581289bd2e066aeef89fceb9615d490d673131fd1a7047436706834e; //v1.1 - - enum OrderType { - FULL_OPEN, - PARTIAL_OPEN, - FULL_RESTRICTED, - PARTIAL_RESTRICTED, - CONTRACT - } - - enum ItemType { - NATIVE, - ERC20, - ERC721, - ERC1155, - ERC721_WITH_CRITERIA, - ERC1155_WITH_CRITERIA - } - struct OrderComponents { - address offerer; - address zone; - OfferItem[] offer; - ConsiderationItem[] consideration; - OrderType orderType; - uint256 startTime; - uint256 endTime; - bytes32 zoneHash; - uint256 salt; - bytes32 conduitKey; - uint256 counter; - } - - struct OfferItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; - } - - struct ConsiderationItem { - ItemType itemType; - address token; - uint256 identifierOrCriteria; - uint256 startAmount; - uint256 endAmount; - address payable recipient; - } - - constructor() {} - - function visualizeEIP712Message( - bytes memory encodedMessage, - bytes32 domainHash - ) external view returns (Result memory) { - require( - domainHash == DOMAIN_SEPARATOR, - "SeaPortEIP712Visualizer: unsupported domain" - ); - - OrderComponents memory order = abi.decode( - encodedMessage, - (OrderComponents) - ); - - UserAssetMovement[] memory assetsOut = new UserAssetMovement[]( - order.offer.length - ); - - for (uint256 i = 0; i < order.offer.length; ) { - uint256[] memory amounts = extractAmounts(order.offer[i]); - assetsOut[i] = UserAssetMovement({ - assetTokenAddress: order.offer[i].token, - id: order.offer[i].identifierOrCriteria, - amounts: amounts - }); - - unchecked { - ++i; - } - } - - ConsiderationItem[] memory userConsiderations = fliterByRecepient( - order.consideration, - order.offerer - ); - UserAssetMovement[] memory assetsIn = new UserAssetMovement[]( - userConsiderations.length - ); - - for (uint256 i = 0; i < userConsiderations.length; ) { - uint256[] memory amounts = extractAmounts(userConsiderations[i]); - - assetsIn[i] = UserAssetMovement({ - assetTokenAddress: userConsiderations[i].token, - id: userConsiderations[i].identifierOrCriteria, - amounts: amounts - }); - - unchecked { - ++i; - } - } - - return - Result({ - assetsIn: assetsIn, - assetsOut: assetsOut, - liveness: Liveness({from: order.startTime, to: order.endTime}) - }); - } - - function fliterByRecepient( - ConsiderationItem[] memory consideration, - address recepient - ) private view returns (ConsiderationItem[] memory) { - uint256 recepientItemsCount; - for (uint256 i = 0; i < consideration.length; ) { - if (consideration[i].recipient == recepient) { - unchecked { - ++recepientItemsCount; - } - } - - unchecked { - ++i; - } - } - ConsiderationItem[] memory result = new ConsiderationItem[]( - recepientItemsCount - ); - uint256 resultIndex; - for (uint256 i = 0; i < recepientItemsCount; ) { - if (consideration[i].recipient == recepient) { - result[resultIndex] = consideration[i]; - unchecked { - ++resultIndex; - } - } - - unchecked { - ++i; - } - } - - return result; - } - - function extractAmounts( - OfferItem memory offer - ) private pure returns (uint256[] memory) { - uint256[] memory amounts = new uint256[](2); - if (offer.endAmount == offer.startAmount) { - uint256[] memory amounts = new uint256[](1); - amounts[0] = offer.startAmount; - return amounts; - } else if (offer.endAmount > offer.startAmount) { - amounts[0] = offer.startAmount; - amounts[1] = offer.endAmount; - } else if (offer.endAmount < offer.startAmount) { - amounts[0] = offer.endAmount; - amounts[1] = offer.startAmount; - } - - return amounts; - } - - function extractAmounts( - ConsiderationItem memory consideration - ) private pure returns (uint256[] memory) { - uint256[] memory amounts = new uint256[](2); - if (consideration.endAmount == consideration.startAmount) { - uint256[] memory amounts = new uint256[](1); - amounts[0] = consideration.startAmount; - return amounts; - } else if (consideration.endAmount > consideration.startAmount) { - amounts[0] = consideration.startAmount; - amounts[1] = consideration.endAmount; - } else { - amounts[0] = consideration.endAmount; - amounts[1] = consideration.startAmount; - } - return amounts; - } -} diff --git a/assets/eip-6865/current-EIP-712-signature-wallet-interface.png b/assets/eip-6865/current-EIP-712-signature-wallet-interface.png deleted file mode 100644 index 9e2a8fb009ff3f..00000000000000 Binary files a/assets/eip-6865/current-EIP-712-signature-wallet-interface.png and /dev/null differ diff --git a/assets/eip-6865/vision-EIP-712-signature-wallet-interface.png b/assets/eip-6865/vision-EIP-712-signature-wallet-interface.png deleted file mode 100644 index c7bc75c4bb97c8..00000000000000 Binary files a/assets/eip-6865/vision-EIP-712-signature-wallet-interface.png and /dev/null differ diff --git a/assets/eip-6900/MSCA_Shared_Components_Diagram.svg b/assets/eip-6900/MSCA_Shared_Components_Diagram.svg deleted file mode 100644 index 836a8ad980db72..00000000000000 --- a/assets/eip-6900/MSCA_Shared_Components_Diagram.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - Alice's MSCAUO / TxPluginHooksValidationExecutionPluginHooksValidationExecutionUO / TxBob's MSCA \ No newline at end of file diff --git a/assets/eip-6900/Modular_Account_Call_Flow.svg b/assets/eip-6900/Modular_Account_Call_Flow.svg deleted file mode 100644 index c4112c335c83ae..00000000000000 --- a/assets/eip-6900/Modular_Account_Call_Flow.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - Direct Call1 validation2. Execution Modular Account validateUserOpPre User Operation Validation Hook(s)User Operation Validation FunctionPre Execution Hook(s)Native Function / (Plugin) Execution FunctionPost Execution Hook(s)EntryPointEOA / SCRuntime Validation FunctionModular Account Call FlowPre Runtime Validation Hook(s) \ No newline at end of file diff --git a/assets/eip-6900/Plugin_Execution_Flow.svg b/assets/eip-6900/Plugin_Execution_Flow.svg deleted file mode 100644 index b5b013d5e4fa34..00000000000000 --- a/assets/eip-6900/Plugin_Execution_Flow.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - Permitted External Contracts & Methods executeFromPluginPost Permitted Call Hook(s)Pre Execution Hook(s)Pre Permitted Call Hook(s)Permitted Plugin Execution FunctionPost Execution Hook(s)PluginsPlugin Permission Check executeFromPluginExternal(Plugin) Execution Function1 calls plugin 2.2 calls external contracts through executeFromPluginExternalPre Permitted Call Hook(s)Post Permitted Call Hook(s)Plugin Permission CheckModular AccountPlugin Execution Flow2.1 calls other installed plugin through executeFromPluginPre executeFromPluginExternal Hook(s)Post executeFromPluginExternal Hook(s) \ No newline at end of file diff --git a/assets/eip-6956/.gitignore b/assets/eip-6956/.gitignore deleted file mode 100644 index 74956f8d80a992..00000000000000 --- a/assets/eip-6956/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules - -#Hardhat files -cache -artifacts -typechain* - -package-lock.json diff --git a/assets/eip-6956/LICENSE.md b/assets/eip-6956/LICENSE.md deleted file mode 100644 index 1d853863a3b82b..00000000000000 --- a/assets/eip-6956/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Authentic Vision GmbH - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/assets/eip-6956/README.md b/assets/eip-6956/README.md deleted file mode 100644 index dffbad5b12688b..00000000000000 --- a/assets/eip-6956/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# ERCxxxx Reference implementation -This reference implementation is [MIT](LICENSE.md) licensed and can therefore be freely used in any project. - -## Getting started -From this directory, run - -``` -npm install && npx hardhat test -``` - - - diff --git a/assets/eip-6956/contracts/ERC6956.sol b/assets/eip-6956/contracts/ERC6956.sol deleted file mode 100644 index 39543258b1fae4..00000000000000 --- a/assets/eip-6956/contracts/ERC6956.sol +++ /dev/null @@ -1,491 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.18; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/security/Pausable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; - -import "./IERC6956.sol"; - -/** Used for several authorization mechansims, e.g. who can burn, who can set approval, ... - * @dev Specifying the role in the ecosystem. Used in conjunction with IERC6956.Authorization - */ -enum Role { - OWNER, // =0, The owner of the digital token - ISSUER, // =1, The issuer (contract) of the tokens, typically represented through a MAINTAINER_ROLE, the contract owner etc. - ASSET, // =2, The asset identified by the anchor - INVALID // =3, Reserved, do not use. -} - -/** - * @title ASSET-BOUND NFT minimal reference implementation - * @author Thomas Bergmueller (@tbergmueller) - * - * @dev Error messages - * ``` - * ERROR | Message - * ------|------------------------------------------------------------------- - * E1 | Only maintainer allowed - * E2 | No permission to burn - * E3 | Token does not exist, call transferAnchor first to mint - * E4 | batchSize must be 1 - * E5 | Token not transferable - * E6 | Token already owned - * E7 | Not authorized based on ERC6956Authorization - * E8 | Attestation not signed by trusted oracle - * E9 | Attestation already used - * E10 | Attestation not valid yet - * E11 | Attestation expired - * E12 | Attestation expired (contract limit) - * E13 | Invalid signature length - * E14-20| Reserved for future use - * ``` - */ -contract ERC6956 is - ERC721, - ERC721Enumerable, - ERC721Burnable, - IERC6956 -{ - using Counters for Counters.Counter; - - mapping(bytes32 => bool) internal _anchorIsReleased; // currently released anchors. Per default, all anchors are dropped, i.e. 1:1 bound - - mapping(address => bool) public maintainers; - - /// @notice Resolves tokenID to anchor. Inverse of tokenByAnchor - mapping(uint256 => bytes32) public anchorByToken; - - /// @notice Resolves Anchor to tokenID. Inverse of anchorByToken - mapping(bytes32 => uint256) public tokenByAnchor; - - mapping(address => bool) private _trustedOracles; - - /// @dev stores the anchors for each attestation - mapping(bytes32 => bytes32) private _anchorByUsedAttestation; - - /// @dev stores handed-back tokens (via burn) - mapping (bytes32 => uint256) private _burnedTokensByAnchor; - - - /** - * @dev Counter to keep track of issued tokens - */ - Counters.Counter private _tokenIdCounter; - - /// @dev Default validity timespan of attestation. In validateAttestation the attestationTime is checked for MIN(defaultAttestationvalidity, attestation.expiry) - uint256 public maxAttestationExpireTime = 5*60; // 5min valid per default - - Authorization public burnAuthorization; - Authorization public approveAuthorization; - - - /// @dev Records the number of transfers done for each attestation - mapping(bytes32 => uint256) public attestationsUsedByAnchor; - - modifier onlyMaintainer() { - require(isMaintainer(msg.sender), "ERC6956-E1"); - _; - } - - /** - * @notice Behaves like ERC721 burn() for wallet-cleaning purposes. Note only the tokenId (as a wrapper) is burned, not the ASSET represented by the ANCHOR. - * @dev - * - tokenId is remembered for the anchor, to ensure a later transferAnchor(), which would mint, assigns the same tokenId. This ensures strict 1:1 relation - * - For burning, the anchor needs to be released. This forced release FOR BURNING ONLY is allowed for owner() or approvedOwner(). - * - * @param tokenId The token that shall be burned - */ - function burn(uint256 tokenId) public override - { - // remember the tokenId of burned tokens, s.t. one can issue the token with the same number again - bytes32 anchor = anchorByToken[tokenId]; - require(_roleBasedAuthorization(anchor, createAuthorizationMap(burnAuthorization)), "ERC6956-E2"); - _burn(tokenId); - } - - function burnAnchor(bytes memory attestation, bytes memory data) public virtual - authorized(Role.ASSET, createAuthorizationMap(burnAuthorization)) - { - address to; - bytes32 anchor; - bytes32 attestationHash; - (to, anchor, attestationHash) = decodeAttestationIfValid(attestation, data); - _commitAttestation(to, anchor, attestationHash); - uint256 tokenId = tokenByAnchor[anchor]; - // remember the tokenId of burned tokens, s.t. one can issue the token with the same number again - _burn(tokenId); - } - - function burnAnchor(bytes memory attestation) public virtual { - return burnAnchor(attestation, ""); - } - - function approveAnchor(bytes memory attestation, bytes memory data) public virtual - authorized(Role.ASSET, createAuthorizationMap(approveAuthorization)) - { - address to; - bytes32 anchor; - bytes32 attestationHash; - (to, anchor, attestationHash) = decodeAttestationIfValid(attestation, data); - _commitAttestation(to, anchor, attestationHash); - require(tokenByAnchor[anchor]>0, "ERC6956-E3"); - _approve(to, tokenByAnchor[anchor]); - } - - // approveAuth == ISSUER does not really make sense.. so no separate implementation, since ERC-721.approve already implies owner... - - function approve(address to, uint256 tokenId) public virtual override(ERC721,IERC721) - authorized(Role.OWNER, createAuthorizationMap(approveAuthorization)) - { - super.approve(to, tokenId); - } - - function approveAnchor(bytes memory attestation) public virtual { - return approveAnchor(attestation, ""); - } - - /** - * @notice Adds or removes a trusted oracle, used when verifying signatures in `decodeAttestationIfValid()` - * @dev Emits OracleUpdate - * @param oracle address of oracle - * @param doTrust true to add, false to remove - */ - function updateOracle(address oracle, bool doTrust) public - onlyMaintainer() - { - _trustedOracles[oracle] = doTrust; - emit OracleUpdate(oracle, doTrust); - } - - /** - * @dev A very simple function wich MUST return false, when `a` is not a maintainer - * When derived contracts extend ERC6956 contract, this function may be overridden - * e.g. by using AccessControl, onlyOwner or other common mechanisms - * - * Having this simple mechanism in the reference implementation ensures that the reference - * implementation is fully ERC-6956 compatible - */ - function isMaintainer(address a) public virtual view returns (bool) { - return maintainers[a]; - } - - - function createAuthorizationMap(Authorization _auth) public pure returns (uint256) { - uint256 authMap = 0; - if(_auth == Authorization.OWNER - || _auth == Authorization.OWNER_AND_ASSET - || _auth == Authorization.OWNER_AND_ISSUER - || _auth == Authorization.ALL) { - authMap |= uint256(1< 0) { - from = ownerOf(fromToken); - require(from != to, "ERC6956-E6"); - _safeTransfer(from, to, fromToken, ""); - } else { - _safeMint(to, anchor); - } - } - - function transferAnchor(bytes memory attestation) public virtual { - return transferAnchor(attestation, ""); - } - - - function hasAuthorization(Role _role, uint256 _auth ) public pure returns (bool) { - uint256 result = uint256(_auth & (1 << uint256(_role))); - return result > 0; - } - - modifier authorized(Role _role, uint256 _authMap) { - require(hasAuthorization(_role, _authMap), "ERC6956-E7"); - _; - } - - // The following functions are overrides required by Solidity, EIP-165. - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC721, ERC721Enumerable) - returns (bool) - { - return - interfaceId == type(IERC6956).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @notice Returns whether a certain address is registered as trusted oracle, i.e. attestations signed by this address are accepted in `decodeAttestationIfValid` - * @dev This function may be overwritten when extending ERC-6956, e.g. when other oracle-registration mechanics are used - * @param oracleAddress Address of the oracle in question - * @return isTrusted True, if oracle is trusted - */ - function isTrustedOracle(address oracleAddress) public virtual view returns (bool isTrusted) { - return _trustedOracles[oracleAddress]; - } - - - function decodeAttestationIfValid(bytes memory attestation, bytes memory data) public view returns (address to, bytes32 anchor, bytes32 attestationHash) { - uint256 attestationTime; - uint256 validStartTime; - uint256 validEndTime; - bytes memory signature; - bytes32[] memory proof; - - attestationHash = keccak256(attestation); - (to, anchor, attestationTime, validStartTime, validEndTime, signature) = abi.decode(attestation, (address, bytes32, uint256, uint256, uint256, bytes)); - - bytes32 messageHash = keccak256(abi.encodePacked(to, anchor, attestationTime, validStartTime, validEndTime, proof)); - address signer = _extractSigner(messageHash, signature); - - // Check if from trusted oracle - require(isTrustedOracle(signer), "ERC6956-E8"); - require(_anchorByUsedAttestation[attestationHash] <= 0, "ERC6956-E9"); - - // Check expiry - uint256 timestamp = block.timestamp; - require(timestamp > validStartTime, "ERC6956-E10"); - require(attestationTime + maxAttestationExpireTime > block.timestamp, "ERC6956-E11"); - require(validEndTime > block.timestamp, "ERC6956-E112"); - - - // Calling hook! - _beforeAttestationUse(anchor, to, data); - return(to, anchor, attestationHash); - } - - /// @notice Compatible with ERC721.tokenURI(). Returns {baseURI}{anchor} - /// @dev Returns when called for tokenId=5, baseURI=https://myurl.com/collection/ and anchorByToken[5] = 0x12345 - /// Example: https://myurl.com/collection/0x12345 - /// Works for non-burned tokens / active-Anchors only. - /// Anchor-based tokenURIs are needed as an anchor's corresponding tokenId is only known after mint. - /// @param tokenId TokenID - /// @return tokenURI Returns the Uniform Resource Identifier (URI) for `tokenId` token. - function tokenURI(uint256 tokenId) public view override returns (string memory) { - bytes32 anchor = anchorByToken[tokenId]; - string memory anchorString = Strings.toHexString(uint256(anchor)); - return bytes(_baseURI()).length > 0 ? string(abi.encodePacked(_baseURI(), anchorString)) : ""; - } - - function _baseURI() internal view virtual override(ERC721) returns (string memory) { - return _baseUri; - } - - /** - * @dev Base URI, MUST end with a slash. Will be used as `{baseURI}{tokenId}` in tokenURI() function - */ - string internal _baseUri = ""; // needs to end with '/' - - /// @notice Set a new BaseURI. Can be used with dynamic NFTs that have server APIs, IPFS-buckets - /// or any other suitable system. Refer tokenURI(tokenId) for anchor-based or tokenId-based format. - /// @param tokenBaseURI The token base-URI. Must end with slash '/'. - function updateBaseURI(string calldata tokenBaseURI) public onlyMaintainer() { - _baseUri = tokenBaseURI; - } - event BurnAuthorizationChange(Authorization burnAuth, address indexed maintainer); - - function updateBurnAuthorization(Authorization burnAuth) public onlyMaintainer() { - burnAuthorization = burnAuth; - emit BurnAuthorizationChange(burnAuth, msg.sender); - // TODO event - } - - event ApproveAuthorizationChange(Authorization approveAuth, address indexed maintainer); - - function updateApproveAuthorization(Authorization approveAuth) public onlyMaintainer() { - approveAuthorization = approveAuth; - emit ApproveAuthorizationChange(approveAuth, msg.sender); - - // TODO event - } - - constructor(string memory _name, string memory _symbol) - ERC721(_name, _symbol) { - maintainers[msg.sender] = true; // deployer is automatically maintainer - // Indicates general float-ability, i.e. whether anchors can be digitally dropped and released - - // OWNER and ASSET shall normally be in sync anyway, so this is reasonable default - // authorization for approve and burn, as it mimicks ERC-721 behavior - burnAuthorization = Authorization.OWNER_AND_ASSET; - approveAuthorization = Authorization.OWNER_AND_ASSET; - } - - /* - ########################## SIGNATURE MAGIC, - ########################## adapted from https://solidity-by-example.org/signature/ - */ - /** - * Returns the signer of a message. - * - * OFF-CHAIN: - * const [alice] = ethers.getSigners(); // = 0x3c44... - * const messageHash = ethers.utils.solidityKeccak256(["address", "bytes32"], [a, b]); - const sig = await alice.signMessage(ethers.utils.arrayify(messageHash)); - - ONCHAIN In this contract, call from - ``` - function (address a, bytes32 b, bytes memory sig) { - messageHash = keccak256(abi.encodePacked(to, b)); - signer = extractSigner(messageHash, sig); // signer will be 0x3c44... - } - ``` * - * @param messageHash A keccak25(abi.encodePacked(...)) hash - * @param sig Signature (length 65 bytes) - * - * @return The signer - */ - function _extractSigner(bytes32 messageHash, bytes memory sig) internal pure returns (address) { - require(sig.length == 65, "ERC6956-E13"); - /* - Signature is produced by signing a keccak256 hash with the following format: - "\x19Ethereum Signed Message\n" + len(msg) + msg - */ - bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)); - - bytes32 r; - bytes32 s; - uint8 v; - - // Extract the r, s, and v parameters from the signature - assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - v := byte(0, mload(add(sig, 96))) - } - - // Ensure the v parameter is either 27 or 28 - // TODO is this needed? - if (v < 27) { - v += 27; - } - - // Recover the public key from the signature and message hash - // and convert it to an address - address signer = ecrecover(ethSignedMessageHash, v, r, s); - return signer; - } -} diff --git a/assets/eip-6956/contracts/ERC6956Full.sol b/assets/eip-6956/contracts/ERC6956Full.sol deleted file mode 100644 index d7e67a22c55009..00000000000000 --- a/assets/eip-6956/contracts/ERC6956Full.sol +++ /dev/null @@ -1,206 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.18; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/security/Pausable.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; - -import "./ERC6956.sol"; -import "./IERC6956AttestationLimited.sol"; -import "./IERC6956Floatable.sol"; -import "./IERC6956ValidAnchors.sol"; - -/** - * @title ASSET-BOUND NFT implementation with all interfaces - * @author Thomas Bergmueller (@tbergmueller) - * @notice Extends ERC6956.sol with additional interfaces and functionality - * - * @dev Error-codes - * ERROR | Message - * ------|------------------------------------------------------------------- - * E1-20 | See ERC6956.sol - * E21 | No permission to start floating - * E22 | No permission to stop floating - * E23 | allowFloating can only be called when changing floating state - * E24 | No attested transfers left - * E25 | data must contain merkle-proof - * E26 | Anchor not valid - * E27 | Updating attestedTransferLimit violates policy - */ -contract ERC6956Full is ERC6956, IERC6956AttestationLimited, IERC6956Floatable, IERC6956ValidAnchors { - Authorization public floatStartAuthorization; - Authorization public floatStopAuthorization; - - /// ############################################################################################################################### - /// ############################################################################################## IERC6956AttestedTransferLimited - /// ############################################################################################################################### - - mapping(bytes32 => uint256) public attestedTransferLimitByAnchor; - mapping(bytes32 => FloatState) public floatingStateByAnchor; - - uint256 public globalAttestedTransferLimitByAnchor; - AttestationLimitPolicy public attestationLimitPolicy; - - bool public allFloating; - - /// @dev The merkle-tree root node, where proof is validated against. Update via updateValidAnchors(). Use salt-leafs in merkle-trees! - bytes32 private _validAnchorsMerkleRoot; - - function _requireValidLimitUpdate(uint256 oldValue, uint256 newValue) internal view { - if(newValue > oldValue) { - require(attestationLimitPolicy == AttestationLimitPolicy.FLEXIBLE || attestationLimitPolicy == AttestationLimitPolicy.INCREASE_ONLY, "ERC6956-E27"); - } else { - require(attestationLimitPolicy == AttestationLimitPolicy.FLEXIBLE || attestationLimitPolicy == AttestationLimitPolicy.DECREASE_ONLY, "ERC6956-E27"); - } - } - - function updateGlobalAttestationLimit(uint256 _nrTransfers) - public - onlyMaintainer() - { - _requireValidLimitUpdate(globalAttestedTransferLimitByAnchor, _nrTransfers); - globalAttestedTransferLimitByAnchor = _nrTransfers; - emit GlobalAttestationLimitUpdate(_nrTransfers, msg.sender); - } - - function updateAttestationLimit(bytes32 anchor, uint256 _nrTransfers) - public - onlyMaintainer() - { - uint256 currentLimit = attestationLimit(anchor); - _requireValidLimitUpdate(currentLimit, _nrTransfers); - attestedTransferLimitByAnchor[anchor] = _nrTransfers; - emit AttestationLimitUpdate(anchor, tokenByAnchor[anchor], _nrTransfers, msg.sender); - } - - function attestationLimit(bytes32 anchor) public view returns (uint256 limit) { - if(attestedTransferLimitByAnchor[anchor] > 0) { // Per anchor overwrites always, even if smaller than globalAttestedTransferLimit - return attestedTransferLimitByAnchor[anchor]; - } - return globalAttestedTransferLimitByAnchor; - } - - function attestationUsagesLeft(bytes32 anchor) public view returns (uint256 nrTransfersLeft) { - // FIXME panics when attestationsUsedByAnchor > attestedTransferLimit - // since this should never happen, maybe ok? - return attestationLimit(anchor) - attestationsUsedByAnchor[anchor]; - } - - /// ############################################################################################################################### - /// ############################################################################################## FLOATABILITY - /// ############################################################################################################################### - - function updateFloatingAuthorization(Authorization startAuthorization, Authorization stopAuthorization) public - onlyMaintainer() { - floatStartAuthorization = startAuthorization; - floatStopAuthorization = stopAuthorization; - emit FloatingAuthorizationChange(startAuthorization, stopAuthorization, msg.sender); - } - - function floatAll(bool doFloatAll) public onlyMaintainer() { - require(doFloatAll != allFloating, "ERC6956-E23"); - allFloating = doFloatAll; - emit FloatingAllStateChange(doFloatAll, msg.sender); - } - - - function _floating(bool defaultFloatState, FloatState anchorFloatState) internal pure returns (bool floats) { - if(anchorFloatState == FloatState.Default) { - return defaultFloatState; - } - return anchorFloatState == FloatState.Floating; - } - - function float(bytes32 anchor, FloatState newFloatState) public - { - bool currentFloatState = floating(anchor); - bool willFloat = _floating(allFloating, newFloatState); - - require(willFloat != currentFloatState, "ERC6956-E23"); - - if(willFloat) { - require(_roleBasedAuthorization(anchor, createAuthorizationMap(floatStartAuthorization)), "ERC6956-E21"); - } else { - require(_roleBasedAuthorization(anchor, createAuthorizationMap(floatStopAuthorization)), "ERC6956-E22"); - } - - floatingStateByAnchor[anchor] = newFloatState; - emit FloatingStateChange(anchor, tokenByAnchor[anchor], newFloatState, msg.sender); - } - - function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) - internal virtual - override(ERC6956) { - bytes32 anchor = anchorByToken[tokenId]; - - if(!_anchorIsReleased[anchor]) { - // Only write when not already released - this saves gas, as memory-write is quite expensive compared to IF - if(floating(anchor)) { - _anchorIsReleased[anchor] = true; // FIXME OPTIMIZATION, we do not need - } - } - - super._beforeTokenTransfer(from, to, tokenId, batchSize); - } - function _beforeAttestationUse(bytes32 anchor, address to, bytes memory data) internal view virtual override(ERC6956) { - // empty, can be overwritten by derived conctracts. - require(attestationUsagesLeft(anchor) > 0, "ERC6956-E24"); - - // IERC6956ValidAnchors check anchor is indeed valid in contract - require(data.length > 0, "ERC6956-E25"); - bytes32[] memory proof; - (proof) = abi.decode(data, (bytes32[])); // Decode it with potentially more data following. If there is more data, this may be passed on to safeTransfer - require(anchorValid(anchor, proof), "ERC6956-E26"); - - super._beforeAttestationUse(anchor, to, data); - } - - - /// @notice Update the Merkle root containing the valid anchors. Consider salt-leaves! - /// @dev Proof (transferAnchor) needs to be provided from this tree. - /// @dev The merkle-tree needs to contain at least one "salt leaf" in order to not publish the complete merkle-tree when all anchors should have been dropped at least once. - /// @param merkleRootNode The root, containing all anchors we want validated. - function updateValidAnchors(bytes32 merkleRootNode) public onlyMaintainer() { - _validAnchorsMerkleRoot = merkleRootNode; - emit ValidAnchorsUpdate(merkleRootNode, msg.sender); - } - - function anchorValid(bytes32 anchor, bytes32[] memory proof) public virtual view returns (bool) { - return MerkleProof.verify( - proof, - _validAnchorsMerkleRoot, - keccak256(bytes.concat(keccak256(abi.encode(anchor))))); - } - - function floating(bytes32 anchor) public view returns (bool){ - return _floating(allFloating, floatingStateByAnchor[anchor]); - } - - constructor( - string memory _name, - string memory _symbol, - AttestationLimitPolicy _limitUpdatePolicy) - ERC6956(_name, _symbol) { - attestationLimitPolicy = _limitUpdatePolicy; - - // Note per default no-one change floatability. canStartFloating and canStopFloating needs to be configured first! - } - - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC6956) - returns (bool) - { - return - interfaceId == type(IERC6956AttestationLimited).interfaceId || - interfaceId == type(IERC6956Floatable).interfaceId || - interfaceId == type(IERC6956ValidAnchors).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-6956/contracts/IERC6956.sol b/assets/eip-6956/contracts/IERC6956.sol deleted file mode 100644 index a171a930ece2f5..00000000000000 --- a/assets/eip-6956/contracts/IERC6956.sol +++ /dev/null @@ -1,165 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.18; - -/** - * @title IERC6956 Asset-Bound Non-Fungible Tokens - * @notice Asset-bound Non-Fungible Tokens anchor a token 1:1 to a (physical or digital) asset and token transfers are authorized through attestation of control over the asset - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0xa9cf7635 - */ -interface IERC6956 { - - /** @dev Authorization, typically mapped to authorizationMaps, where each bit indicates whether a particular ERC6956Role is authorized - * Typically used in constructor (hardcoded or params) to set burnAuthorization and approveAuthorization - * Also used in optional updateBurnAuthorization, updateApproveAuthorization, I - */ - enum Authorization { - NONE, // = 0, // None of the above - OWNER, // = (1<0) of the anchored token - */ - event AnchorApproval(address indexed owner, address approved, bytes32 indexed anchor, uint256 tokenId); - - /** - * @notice This emits when the ownership of any anchored NFT changes by any mechanism - * @dev This emits together with tokenId-based ERC-721.Transfer and provides an anchor-perspective on transfers - * @param from The previous owner, address(0) indicate there was none. - * @param to The new owner, address(0) indicates the token is burned - * @param anchor The anchor which is bound to tokenId - * @param tokenId ID (>0) of the anchored token - */ - event AnchorTransfer(address indexed from, address indexed to, bytes32 indexed anchor, uint256 tokenId); - /** - * @notice This emits when an attestation has been used indicating no second attestation with the same attestationHash will be accepted - * @param to The to address specified in the attestation - * @param anchor The anchor specificed in the attestation - * @param attestationHash The hash of the attestation, see ERC-6956 for details - * @param totalUsedAttestationsForAnchor The total number of attestations already used for the particular anchor - */ - event AttestationUse(address indexed to, bytes32 indexed anchor, bytes32 indexed attestationHash, uint256 totalUsedAttestationsForAnchor); - - /** - * @notice This emits when the trust-status of an oracle changes. - * @dev Trusted oracles must explicitely be specified. - * If the last event for a particular oracle-address indicates it's trusted, attestations from this oracle are valid. - * @param oracle Address of the oracle signing attestations - * @param trusted indicating whether this address is trusted (true). Use (false) to no longer trust from an oracle. - */ - event OracleUpdate(address indexed oracle, bool indexed trusted); - - /** - * @notice Returns the 1:1 mapped anchor for a tokenId - * @param tokenId ID (>0) of the anchored token - * @return anchor The anchor bound to tokenId, 0x0 if tokenId does not represent an anchor - */ - function anchorByToken(uint256 tokenId) external view returns (bytes32 anchor); - /** - * @notice Returns the ID of the 1:1 mapped token of an anchor. - * @param anchor The anchor (>0x0) - * @return tokenId ID of the anchored token, 0 if no anchored token exists - */ - function tokenByAnchor(bytes32 anchor) external view returns (uint256 tokenId); - - /** - * @notice The number of attestations already used to modify the state of an anchor or its bound tokens - * @param anchor The anchor(>0) - * @return attestationUses The number of attestation uses for a particular anchor, 0 if anchor is invalid. - */ - function attestationsUsedByAnchor(bytes32 anchor) view external returns (uint256 attestationUses); - /** - * @notice Decodes and returns to-address, anchor and the attestation hash, if the attestation is valid - * @dev MUST throw when - * - Attestation has already been used (an AttestationUse-Event with matching attestationHash was emitted) - * - Attestation is not signed by trusted oracle (the last OracleUpdate-Event for the signer-address does not indicate trust) - * - Attestation is not valid yet or expired - * - [if IERC6956AttestationLimited is implemented] attestationUsagesLeft(attestation.anchor) <= 0 - * - [if IERC6956ValidAnchors is implemented] validAnchors(data) does not return true. - * @param attestation The attestation subject to the format specified in ERC-6956 - * @param data Optional additional data, may contain proof as the first abi-encoded argument when IERC6956ValidAnchors is implemented - * @return to Address where the ownership of an anchored token or approval shall be changed to - * @return anchor The anchor (>0) - * @return attestationHash The attestation hash computed on-chain as `keccak256(attestation)` - */ - function decodeAttestationIfValid(bytes memory attestation, bytes memory data) external view returns (address to, bytes32 anchor, bytes32 attestationHash); - - /** - * @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to burn - */ - function burnAuthorization() external view returns(Authorization burnAuth); - - /** - * @notice Indicates whether any of ASSET, OWNER, ISSUER is authorized to approve - */ - function approveAuthorization() external view returns(Authorization approveAuth); - - /** - * @notice Corresponds to transferAnchor(bytes,bytes) without additional data - * @param attestation Attestation, refer ERC-6956 for details - */ - function transferAnchor(bytes memory attestation) external; - - /** - * @notice Changes the ownership of an NFT mapped to attestation.anchor to attestation.to address. - * @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation. - * - Uses decodeAttestationIfValid() - * - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited. - * - Matches the behavior of ERC-721.safeTransferFrom(ownerOf[tokenByAnchor(attestation.anchor)], attestation.to, tokenByAnchor(attestation.anchor), ..) and mint an NFT if `tokenByAnchor(anchor)==0`. - * - Throws when attestation.to == ownerOf(tokenByAnchor(attestation.anchor)) - * - Emits AnchorTransfer - * - * @param attestation Attestation, refer EIP-6956 for details - * @param data Additional data, may be used for additional transfer-conditions, may be sent partly or in full in a call to safeTransferFrom - * - */ - function transferAnchor(bytes memory attestation, bytes memory data) external; - - /** - * @notice Corresponds to approveAnchor(bytes,bytes) without additional data - * @param attestation Attestation, refer ERC-6956 for details - */ - function approveAnchor(bytes memory attestation) external; - - /** - * @notice Approves attestation.to the token bound to attestation.anchor. . - * @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation. - * - Uses decodeAttestationIfValid() - * - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited. - * - Matches the behavior of ERC-721.approve(attestation.to, tokenByAnchor(attestation.anchor)). - * - Throws when ASSET is not authorized to approve. - * - * @param attestation Attestation, refer EIP-6956 for details - */ - function approveAnchor(bytes memory attestation, bytes memory data) external; - - /** - * @notice Corresponds to burnAnchor(bytes,bytes) without additional data - * @param attestation Attestation, refer ERC-6956 for details - */ - function burnAnchor(bytes memory attestation) external; - - /** - * @notice Burns the token mapped to attestation.anchor. Uses ERC-721._burn. - * @dev Permissionless, i.e. anybody invoke and sign a transaction. The transfer is authorized through the oracle-signed attestation. - * - Uses decodeAttestationIfValid() - * - When using a centralized "gas-payer" recommended to implement IERC6956AttestationLimited. - * - Throws when ASSET is not authorized to burn - * - * @param attestation Attestation, refer EIP-6956 for details - */ - function burnAnchor(bytes memory attestation, bytes memory data) external; -} \ No newline at end of file diff --git a/assets/eip-6956/contracts/IERC6956AttestationLimited.sol b/assets/eip-6956/contracts/IERC6956AttestationLimited.sol deleted file mode 100644 index 6667c3751af20b..00000000000000 --- a/assets/eip-6956/contracts/IERC6956AttestationLimited.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.18; -import "./IERC6956.sol"; - -/** - * @title Attestation-limited Asset-Bound NFT - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0x75a2e933 - */ -interface IERC6956AttestationLimited is IERC6956 { - enum AttestationLimitPolicy { - IMMUTABLE, - INCREASE_ONLY, - DECREASE_ONLY, - FLEXIBLE - } - - /// @notice Returns the attestation limit for a particular anchor - /// @dev MUST return the global attestation limit per default - /// and override the global attestation limit in case an anchor-based limit is set - function attestationLimit(bytes32 anchor) external view returns (uint256 limit); - - /// @notice Returns number of attestations left for a particular anchor - /// @dev Is computed by comparing the attestationsUsedByAnchor(anchor) and the current attestation limit - /// (current limited emitted via GlobalAttestationLimitUpdate or AttestationLimt events) - function attestationUsagesLeft(bytes32 anchor) external view returns (uint256 nrTransfersLeft); - - /// @notice Indicates the policy, in which direction attestation limits can be updated (globally or per anchor) - function attestationLimitPolicy() external view returns (AttestationLimitPolicy policy); - - /// @notice This emits when the global attestation limt is updated - event GlobalAttestationLimitUpdate(uint256 indexed transferLimit, address updatedBy); - - /// @notice This emits when an anchor-specific attestation limit is updated - event AttestationLimitUpdate(bytes32 indexed anchor, uint256 indexed tokenId, uint256 indexed transferLimit, address updatedBy); - - /// @dev This emits in the transaction, where attestationUsagesLeft becomes 0 - event AttestationLimitReached(bytes32 indexed anchor, uint256 indexed tokenId, uint256 indexed transferLimit); -} diff --git a/assets/eip-6956/contracts/IERC6956Floatable.sol b/assets/eip-6956/contracts/IERC6956Floatable.sol deleted file mode 100644 index 6c345fdc25fcac..00000000000000 --- a/assets/eip-6956/contracts/IERC6956Floatable.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.18; -import "./IERC6956.sol"; - -/** - * @title Floatable Asset-Bound NFT - * @notice A floatable Asset-Bound NFT can (temporarily) be transferred without attestation - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0xf82773f7 - */ -interface IERC6956Floatable is IERC6956 { - enum FloatState { - Default, // 0, inherits from floatAll - Floating, // 1 - Anchored // 2 - } - - /// @notice Indicates that an anchor-specific floating state changed - event FloatingStateChange(bytes32 indexed anchor, uint256 indexed tokenId, FloatState isFloating, address operator); - /// @notice Emits when FloatingAuthorization is changed. - event FloatingAuthorizationChange(Authorization startAuthorization, Authorization stopAuthorization, address maintainer); - /// @notice Emits, when the default floating state is changed - event FloatingAllStateChange(bool areFloating, address operator); - - /// @notice Indicates whether an anchored token is floating, namely can be transferred without attestation - function floating(bytes32 anchor) external view returns (bool); - - /// @notice Indicates whether any of OWNER, ISSUER, (ASSET) is allowed to start floating - function floatStartAuthorization() external view returns (Authorization canStartFloating); - - /// @notice Indicates whether any of OWNER, ISSUER, (ASSET) is allowed to stop floating - function floatStopAuthorization() external view returns (Authorization canStartFloating); - - /** - * @notice Allows to override or reset to floatAll-behavior per anchor - * @dev Must throw when newState == Floating and floatStartAuthorization does not authorize msg.sender - * @dev Must throw when newState == Anchored and floatStopAuthorization does not authorize msg.sender - * @param anchor The anchor, whose anchored token shall override default behavior - * @param newState Override-State. If set to Default, the anchor will behave like floatAll - */ - function float(bytes32 anchor, FloatState newState) external; -} \ No newline at end of file diff --git a/assets/eip-6956/contracts/IERC6956ValidAnchors.sol b/assets/eip-6956/contracts/IERC6956ValidAnchors.sol deleted file mode 100644 index 7de78697c886e1..00000000000000 --- a/assets/eip-6956/contracts/IERC6956ValidAnchors.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.18; -import "./IERC6956.sol"; - -/** - * @title Anchor-validating Asset-Bound NFT - * @dev See https://eips.ethereum.org/EIPS/eip-6956 - * Note: The ERC-165 identifier for this interface is 0x051c9bd8 - */ -interface IERC6956ValidAnchors is IERC6956 { - /** - * @notice Emits when the valid anchors for the contract are updated. - * @param validAnchorHash Hash representing all valid anchors. Typically Root of MerkleTree - * @param maintainer msg.sender updating the hash - */ - event ValidAnchorsUpdate(bytes32 indexed validAnchorHash, address indexed maintainer); - - /** - * @notice Indicates whether an anchor is valid in the present contract - * @dev Typically implemented via MerkleTrees, where proof is used to verify anchor is part of the MerkleTree - * MUST return false when no ValidAnchorsUpdate-event has been emitted yet - * @param anchor The anchor in question - * @param proof Proof that the anchor is valid, typically MerkleProof - * @return isValid True, when anchor and proof can be verified against validAnchorHash (emitted via ValidAnchorsUpdate-event) - */ - function anchorValid(bytes32 anchor, bytes32[] memory proof) external view returns (bool isValid); -} \ No newline at end of file diff --git a/assets/eip-6956/img/erc6956_concept.svg b/assets/eip-6956/img/erc6956_concept.svg deleted file mode 100644 index c9da78ca982832..00000000000000 --- a/assets/eip-6956/img/erc6956_concept.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO196VIjSbbm/3qKtLp/7pi14vq+tI2NXHUwMDE5XHUwMDAyxFwiQGxim1x1MDAxZcO0XCJASGhhbevnue9xn2y+I0hcYoVcIlx1MDAxNFx1MDAxMkikspro6swqJFxcLvfznfNcdTAwMWT3s/zzj1x1MDAxZj/+7D/d1v78+48/a4+VUvOy2i09/Pk3+vl9rdu7bLfwklx1MDAxOPx3r33XrVxm3tno9297f/+v/6L3XHUwMDA2l+XaY9Bo9/ovv1Vr1m5qrX5cdTAwMGbv+7/471x1MDAxZj/+Ofgz9DndWqVfal00a4NfXHUwMDE4vPT+UUZ4XHUwMDE2/fFOuzX4XFwtJOdWcvH2hsveXG4+r1+r4tV6qdmrvb9SXHUwMDFkTHRX3m6621pVbVTWs+py/arvVt8/tn7ZbFx1MDAxZfSfmoNp9dr48u+v9frd9nXt+LLab/xcXIHQz5N+q9u+u2i0aj36+vztp+3bUuWy/0Q/Y+/f7mVccv7+4/0nj/RJnrnAOauUY1x1MDAxMv9o/fYyXHKQUcZcdTAwMDTCcOdcdTAwMDU3+INFZ7bcbra7NLP/YIPnfW7lUuX6XHUwMDAyXHUwMDEzbFXf31Ov19/f8PD6Zbk1gffScO6x5EqEptCoXV40+jRN41x1MDAwMm2tt5xzKbR9f0uvNthcdTAwMGWOXzWOZvD2XG59+O1G9V00XHUwMDA2P21t+Ov8WaV612ndnK2uPSzdyUb2bVJ4g2c79Y31o7tVrjtcdTAwMTct8cw2i42l8Fx1MDAxYpbrtr+yvbV+WLqSq9nuoz7qXHUwMDE1M3++vv7/ojvXKHVvX3foz1x1MDAxZf1HaIVocVajXHUwMDAyXHUwMDFjXHUwMDE24iFBLnW77YfQRN7Ebjl3x7Jq++GcPyzz9fLFo8v12J9v7/vX3z447slG83ynUcr191x1MDAwYozdnujdjjkqzmDc1Vb18uAw13zkS4f2vpI9urq4r6WP+/LLhfbDfud0s7Zj93dPV1x1MDAxYlvV5iarXHJ/yvBcdTAwMDQmXHUwMDFj92CjsFxczoh+y963Kvkl5/3p+c5cZsZduezrZpOxO3t7f5S9Km2vP+2tTTbuiEjd3VZLL9pcdTAwMDdgXHUwMDE0mlugU0n39nrzsnWNXHUwMDE3W3fN5lx1MDAxZqGZjajEfu2xXHUwMDFmp1xyueMu+uOf2lAwy5mWTJuJ1eHR9v5WR51dbvWLsnJfvdjJNJd0gjpslCqNu24tSSHyKKzmqFx1MDAxMJVccpyWinnDlORGXHUwMDBlK0RhXHUwMDE4tFx1MDAxNdPeXHUwMDE47aA1ozObSiH2u6VW77bUhVx1MDAwMlx1MDAxONWLXHUwMDBlmldK5aRlXHUwMDBlmlm8T+RNLWo5UM7Qh0455ZmNqkUlXHUwMDA0plxi5ZiiXHUwMDE1f6HS+5B4v1x1MDAwYlG71T+4fK5ccr5roFx1MDAxNDfMWss1TFx1MDAxNVND78qVbi6bT0NyMcBcdTAwMDGmuJM7XGZvU6+GT6JcdTAwMTGlTVpe+s2l5uVcdTAwMDWh5s9cbr5KrTtcdTAwMDSo/iXIzdtcdTAwMWL67dBcdTAwMDJUMJdcdTAwMTLG725Uo1+m3b28uGyVmoeheY1cdTAwMDXyYJ4xQNbgNYlA5kpcbpJcXDY5kI9WXHUwMDE0v2+d7YvN9rPbbm7XKvWrj/Caj8CYfVxixpJxXHUwMDE1cKWMgjhoyMUwiqW2gTWKS+edV9579Vx1MDAxOVx1MDAxOMfzXHUwMDFhZVx1MDAwMmOdZoxDjL1cdTAwMGKJY1x1MDAxOL9cdTAwMWW0R1pcdTAwMGZlormTUfxy5lx1MDAwNDSNdSpcdTAwMDXAxZWjs7OGXFzKPD+vN9RRv5G/eJJhXHUwMDAwd/OZ5pNr9ct3ailb36q4YvGiN1x1MDAxYoRcdTAwMGZcdTAwMTZjblxi7/VL3X72XHUwMDEynKF1XHUwMDExfa3Wqia80iz1+svtm5vLPj5vt33Z6seOu0Qmt1ErjUBcdTAwMTEjJ752S8Nccq//+7/9eFx1MDAxN8TBf7z9+//7W+y7IaXOXHUwMDEzc+XKK6hrXHUwMDE5/m1cdTAwMWUoMHPwYulcZodcdTAwMWRgOm04XHUwMDFiMFx1MDAwZaPFmNCMw4r5v1xyfZZmwnpcdTAwMGWRgtBcdGNTh+MscIY52DujmdaG6fB4sD7ScSaAI600XHUwMDAzoNLGXHUwMDEzPNCe8OhcdTAwMDA968w7m3hcdTAwMTlPOsWdXHUwMDE1VkOFXHUwMDBiLVKHU4FXRkphXHUwMDA1ZsjwvYaHs1xcXHUwMDE5i5U1QjAptUtcdTAwMWTPXHUwMDA2Wlx1MDAxYVxy2+FcdTAwMWT9ilx1MDAxON5cZqO5sFJ6xiSmx1NXT/KAS3whXHUwMDA2r4Vp4YZcdTAwMTYvg6VV2oNzWe+AXHUwMDAyL1NXT4qAXHUwMDBiXCJcdTAwMDOcK23hK4XHXHUwMDEzgXbYVKZcdTAwMTXXynrGJ1x1MDAxOFxyK0aSx5WAnLnwaDrwQjHNtYVXXGLvLHUrpFx1MDAwZUBRICxcXGjv7butpMdcdTAwMDfMcc1pp6Sx4DSpo5lcdTAwMDA6XHUwMDEx31x1MDAwNsIslFx1MDAxZt5cdTAwMDdHq1x1MDAwMM3OXGJcdTAwMTJcdTAwMTCY9HXTgVGCfkHYwYZcZolcdG26XHUwMDAzuZXOQVx1MDAxYnEr0mcnXHUwMDAyQdKLlcOXglf6t+FNXHUwMDE3XHUwMDEwXHUwMDE0XHJcdTAwMDFcdTAwMTKAXHUwMDFhM3ZcdTAwMDKps5iTI6lzXHUwMDA08mEpJpRBXHUwMDAxWG7hgGuXLsRcIrBkTFx1MDAxONZcdTAwMWPAsGxIpUhF++S5slx1MDAwNipCeTlcdTAwMDHGmGdcdTAwMDYqxYFVecmGpFx1MDAwZUuroVx1MDAxOaBMvFx1MDAwNS1cdTAwMTbapI6H3zDYVXxbXHUwMDBl4Ye/PzSeXHUwMDBiMGOutfCC5skmXHUwMDE5XHUwMDBm+2ZpPIE9XHUwMDE2Q5tcdTAwMDFcdTAwMWKMdbBCK5JzqJVJRmOGLD5cdTAwMTgkuINcdTAwMWVcdTAwMWFO81x1MDAwMJZaa1x1MDAwZdZcdTAwMDDUpqs7aHdviIs6I5mTw1uhXHUwMDA1XHUwMDFjXHKvJJBcZiE3PF27c4KskVhxJWA1vFx1MDAxOVx1MDAxZU9cdTAwMDa0T9xj+72zSqauXHUwMDFk1DuGguZkXHUwMDEyptg5ySPfVkNKXHUwMDAwWKh+MCaV+n0xXHUwMDFls570p9T4a0ijKFx1MDAxZIBoWehjfFx1MDAwMS9N+mhg35ZDXHJDqjyUN+eRnVx1MDAxNfCKlOBANPRd+nBcIsB34Yq2XHUwMDE2Slxi+1x1MDAxMpVcdTAwMTRccqrIXHUwMDE0lkIw8lx1MDAxZdLGy3DAllx1MDAxY0XGXGYkWVx1MDAxOT2kplx1MDAxNHBcdTAwMDNTXHUwMDA2OVx1MDAxMbSK2JnUXHUwMDAxVVx1MDAwMPUjSVLhclx1MDAxOOmGvrD0XHUwMDAxXlx1MDAwM1WEblx1MDAxZohN6nDgXHUwMDAySurB+nGokOHvXHUwMDBi34ZcdTAwMTmB5YWQMytF+tf1gXOC1FxuxlSQXHUwMDE1XHUwMDFi0Vx1MDAwM9BPkHBoXGJ8Z5s+nCFRVsRUwHx0RPSmVytcdTAwMTmoSa9cYrpcdTAwMWO0XHUwMDEyXHUwMDAzusi3XHUwMDA118BcdTAwMWXhi0KShE+FXHUwMDFhzDOANNhdXHUwMDA2XHUwMDE1XG68qyG1rFx1MDAwMFx1MDAxZPqqmCPHzlx1MDAwYp9qNKD2gCRGRkjAXHUwMDFhXHKpPfJdofcg7/A0IX2pm1x1MDAwYpNBxFx1MDAwMqtnIdPDi6egpdiAWHMn6LQ0bTBPOk/QziroXHUwMDBlPsxcdTAwMDTwRYVcdTAwMDO/k5Jb+szUL0pKgEkzUFx1MDAwMmD5XHUwMDEw5shOWMlcdTAwMDfmjvRouqBwkChsKeko6DtlXCJcdTAwMWNcdTAwMGaCwiU+XHKEXGKULH3hoEI10WlcdTAwMDBcdTAwMWOS71wiSkpcdTAwMTJFhpLS3ijY0Fx0VLJccjiUsVwikEFYRMQ6SrxcbnZvPLP0XHUwMDE1UllcdTAwMTRcdTAwMGYgaJpkzlhDXGZcIsIspIfgWJpcdTAwMWPsN0ulXHUwMDAyXHUwMDEwYqv9wIGFdIHZXGbJXHRcdTAwMTFcdTAwMWaoXHUwMDA2zNvB+4RPkFx1MDAwZVxuUFx1MDAwYmU1yVx1MDAxZFx1MDAxMUdcdDZcdTAwMTVhZlx1MDAwMl+XXHUwMDBiclg5XGZ4quRBh8JcdTAwMTOk3WCKw1CCXHUwMDBiXHUwMDBm80aliN87onlYjvTlg8M8UPFcdTAwMTLmXHUwMDA3LsvQ5pI+JNdcdTAwMDLrQDzKp+uoXHUwMDE0XHUwMDFkXHUwMDAwXHUwMDA3XHUwMDAzI8EjXHUwMDAwJbfO6lThS3Jccv9cYv+ddNhTazYvb3ux5z0ypI2j5z3EoGExQs5M6nnPfft+9fn0qNraK1xcXHUwMDFj3dvM/uXJ/d2i32Np+FNkueGIXHUwMDAyOfz9mPrlXHUwMDFlXHUwMDBiuFx1MDAwN6+0XG6kXHUwMDBldMKGjnVnfW5cdTAwMGJcbktcIqhIX0pjzPtcIryd+0BDO+hcdTAwMTn4XHUwMDA38K3hwMj32f48+HEwXHUwMDEx5N28XHUwMDFmjiRcdTAwMWP8bO8+sPvt/OOz7p9cdTAwMWafnj6K6noxfK5zt7FZ6lxcZTtHXHUwMDE3ZfG82eo86otSdoGPdsdiIOnAXHUwMDEzpk4mXHUwMDAxgO5cdTAwMTah0ZWaWP6vT4+fTHl7o7Z9dpI7XbY92dG9RZd/I1x1MDAwM1LhXG6+XHUwMDFkSJ1Ww/IvjVx1MDAwN0HDOFx1MDAwM2xcYqn0Z+T/Pyq1qqqWRmVfQPQ9hy3xsPFcdTAwMWFKOEb2XHUwMDFk4Vx1MDAxNNrS4k109lx1MDAxYVx1MDAxNX38VFx1MDAxMHE2XHUwMDBiLPnzPfL8bVx1MDAwZS8ziftNz8hOT2nwksH+zotGrFx1MDAxZChcdOndKdCuVlx1MDAwZawvnph85uzq8rRfPL1+PLdcdTAwMGKPdkaHPNBrcNylNVx1MDAxMWtcdTAwMDefJOB03MDgkinBrJ1cdTAwMGLauVxmpFRcXDNiSLT9MVdcdTAwMWMgmVx1MDAwMkbO0iUmaKZcdTAwMGJdRb7iXcPhYJCOtDvKb7z/crwnbzg9o1s9K8DbkCGJXHUwMDAyXjm4uDSjiVx1MDAwMd/IXHUwMDFmP1+3j9pcdTAwMWL3YuPser/TPthXXHUwMDFmus78UsDD+Vx1MDAwM+DhqEunQmFYr3hcdTAwMTd0XHUwMDE2xVx1MDAwNN6nKWZgPsadXHUwMDA38DuZgdNoLDfy/UApXHUwMDA0d1x1MDAxNcAvNeRjYVwiTo1cdTAwMDZqXHUwMDE5UFs43lwi9UbzXHUwMDFi7r9cdTAwMWHumeRcdTAwMWSnZ2SvZ1x1MDAwNneeXHUwMDE4vcBcdTAwMTlcZlx1MDAwNVx1MDAwNbRMXHUwMDBl9/v13EVp76GU0etcdTAwMWR11dstne9cdTAwMTTMosNdqUBcdTAwMWFcdTAwMDDace6cj5p3cH3BtDOGTr6k/lxcUGaidXeBXHUwMDA1XHUwMDEzV1x1MDAwM51cdTAwMDLtXHUwMDEyw+XxXHUwMDE2XHUwMDA214uOt4VnUo/ELzhcdTAwMWGBmW+sLzzWXHUwMDEzd/v1xeF9nlx1MDAxOdLVmHMryUDmJ1x1MDAwZjd8rO3VXHUwMDFmt05qdzfVm+5Sd+uhfLq+sug4h1mn4CCpvfXGhVbjXHUwMDA16MpcdTAwMDR0klx01Sukk4bPh8eDuDHHXHUwMDFk3U5IuE9KxUFdXHUwMDA3wkq6jFFcdTAwMTBcdTAwMDXlzEisoVdcdTAwMTa8z3377YtcdTAwMGb25FxypyczutczwjuGM0l4x1x1MDAxYXO6cZnccT/Jbub3187valx1MDAxN5ut50y/fGOXslx1MDAxZjqm+8roYq1cdTAwMDJvKVxiyzvvRPSUmrmAokawTlx1MDAxMmD6JI9XXHUwMDE1U6vrUbhrXHUwMDFmXHUwMDE4q5VcdTAwMDOTYiBuPC7fQlx1MDAwNFx1MDAwMsTDKK244GzouPyVx1x1MDAwYu1AU0L3Z1x0cP9t0fxcdTAwMTdcdTAwMGY85DZQRlhFXHUwMDEwt4yipod1XHUwMDAwVIRkXHUwMDE0/Owp7I+l31wiS1x1MDAxOTjDPeRXXGLufWQ8XHUwMDE2MC0ocJ5cdTAwMDZcdTAwMWSETaWHWfHAa1x1MDAwZcZcdTAwMDFcblx0qFxmX/yKwEkpXGajyFx1MDAxZkr9SVx1MDAwZmSSLKA4XHUwMDExXHUwMDAzhcfwvb2IXHUwMDA0vJG8W2cpXHUwMDFjRttcdO5WKZCJoiyFt1xuiFxyXTC8zE/iu1x1MDAxYUbXmzCMMv3ul1x1MDAwNnRcXGpjPFxiXHUwMDAxXHUwMDFlJ4Z3JFx1MDAwMFx1MDAxMD3YXHUwMDAyXVx1MDAxOOGru9RcdTAwMTFZYChg0Ugoerzf2WhcdTAwMDBcIlx1MDAwNVx1MDAxMdFtOL6CoSiGWdE6wVx1MDAxMvNIrMFyOSEmV/O3ZqVeydur3G6n2bm9vcpcdTAwMTftQT9Bzc8hjeRj8edYUVx1MDAxObBBXGIj/Fx1MDAxZlxuQ41oeiFcdTAwMDPwPZg8TdEqnzugXHUwMDFke1x1MDAxZKlcdTAwMThQXHUwMDA0k6NcdTAwMTT0nlchevbO7WxgjKNJek1RXHUwMDFiYbvz8zpSa9BcdTAwMDTL0jy5b22/mNpcdTAwMWVgx1xuSFx03axgzoW0Q9o041x1MDAwNoFcdTAwMWTa4zVvSFx1MDAwNFJcdTAwMDfk8FxcKJpcdTAwMTfC5Z0yKjqc4aT7XGbFck5cdTAwMTD6XHUwMDA2XHUwMDExxNtcdTAwMDRFVVx1MDAwMzDgRlx1MDAxMUY6XCKhqeZccsbD0rmwXHUwMDA2l6Iw06FIlYxcblx1MDAxOJ1EWlx1MDAwNoVrtfPpgaEyoFx1MDAxMG0rYDqk0yxiLaFLraBAJIylwJhTp1x1MDAxN3BLRohcImXwp1x1MDAxY1x1MDAwZVx1MDAwN8vYwFx1MDAwZlx1MDAwZUlcdTAwMDUgZ4bi9CdSzGNznr1NTHmmyHxcdTAwMDWn4Fx1MDAxZCppynl//2Dl+jy/vCQqrW7v9vjp+eHhZvFz/Fx1MDAwNC0wN57i/L30UeVcZpKuIW6Cw0BiWz+VXHUwMDFjNFY5c0VcdTAwMTd1mlKvgUtcdTAwMTXOXHUwMDAwXG5pZ1x1MDAxZlCEkXKDSFx1MDAxZVx1MDAxNnOmTlx1MDAxN6xcdTAwMTS+m3aF9pdcYvXgLplcXGCRrFxmXHUwMDA3/6TeXHUwMDA1XdV3XHUwMDBiXHUwMDA3h6rbzl2Xzp86vtmpPS62XHUwMDBmmXGQXjBLcDtcYjIzUenl3Fx1MDAwNFCkkq594UWCOM/FizTg01x1MDAxNFPrKJFcYmYjzouUXG6MlktKYpPg1Wrk7lcwUsA6fJxcdTAwMWYvuTu1vM/zi8zWTnmHburrp7Wq/6ZccvM/XHUwMDE1hiZU0ISKwls5XHUwMDFizpzJkGqC6+DgKUpcdTAwMDH1NIFPpym72tCNLlWVsHyYN7CAklx1MDAxMZjXQkhccq4yQU5cdTAwMTSFdlOSLtxcdTAwMWOKi41cXEljflxu3qjjoFx1MDAwZuT1pVx1MDAwZSdEwIm5XHUwMDAwOLBcdTAwMTLDNFx1MDAwNGbBQY7hyFx1MDAwMn1Cpae7ZVx1MDAwML7AwoeVSjhcbipcdTAwMWXOn1x1MDAxM3TJg4Xzjlx1MDAxMKQmXGKXXHUwMDA1yaCEXHUwMDE2XHUwMDE4XGbLIKBeRGhcYlx1MDAwZiyDUYNcdTAwMDNcdTAwMGI/XHUwMDE2fD01otfAJ9Z0XHUwMDBlXHUwMDAwJkfR8cMsiVx1MDAxMko47bpXms6oZ3XDh81cdTAwMWRTaEBRvtFcdTAwMTRcdTAwMGVi7+pova/Y2o44WC/4y/WNXHUwMDAzs1JbdFx1MDAwNzFcdTAwMDPOXHUwMDE3gMk6aHPrgYZIwFx1MDAxZWQ1ILtcdTAwMGXX3TtcdTAwMTD2OTqIUlBcdTAwMDaV8Vx1MDAxYU6eZbFcdTAwMTTEXHUwMDE5SlFzSpBcdTAwMTgzx0fiVWF1PfhUalx1MDAxMM+3XHUwMDFl/zXun1xuKPWFquTA3zA6kn6S8VQ7QYPuQpVCWaZH2mfwnoBcdTAwMDJRrFx1MDAxMoaTryeiXHUwMDAzalAxzYmYkulPXHUwMDFm0FxysjThfHjKzeJi2DKMimCq7Vx1MDAxMibQUFvwd+DokWxcdTAwMGZ7gPSVpaLaR1TlKT1cdTAwMWJcdTAwMDC+WFx1MDAwMIeMsjnAvqK5XHUwMDBmcFx1MDAwMVx1MDAxZD5cdTAwMGVcdTAwMTCQXHUwMDFlLpTx6Vx1MDAxOVVcXJBLiVx1MDAwNdSUkIl1XHUwMDFhnqCTVFx1MDAxNokxrK/A9/bTns5cctffXHTfwrjES1x1MDAxOLqCYZxcIjom1r5Xu/t7j7X7pa3a2naPXHUwMDFkiWpz4yEpfDJF+0b9rDlyaEPEhnFwXHUwMDFimElKgYxwaNhtRZGkimyjVmMuXuuD51x1MDAxM2VeQDhATqDiwYvoMH5U/UJU6dSB8oyE8mr06pVTVlx1MDAxMFi2S7uM2Tnb2MuX7kU+q4qnZ/xiU663Nv6d1G9cdTAwMTRcdTAwMTVcdTAwMTFcclx1MDAxY315tqVcdTAwMWVop1x1MDAwN+mHXHUwMDFhvJpcdTAwMGafxit4U9YzZZV1XHUwMDA2tDGd+SZcblx1MDAwZT1cdTAwMTkqtDBcYtsykkK0VDir/VPcbVxmdaP7XHTHrJhcItXoeNVcdTAwMWZcdTAwMTTb+unuvlsrXHUwMDE1l1x1MDAxYvl89TApXHUwMDE2c4G0XHUwMDA3JfDAZDA4Pt6ISM08TjdNnlLsQaacZzL5/OizyoPBWYHzXHUwMDAz41x1MDAwNvfGY/ljko1cdTAwMWOju1xc+FxmgzR4IeOuclx1MDAwNadiXG5pkVx1MDAxYv/22uOXnd0n7fLg5ZFccp5cdTAwMTLp41x1MDAxMlx1MDAwYkN5RCOZXHUwMDE2YPtcdTAwMTZMcnJH7dJuXHUwMDE01nf2XHUwMDFhe0f99W2+28qvWP5cdTAwMWJQXHUwMDA1ZVx1MDAwMkrzdlx1MDAxNJBpXFzUT4NcdTAwMTOvPHfE+1x1MDAxONxknpxY9Vmwgy96byibn1x0b3VcXGIh3qK8XHUwMDAxgfOYraRcXMdcdTAwMTGw06mgkWHGM2uwf/1ZcVJBQ1xmmSS+gkvKwJymXGba+Um196wvXHUwMDFhl6JS4ftP9vSgepdZePF1TFx1MDAwNFYqUERcdTAwMGbSIaKHxZSiXHUwMDBl50NcYmmEsC45Lfaz0itcdTAwMTmnTzI/n7jcQGNcdTAwMDIqhCRcdTAwMWRcdTAwMDU1w7yO5Fx1MDAwNkqFV1VSObTFqEOosaKajD+d7eB7hCabUoeQPXIhgyBcdTAwMDA4w+v8Vo9QJS7PUD3CZq0+jIbZVCNcZs/uQ8yR82RjQrUqyNWanDluXFyoKuOieWnLvna69Sgy14eNRb95zHjrXHUwMDAz55X7icdcYlx1MDAxYYFcdTAwMTGmrZaeXHUwMDA0T3o3v1M/QU5l6OExyTxcXFNcdTAwMTmW0KOieISvojhlJE2Px29K+KnMnMTto2d046akhElcdTAwMDY1+eqVc6Kg3E3OXHUwMDA2W/XGbvepu37erbdWtK1cdTAwMTX3bPlg4Vx1MDAwMcyhgLHyjqSQ0Vx1MDAwMdFwKlx1MDAxZVx1MDAxNlx1MDAxZT7+XHUwMDBievHnXHUwMDFjq0xcYlx1MDAxMzjpwUipYlx1MDAxZDzRuLguwYbEYPTgSFCIomQ2/th+MeypN1RcdTAwMDXm7Vx0UbpcdTAwMTRzunRwsFx1MDAxYV/Yl1x1MDAwZf/i1+UrXGbpy7w+ZEKlTIzdobBcdTAwMDfKXHUwMDFim5zPXts91rxfPl7Onm1tXFzkXHUwMDFllrN76nrhXHUwMDAxaFx1MDAwNmXzKGZcdTAwMDZcdTAwMTRLqvdcbqevJtQrwJNcbq9cYmnZJ3PjUlx1MDAxMVxiXHUwMDFmWHmmJIx2nFx1MDAwMZV0qqeUo8pyXFxhsiNnL3BD4I8xmVx1MDAxMFj5bUHnZkFcdTAwMTO37+XVkZ2bkVx1MDAwNVx1MDAxNckl9qlcdTAwMTAs1XTzk4cv9fdLRVvJP3T2no/9/eamUUc3nYWHsINcdJVcXFFcdTAwMTFDTTVcdTAwMTmHWTD0fdiEik9Vqlx1MDAxOY9gXHUwMDFiR3pcdTAwMDWPgnTQXHUwMDA1xkr1XHUwMDE3NZM7y+uF/Xg7qYfePXfD+DKTXHUwMDBmWcYxuKJ4YkWhwFx1MDAxM8Pqsah2O2XRWFm+uMg+lq6fj9av1lx1MDAxNlx1MDAxZVbW0IWy5lx1MDAwNjaQ0+HfXHUwMDEwrJxcdIw3ZlAtki7m54YqTlF/L1x1MDAxZkPV8UKh4eGYVlxupYKjXHUwMDBiSaVChqOXXHUwMDEyVIN1cJn+XHUwMDAxzH1cdTAwMWLGT9V4Sdo+ejKjOzcjw1x1MDAxOL5jXHUwMDFmqfHCLJU0n4Lbblx1MDAxZp1cdTAwMWOtrDyudM/36yuP/ijP18++MCbso86l0jbgQlFhbKNtuHzKgNzSveOgXHUwMDE0M1fCaTvPwHRmWFxmbGNso6N6oOFsur+ebcxcdTAwMWOuLq/vXHUwMDE0tlxua6eLYybDk/pcdTAwMTDgpE2uouathvqdXHUwMDAyb8VcXOk4c7i/qsxcdTAwMGXjm2v6WJw1z38t3nxcdTAwMWHcwOdcdTAwMDZlLfBob1348uM1XHUwMDFi21x1MDAwN1JRyFx1MDAxY1x1MDAwNSQrNkeTyX2ghTV0hUiRxzbGZGK7yCPxXii8g6mRu1x1MDAxMc6VXHUwMDFiXHUwMDE05k6LwXzq5c76Z+f5TrO5tlncfPI3x+V3waZptfYya6ZTXFy9riyXsqJ0+Xil7lx1MDAxNqvZk6BbXHUwMDE24jEvyV3hqIZcdTAwMTRI/89/x2JcdTAwMTirn7C8X1x1MDAwMWlMaiyGxyZ0jVx1MDAwM7KTznlcdTAwMGY/amIkXHUwMDFmr+RbleWcOqz2t+qyom5E9nR30ZHMXUDxNtT4htJcYm3kkp5cbiaBQcCkXG6tKG9mfj6lXHUwMDE4XFzBS01Zv1TASceEU0tGUfxSQrVQp46RYGpNfUjgXHUwMDFlLzCQ3z7jn2//9iZBOVe1hcv9hqlVttqGN5ZX2e1eaFohuZ6uReN1/vmylF85Y2dcdTAwMTlzXFzaeF67lrNo0bh5Wrpu9eSKObquMbu+srl227mYwbi1m6eDle1W46ldqObLu/J6u1I+mmzckS2ad6RE+Fxub4R9a052ZYpjqcyd6d/YtdWb7u5GabOi9ONmPymrblF0iFSg3oZcdTAwMDLBX1x1MDAxZVx1MDAxN1EhLFx1MDAxMFx1MDAwNnZBcFAjpbWf39WsdFAhzlx1MDAxMU9lnvJ84lx1MDAxY2i62oFHQdzbXHRcdTAwMTNcdTAwMTPUR4nFylqVlrH/XHUwMDFiXHUwMDE4e69cdTAwMDMjXHUwMDA0aJqgQNZQ3FOKqS/sLy1vrcaae7pcdTAwMDGKXb6vsPav81x1MDAxYVx1MDAwYtTEcy6TXGJURdevXk1cdTAwMTFDUchUSysrl+flh9OtPX2WO2pWdmZcdTAwMWSQVy31XHUwMDFhtVnfwVx1MDAwNpJapjlmrfaSjUDVXHUwMDA2VJHa4FVccmo9P95uhINWkFx1MDAxMGYq1WhYjK23KvBaUVx1MDAxYjVphuf6XHUwMDA2VEHx4OktXHUwMDFla41aeb+yetO4qF5Vm42DcvP2XCL7XHUwMDFkfTv3i6LETaZnZHvfh/sj/PfUMFx1MDAxZlx1MDAxM2lcdTAwMDGPZ1x1MDAxMFx1MDAwNDb5efb9ytHR88Ptff2ynrmpXHUwMDFlblx1MDAxZmSy+8e/XHUwMDAxzuFcdTAwMTUzS4U/qPvKUH3xV6DDJdaMYyjYSeHnXHUwMDE3baE8tTc0ntKM4WhcdTAwMThcdTAwMTNcdTAwMTNtXHUwMDAxZkD5YeD1iiqZcD1cdTAwMTJtQYVDNFVx/kb6QlwiPXmX6Vx1MDAxOd3fKbE+1onnTCVcIp7yXHTZVJHK225/qdRaOt3IXHUwMDE0norVvr9cdTAwMTNcdTAwMDe5XHUwMDA0vC9KVVx1MDAwYi50YKk5qFx1MDAxZCRrKlx1MDAxZDmNg8WnxpGOUS9HakM5n1x1MDAwZSaKmrYywbVhVFxuzsek40HRXHUwMDA3VDbTXHUwMDE4bzllz4+YdEW6ypvUUqhm53zn9vRw5666en25kt153jtYnVxm6J/xzz/hR49MbVZ+6XhscJ98N+SpgppQU6Sh7ORu28/le32ev9g9rVx1MDAxZrVu1NXql7Uz//DdbkDlNzxcdTAwMDNdlC58NPRcdTAwMDJcdTAwMGVpXHUwMDAyKjlHrYlhXHUwMDExwyxzVu3MpfSB8VB7XFwqqpukYmInNFNUesZcdTAwMTloUCv8aL1cdTAwMTfq8WYsJdP8MmTMXnbH5E9xoVx1MDAxMvtSca6dpO7wXHUwMDEzXHUwMDBi7l7hrHizt2+qVZFd48/V0/Pn3dXFXHUwMDE2XFztXHUwMDAyqjZgqUhcdTAwMWM1uo3caHJcdTAwMDeKJyEsmspaQuvOQXCNp2JejFx1MDAwM1x1MDAxYlx1MDAxNisu4/JOXHUwMDEy3/J2y0llSbCZaUeyv5PcJp1cdTAwMDXqZCbCqViopX6yXHUwMDEzXHUwMDBi7dXxkz7PXHUwMDFkbeeOy8dS2p262Mwsfp6GfqlcdTAwMGbgX1x1MDAxMzVkpD6AXHUwMDAzS9CC/4xR0/O7T/BxeVKx9/COKaV46Nr+L3RcdTAwMGa/u18o5DL4Z7mwc7hf2FqEa/iROY1cdTAwMDVbYkl0lXh5ZynVV05zntc4qj+2l7r+VG2c6syzLzZuqs2Fx5pUXHUwMDE0UiRccrXM5HD4I0lRwsrAK0hcdTAwMWIjXHUwMDFi4sck2H76XHUwMDFhnlhcdTAwMTYlXHUwMDA0WICJelx1MDAxN8e17qTwc/hcdTAwMWZSXGIqcONHgciEo+YsXHRcco7m6r77sUD8i7vvybtHT2Zk497H+yP897TWkrPE3mSOUvykncJY3u8+nd8xVV7aLqxd7JSqrVIhl9R5d4FcdTAwMGXqNFx1MDAxZlx1MDAwNMu/JjUyXHUwMDFmOZDnxlx1MDAwNixsLT91UPdcdTAwMWZCuHLNjMI3lE1cdTAwMTmCa1x1MDAxNJ9cdTAwMDJqXHUwMDA0TNSxX2Aox+MzZChDfCPFMvbbsbYwRFS/wlx1MDAxNmJcdTAwMTZcdTAwMWayfoIld/oyimkh1Fx1MDAxNNfO7vL6TDyIk5o5qZxVTPZ22XbXP4aer+tcdTAwMDGU0Vx1MDAxMaZcdTAwMTk55OZcXFx1MDAwNs46/zMhQs0v6FP4mJhPeHDv8aZcIqbvj6CMZUaV1Fx1MDAxNlxyUH9xg5dRwc9dXHUwMDE5/PnuXHUwMDE00KN1XHUwMDEw2jaYxbThhFx1MDAxYvp4ikRWofF5+u9cdTAwMGZ/YrRcdTAwMWVVeDCtXep8klx1MDAxNuNT1tolXHUwMDFhay5cdTAwMTiVXHUwMDE3VFOcx1SerMmf9crNXHUwMDE1cba3b1x1MDAxZbdP2d7hwutcdTAwMWKK79f+Zz1cdTAwMTAjWbT2XHUwMDFk88O+7fyu1Vx1MDAwNDOBXHUwMDBmP3FnipaKaYWeXHUwMDExwk1cdTAwMTXBWWLc62JcdTAwMTh0pYe/qFx1MDAxZnrT2JogpVJcdTAwMTCUSrFWXrGktfmagiCvU1x1MDAxYovEsSf7ytnkWpRcdTAwMDOWrqdpXHUwMDAxmH2uru6v3/aeyuz5hLXOLza6ei9cdTAwMDGQXHUwMDBi0lx1MDAwMpBbKahUn7LcMsbDbTZfUj6YpWN3yY1Qwij+qXzm+Fx1MDAwM1JQrIDK6lx1MDAxOMGhXGaciFx1MDAwMaFcdTAwMDVJwUvYXHUwMDBlTENcdTAwMGXVNfh55eWh06ktdCxcYt+0N1bs8a6/1Vx1MDAxNEfsdPnxYctcdTAwMWPdXFyGwovxhtbN8WbucOn2uf900zxYXdmtdfJnc7xcdTAwMTOLL5dcdTAwMWFcdTAwMTKq5dxcdTAwMWTLqu2Hc/6wzNfLXHUwMDE3jy7XY+kxoKnjnmw0z3dcdTAwMWGlXFx/v8DY7Yne7Zij4lxmxl1cdTAwMDWrOTjMNVx1MDAxZvnSob2vZI+uLu5rk8aszit2d14xtr2VR2aO9rZZt7nVuyia051O9mxcdTAwMDbjtk+LfPu0c9e4PV9Z6dzd8Wp3ozGDcZ98O1tsdtuVTqOWVeaym7vcOZ/BuGeZ8+WVytFT+/mudH113qm3bvdme6dcdTAwMWJr6sZq/sRcIlx1MDAxNt4kK33tqWdwOPw2Tel3XHUwMDBi5dt7bc9U7fbOt2rrK3vPz1x1MDAwYn4rxi24sqJcdTAwMThcdTAwMDNFXHUwMDA0TEarslx0T1RcdTAwMWFWQUIn60+2gYy/zaWihkx5eJeCXHRcdTAwMTeu/Vx1MDAxOe7lLpjiVKnWOpinmCaQ0kI4PE+NXZyj1p/vmelv41xmUnk9Zlx1MDAwNLV8XHUwMDEx0nk1XHUwMDE0pTjoNsU96Fx1MDAwNTU/hMClN3NMlFx1MDAwZnoyQlx1MDAwNkxyyjVXRlN7u5l1hPYu+URcYt9Aej9NeeFC5bLer1/d6qXTnavCSfe6c3aVlFx1MDAwNrwwuoHC3ZU0XHUwMDA2TphcZn/ZgWrQQCWn8lPSUqO0z1WYSo70kFx1MDAwNtRcdTAwMWOI54NAp1x1MDAxON3AXHUwMDAyel2T+zhcYmqMXHSCXHUwMDEyXHUwMDEwR550LPStXHUwMDFivvBmRGhqXHUwMDE45Vx1MDAxZF1NUbO7YSyPbGWqckhcdTAwMTSQl1x1MDAwMalKpVx1MDAxMlx1MDAwMivILIy5nF37aD4mmsZRJ6JpiMPziW7Ut/a2lld31m17m9VcdTAwMGLnS0uLrlx1MDAxYyyMsqHWP4N2K9H+0dzBXGJY9tIlgCpGz4M5UFS2sFx1MDAxNIkp1dA9Z6hfTDDwXHUwMDEyISSwSFbpkVtcdTAwMThQQM5cckuNkPxWXHUwMDBl86/3XHUwMDAxKupcdTAwMWR8d8uUldFe8Cqgpu1SckFtoORcdTAwMDRtllx1MDAxM+Vj8GlcbsRBUZou8Vx1MDAxMUqSnZlukMlcdTAwMTmMXFxbTVx1MDAxMWaTx4heb3VrrHVXqN7teba+1r2/bayaRddcck5cdTAwMTLsLKdcdTAwMGWetLbDyoFcdTAwMWJcdTAwMWVY5amSXG54oDFmXHUwMDBlR0mzUVx1MDAwZUZqOlxmU9/M4VcrXHUwMDA3Q31tXzxyatUo+JBy4CwwkmrkXHRv4XUw7f9iyiaxVpFMrrgg4Ntb7UNcdTAwMWQt03TNVlnr4u7DxdJcdTAwMWV/WFnaldXNnVx1MDAxM7foUVtcdTAwMWP8XCKgLo7YXHUwMDAywUy40OZgXHUwMDAwXHI3hVx1MDAxOU05qVRI6nPKZuwlknaxpatHYs9ccjXVUeHQ1XitMr9cdTAwMDPoXHUwMDA0pVx1MDAwMWg5N4ifXHUwMDE5qzRCt0lUL1x1MDAwNuyJWldcdHDiMOlNuU5a3V/OXHUwMDE4n1BfnodMxtBcdTAwMDVSXHUwMDA136DWncNcdTAwMTXS23TGwjCxk5lPPiuwdG5l7Fx1MDAxNIVPxqfFLGh7XHUwMDA3SZe16lx1MDAxZIU8epLINXQ0NCCn8MrhQlx1MDAwMTMvXHUwMDE5xlx1MDAwMlx1MDAxNnpETDa0XGKsXHUwMDFkdPfEhCVU/WiCyKBcImhiJ7PFXHQmeT+D/7P28lx1MDAxOVx1MDAxYlx1MDAxM6TeXHJcdTAwMDBauaPpZFx1MDAxOFx1MDAxNXnglFxyXHUwMDAzQ1x1MDAwN1x1MDAxY6twg8I/L0q3JEiB4Fxm2tNcbkWNXHUwMDE533pcdTAwMDC+XVx1MDAwNlxmx7EkTWp8TZ/hSTGsXHUwMDA2XGawk4N0XGKt+MikyPmHOcXEyPTCYo9Masb8Zq5d2ZLFlp5cdTAwMTGBnZJBpFx1MDAxNG5KTGujbVd8Kv11q7ZcdTAwMGIn+ZWLZiGfq+j1h6PT49WnRadcdTAwMTHCUMqYpM5WXHUwMDFluI9cdTAwMTRucnTrTCHhnrLa7Fx1MDAxYytcdTAwMDF7XHKkae2p6lx1MDAxMvlIcVx1MDAwNdhgdJnAOzSVPLR8NEmIyk9cdTAwMTmenva5dnVw3b27vTg9L1d6slct+cKVnHva57yuSud1pZk72Vjh4kqZnqvktlrnXHUwMDBm57nm7Vxmxt0qZZ+aprhd7anN/LJ4PNq7za/OYNxS/aydk63n5fbq2sFBXHUwMDAz2mJ3+XiycUe2flZXsOPVj0qmT+ShXHUwMDE5wadcYl3v7md7up59zvRcdTAwMWFnm5fNbvahmLtfePVcdTAwMDPEXHUwMDA3lsN35ZRcdTAwMTTAxPCZXHT3MtBcdTAwMTa2TkhFXHUwMDA1WefYz0OQdlx1MDAwMX8jTlx1MDAwMC3jbVxc2lx1MDAxN3SlJSXFqPAhJaiPqCBcdTAwMGJcIsFMOKl01ipoLicjY4U4uWbZmD7GVMHL2Slqfov67sVV4+E82z3Y2vadji9umaSaXHSLJL9cdTAwMGX83jhmQFB4JE1RMVx1MDAxZVB0XHUwMDAxXHUwMDA37YdcdTAwMTjb+UWOK1x1MDAxMd9+ZkQ+hWZcbqQmNUxsweQzzlx0J8pcdTAwMDCPyoK+ayqVPXEy49iQztAhwOLEcCYh0CTnPlFJXSpUMEVDqEp57aTTzFWqd539dregsrmL41x1MDAwZvZcdTAwMDL+uvhNyTlcdTAwMGZbkGhccmHJhlKfQnE2My9Gpn1cZlx1MDAwNuNsXHUwMDA09XpU9iMtZ74sXtrAyVxmP1x1MDAxM/dQ7GPKrb/DcVNM/KNValVcdTAwMWHtLvYspbNcIlx1MDAwZp2gfU2+VNosP4RHrkMlu2NcdTAwMWGeUlx1MDAxZdFcdTAwMTSUbr2Z7exs3rZcdTAwMGYq9lxc1Fx1MDAwYudPZWNbi1x1MDAwZUjHQZJcdTAwMWN5YtZcdTAwMThcdTAwMWRKQ/mZue9cdTAwMDPuqFx1MDAxYlxiRd9xM7/igOGKfu9cdTAwMDdgblx1MDAwNI+DnC9cbvVbYDhSmTsqsqakclKyyeH4z1x1MDAxZv02XHT2i5X52z9aP35EpJ06XHUwMDEx0497gFSt+vdcdTAwMWb/898//vUjXHUwMDE2pEInXHUwMDFjT89cdKRcdTAwMWad+8c9Mi2S61x1MDAxY1FMn7ThXHUwMDBl72nw3Vxc2TtYO75Yvun2av1u+Trb3L5Z+Hslp0VggVuq7K60VJFLbEnoNlx1MDAwZUvB4Z5cbuHnZ0+F8oHwwKbUSlx1MDAxYavjqlx1MDAwMVxiMOxBnzlyy6RcdTAwMGVL6E90e9BDkX7rVDhcdTAwMTVXe8W8LMna/nanVci7Z7n3fSZcdTAwMTRcdTAwMTl3XHUwMDExw9zHI5olXHUwMDFhZPAwTYxwXG5ElzJytfGwV6/pXVG/r1xc7JZyLb3wiFYmoFx1MDAwM1PttYOh81x1MDAxMYYslFx1MDAwZVxmPHUjmPZcdTAwMDA+n1x1MDAxZqK5dFx1MDAwMafuXHUwMDAwQlxmOtbouOJ+JmDWOipcdTAwMTNBXHUwMDA1tNmIuVx1MDAxNlSmJMFYz1x1MDAwMs5p/us4eMy/4vzsTm+MTDy9XHUwMDE5xJZOcfNRKNZcdTAwMWVue6utXFzmuL98VShs+vra9sLDQqtcdTAwMDDyZalajLAmWm+eM2rwTCnJsD3S89DKz6FccmJA5Ysp29Ir7l1cXFx1MDAxOWuA1MNcdTAwMWFTlVx1MDAxZVxytTVcdTAwMWHu7Z1VVDf11yFjds4nfHbLrLbKMVxuSFxuhdGmXHUwMDFl7Vx1MDAwNMFFqbdbelxuh0uEOKyWSev4Nac9oemNRW1iyIVcdTAwMTGJRz74TlRcXGVyczZeXS1oxIWRVH3eUihcdTAwMDVFWYZcdTAwMWPI14BcdTAwMGJcdTAwMDbcwtLIQVx1MDAxMzc3P9hcbiZcdTAwMDNcdTAwMTgxXHUwMDAw1yi6XHUwMDBmiFxyg1x1MDAxYZRcdTAwMTbxQFx1MDAwMKfyWdyNXHUwMDA2XFzAIbbUL3nhyuFMXHUwMDE4cJFcdTAwMWHb8Fx1MDAxMrTAXHUwMDAzT0VUjKHiqczJcLGMt/BcdTAwMDeKXHUwMDA0cFx1MDAxNlT/5YxsXHUwMDEwwPb6rikjLsYztVx1MDAxZu+hXHUwMDE0ithcdTAwMDQzkoP9KGVjJpVhXHUwMDAxl3C8PYVaUl1qKezIpH6niItkwaVnVGTfx/sj/PfUqsuZZC5O/WGpXHUwMDA03eRRm+NdkVx1MDAwNVVenK5cdTAwMTC5oFKRzIKKy0i1XHUwMDAxwSntz0B7gf6G63jN/LCaavtz5iH80pjwWfR7vFx1MDAwNVx1MDAwNMFB5rlcdTAwMDIvIb4+wjlAj1x1MDAwMGdcbiP+TXXX+Fx1MDAxM5o3LSGo9oI2Wlxmqj/rcGG7ocgtXHRfXHUwMDEy4HJeUY4nvJeP6a7xVTTeZuVcdTAwMDJcdTAwMGZcdTAwMDB7XHUwMDAzJGtKXHUwMDEzVVx0s1x1MDAxYeRcIllcdTAwMDa2XHUwMDAz/cuM0iOz+p2UV6Lg0jMqslPqrvHFUrRMvm5cdTAwMWJ0e1x1MDAwMiOZ3Gu6MTdGbJmKPLfWZzPds8Ja4un+glx1MDAxNEuB9Vx1MDAwZSjMVSkqOSZ4tEWAhiNjJOXHgdNguT7Voys+w8WywFhH7f08XHUwMDE3JFx1MDAwNDGki9lAQFx1MDAxMLizcKeYXHUwMDFmLdaLjdRQv1x0N3DvMDp+qN9fn2h24DOK7+U3q1x1MDAxYrXSPFx1MDAwM8W+a6GEf7nSPNbi4LB+3c9eXe7tPGXytZ38Lz5NScybtzrxOFx1MDAwNSZBO+OmqWx2wi42N3dUvZS3/Fks96pnpU5SO99cdTAwMDVJfzPWXHUwMDA3lFx1MDAxM8s1lVx1MDAxMFx1MDAwZTter1x1MDAxN1x1MDAwNzyg3qCekVx1MDAwMbXzUFxyylCwXHUwMDE4rCFxXHUwMDEyOFx1MDAxMjH3XHUwMDA1Mlx1MDAxMFxu3FZCW1x1MDAwYlx1MDAwZm95tKKGprwnnuSOzUI1fKe2XHJcdTAwMWWqnSZcdTAwMTRcZrWkPnLaXGZVXHUwMDBihsuFPfSSaVx1MDAwMZuj8MVTh0vafHoyMoBcdTAwMTmgljKerq2scOp9vImIQTLuXWIqXHUwMDFhRZsw4lxcXHUwMDEzw75fKi2Jzcddm80s352Zw9JtbeNswWHveGAt/Fx1MDAxNsok5U5GjmOkXHUwMDEyVMzQU4FcdTAwMDNcdTAwMGXv51PHMVx0sNeBNpaq+TiKOYhccoVcdTAwMWL0+1x1MDAxNd5S5iP1KfajUTmgNJz+l3ZR+Fxy/MHziYRcdTAwMGZcdTAwMTPAvdXKQVx1MDAwMUNcdTAwMTVLNlxm1dGtSoV+olx1MDAwMFxmXHUwMDA2ZIFcdTAwMTRWK7hAXG5cdTAwMGWc5z5U+fRz2Hdjqifjsc5cYjU5+OuNh0Zn67DIurp8nlVcIvvYr32oeOJX2nxcdTAwMTdYb6SheiNORa9cdTAwMTax7oEyXFxCXHJiP+Gqzsfoe+HgmzsvvFJx7b5FoCz5K1x1MDAxY/aBhzpcdTAwMWb97FxuSJVX5LfFnz/wXcA9XGam4Vx1MDAxY1x1MDAxNJArZiM4paq4XHUwMDBlQMYrjlpNTGDz4/d+8GlcdTAwMDFVXHUwMDEzNoK6zoNi8GmPXHUwMDAyklEvx1x1MDAxY2NSN1mrp0g+72U2V2qsU3BcdTAwMWR2bJ/zxcrjdS8ppGCBUO+pIShds1xmXHUwMDE1XHJ+LZ7n6KyfqUFMXHUwMDAx5/JTYe/zQj3FIXBvjP2293OGvVx1MDAwM/dcdTAwMDaVd5xpLrSQXHUwMDExlCphXHUwMDA3zWM5RXpNQPR/Ly0y9kBRiMSSOVx1MDAxMFxcLew0pfbO9/NLxe3z1Ua2s3P6vFs5Xz1sJjVfWFx1MDAxMEViOV30Y9cl9adcdTAwMTMqelx1MDAxOWJNwEDrXHUwMDA0XHUwMDEzXHUwMDEyRFCa5OyveqnMmE5RJJH3vEUluaFQ97gm4lxcQ0T0z3/AZkbcXHUwMDA3NSggYX7dgeLsj7qql6WbdliBhVx1MDAwNDe5q6LRjMA3RZT7Vnv1IZsx7DxzvMmO6k/buzZvXHUwMDE3XFxwmVx0KGSIzpKoJGbUXHUwMDAywidm+KngXHUwMDAyKlx1MDAwNX5JckjdZ1x1MDAwNFfEpChcblx1MDAxZFxib97+0XIk4MBLY5hwbn50d6FEVSX7aEZjXHUwMDAz2TSiunptT9z21vX95dbBTqtYP3vulCqLJqo6XHUwMDE4dKtxsC50a1x1MDAxOfp6P0XXU1x1MDAwNJRcdTAwMTNUTkpHw2cgulx1MDAxNKJBXHUwMDFkr+F9yy+UXFxcdTAwMDXLTXlaTFDFWz16NOslh0dtU4tcdTAwMWT/TpI7pnetSK5OwSk8TlxiOUV6bX99u9pYW64ssZsrq263sjsrxfKiiW5cZj2Q1NTAUIMjXHUwMDFi1bJwQ4SkiEmuweRFcnVcbjDLesWOXHUwMDE31eh73qO8YujASFx1MDAwZlx1MDAxNMNcdTAwMWQ1pJ/j4eHXi+Z46pp83Vx1MDAwNYdcdTAwMGaEzvspXHUwMDAyiJ1vq+uT5ee1p9vSiaizjVx1MDAxNit3XHUwMDE3XThVXHUwMDAw06Es/ufxL1HuanigubScnFxuK71LPvr6jHRy+E+Ov/1jQ+r8/c5LXHUwMDA1JsRcdEZTZFx1MDAwNNOKXG4x6LTQ4b+O9CbnTcNwXHUwMDFhK9hcdTAwMTSBaMdup7TRazVcdTAwMWaP2oWj7exSprZReF504Vx1MDAwNX9lXHUwMDFhelUqjn8xo8LLlaR4RzBcdTAwMDFcdTAwMTMqZj9cIrxcdTAwMTUvSqI0Xnij73krXHUwMDFjmOhUTS67nFPLcCo19+9cIrsqXFxDLqp6vVx1MDAxYpQqY5Or3vWTc7O6ff1YOri/K5SzmfvNja1cdTAwMGZcdTAwMTXh/sKWTdr6gHL9Sf9ipV30zlFDfOH7eJBIRq0z5nBcdTAwMDA5kzAkQUXcvGcsrdBuPptfvyx3TUlXXHUwMDFil5vr9Wyjutr5jkOafNx/nziksOM04jUwI0CnpzhULHe6rYKo9PhqvZ4t16/Y5WN3a7FtXHUwMDFi11x1MDAxZdSLXHUwMDAyQVx1MDAwNLeS/o5oXHUwMDA3plx1MDAwMumpziB5x3Ck5lCjfyaRSFYxTi+lubtcdTAwMWZXXHUwMDBl31x1MDAxN1x1MDAxNIPnL1x1MDAxMolcdTAwMDQzk4R8YJ6yXHUwMDAzp4hEqudyXHUwMDFi5jTbLtVXL7JcdTAwMGb+/jBXc72FXHUwMDA3/qD+vpNKce5cIlx1MDAwZZmUOqDuKjC4XHUwMDE0qlx1MDAxZI5cdTAwMTJatEgkXGKIZd4k9FP9XHUwMDA2/nck0stPw127WOJxXGZVTVx1MDAxNCCfk1x1MDAwN1x1MDAxZtcun/J7/N50VO35aa3Y2imKh0Vv2qU9lDjF8TA4kpqHXG5bv0ZcIrFcdTAwMDAsXHUwMDFjsFKUXHUwMDE3XHUwMDFhPjhdpJiEQdVPa1x1MDAxM/KpvpH/XHUwMDFkilx1MDAxNIH9mHpFVLVAWmMmv1wi2FkpP4sncb99fNZcdTAwMTLqeIXll3tJ7dRcdTAwMTdcdPdcdTAwMWFcdTAwMWOeWpBrY0z0JMuLXHUwMDAwgFx1MDAwN6+TUPCh/N6Fgr3iRmrmU8+wvmE/eL5DkWZcdTAwMWaKXHUwMDE0ysdcdTAwMWZccmqkpfLhUl+pl42PW9u16sb+Wa6a7Ww1jp9b7bOLXHUwMDA11yTwxlx1MDAwMmxcdTAwMTVT1Fx1MDAxM1x1MDAxOFx1MDAxYlx1MDAxNy2TRFx1MDAxNTGtclpcdTAwMTK5XHUwMDFio0h+eShcdTAwMTK0ndNKhVx1MDAwYvh89anil0Z4yOSam1x1MDAwMLuHvzdFUm6/vN+8r+RcdTAwMGIud3dn78tn90/7+7eLLrmDjm5cdTAwMWOs13DLRm2gVXhZMlxy3WGE8mw+YXRcdTAwMWaLRuJSXG6p6Y7/30NY9Vx1MDAxOEeNyZfyXHUwMDAyXHUwMDEzXHUwMDBi61Zhv1aV+e1Ded7fq/q1XHUwMDBlq9WWXHUwMDE3TVjHhyNccoTXXHUwMDE4aqLlLL0pKrs6gKcsjKTqK2w+Wvdj0Uhcbj6lnKCD6u8kuGOikaRNjFUmUzTc2C21VU6xcrrk95aWLk6WPL9/eKiuqN+CXHUwMDFmvN+Zj1x1MDAwNivzQFx1MDAxYjhkXG5cdTAwMWNBUKnFRFmdfziSMlJgR+Z4jrBgl+Iy+dZcdTAwMGJMXHUwMDE5xpFNkYi3fNDIXHUwMDFjup1SUdqdbru6fLi3tLrgXHUwMDAxXHUwMDFkkE7oWevhSiqu8S9R9qpV4Fx1MDAxNMRXQJUyXHUwMDFiKlx1MDAxNrN44UiUSmzAwlPbz/51pJcl5pBzcjawkHJy52t5ZXWvfVgyXHLb5zJfb15cdTAwMTWP/enCiy88XHUwMDFmy6FaXHUwMDE5c9KGxPNNfOHQXHUwMDE4R+F08NCT62L98ngkLzh3Yp5nOV8vvEnnj5wlclx1MDAwMvo84aZcdKOrbVx1MDAxOL1UfvaqvFrnq0e5vaXDbFLtg4UpXCJrlYVvpbSgXFxwLXS06ImUcIC8IWavzLhTg083XHUwMDAwYlRDXHUwMDE2zlx1MDAwM/Xqg1x1MDAxOYhpRVwiwLTBfy13SnAvw0Fir8JcdTAwMGLLYIxgXHSpIYtTzi16eJjwym9zLJm4efRkRvftfbw/wn9PXHUwMDBiXzGuNrpcdTAwMTFUfXuKUKHHs7W8aVx1MDAxYlnsNs9yq9m9xtPJ+cOi45dzrVx1MDAwMy2sx8rDsNjI0YnWkrpcdTAwMDdpSWWBrPGfql4yXHUwMDFlv+RcdTAwMDbTtbCl4yqr44xcdTAwMGZ1w6PoXHUwMDA1qFx1MDAxYc6M16G0s5+8X1hcdTAwMDF9o+MjXHUwMDA3vlx1MDAwMTw3XHUwMDAwJ+5cdTAwMWU9o/s2JYBcdTAwMTPbXHIl1yNcdTAwMTRCXHUwMDFhfNRcdTAwMTRcdELqIM+Wn7ebaktt9LZrZv+2cPHB9rVfXHUwMDE4XG5cZjcj3H87klwiJMHXQt2/zPzwq+NcIoDtSMSvcXRcdTAwMTUp/ddcdTAwMDM05P+mVF+HRvThx1xmvWlcXOsvWqB6rbs0aNLzn3jpf4VcdTAwMTf0vVx1MDAwNjtcdTAwMWL61bn3+oqZ1ofgXHUwMDE2jshcdTAwMWG1ly9ccqMn57tcdTAwMDeba9vt3L19auf2enf3+0zI5YXv1+o4peQnwc17R8W+vlx1MDAwMG5WxYTW8dFcYntOfYFcdTAwMTjFQSww4LxcdFTombyR5dLh4erB4dLhRmEnXHUwMDE2aKFcdTAwMTjIr1x1MDAwMFp4OmNcdTAwMDGWkt8ypqiWloJcIigmR5noXFxfdXZZfmel+XR+o/KnK7leddFRRoVcdTAwMDNcdTAwMDOnIUTeSmM9j5w1e1x1MDAwZX/BXHUwMDBivKK9ocDEueFsJn3ZJedmgpY9XHUwMDA3rPXw3Lsuqf0zlr3vP16ZSq/83YErMu7v1pX9WVxcblx1MDAxZFx1MDAxNUqd0vbJbu5eni+v7Fxcbcxg3Ll3e4/X35/QazJRrzlBvbesnfyM97mwbLpPhfX9Zu3oaOn8pqH0tlxcfLVm7CC+xVx06nlcIljkstcxOlx1MDAwNNZcdTAwMTSc5blcdTAwMTSL3+ydycGhTGre9Mc12yfj9j4gw0nkV8vEs17iLN7rKVx1MDAwMlx1MDAxNzr7+7ndUq9QWlary5V9w1x1MDAxZVx1MDAxZmpm4cXXMFx0ibBw8OloTEfZL8TBXHUwMDA11DPFco7/O1x1MDAxMZ3aLM1yTEZZXFy7d1wi6lLrhFx1MDAwM6GvkNBxOny3WivaSq1fbd1087X8Vr1du9mehe09zrjL2ubNwVnxeblkrmuusy7nb1x1MDAxYmK5/ceb1JfLQVx1MDAxMJTL8cz+q7vU/5zMWN2R2PlH8uSLXCJcbqqkXHUwMDEwn4mVx3jLv6CNf1x1MDAwNHnORklcdTAwMDVcdTAwMDfGXHUwMDE4b2yE0msnv+qkylx1MDAwNF5cdTAwMTkhtJbOUaPfUU1izFxih+fOc5jAcHfQiVx1MDAxZOm5XHUwMDE4sdBcdTAwMDdO1OpnvCP4Y7hljlx1MDAxOSTmYlx1MDAxN4Sk7mAu9LbXbmY6cIpL76SQXHUwMDE2/ECNdlx1MDAwNJuo1c+t2i6c5FcumoV8rqLXXHUwMDFmjk6PV5+SZuU1g1MoXHUwMDFk3YMz70cmJVx1MDAwMubeYodBN2P6XHUwMDBm/U6dfiiPlnsqKq8gp6FAO3ok02m/XHUwMDBmg1x1MDAxY2BcdTAwMGaxPcZcdTAwMDNaytmhcHpcYvn7XGJ/hP+ellx1MDAxYckxXHUwMDA1OZRcdTAwMTnU/5uiIMfp4VXtxp7d3VevbkVl5ancPW1fL/o5vFx1MDAxOJQ8TD6H1zLQXr4qN60/V1x1MDAxMjjlID7u6jsmUENxqs6eULFzMVx1MDAwZVx1MDAwNjVcdTAwMGI8V8Jp+/LnpNyhV6rXXHUwMDBlX0+9c932zX/++Efr5S11/Fx1MDAxN/ZuYM7L5b/9/HG/TT8slYKgVPrbj/efXtdaXHUwMDFiVVxuhmVcIv4oX4R2+iuIyHy+2YdQL0xy7p1khmPHp8i96yyt7+5Jt3+33T81S3L7oHC8t/Cdxbm141BcdTAwMGYj9UWcRknKtTLKc2qbXHUwMDE4vqdcdTAwMGa78yMqXHUwMDAwvj+VXHUwMDBmSki0X1xmXHUwMDFkoKhtkjCSw33w6t2EpemA0uC2K/t0SKL+j1x1MDAxNlx0+o///X+AXHUwMDA1LiSoPDYt3rHwX4rnXHRm+TGTrJM79b20n5/mqu60euBqh6WDgyu119PPrq6qudzig9PZgFJcdTAwMGa19lx1MDAxNi6oXHUwMDFmrpLFbSA5QMtccjXDXHUwMDE2ao6nbVJAhkNPTFAwNzKg9CVOMZVgt2Y0SVY4T02dXHUwMDEzstpcdTAwMTZcdTAwMDSsMrCOSUflR+hcItRNitb2XHUwMDAz4FKo/+fAXHUwMDFlkZV687j/0YrFKVc2acG+XHUwMDAyt2PnO1x1MDAxNrGJsaQ28XJdXG7vjJsmXHUwMDE2bUtcXFx1MDAxNevX/Pmsfpo9uD3udcz+fnvGR1x1MDAwNNU2+VAzRaww1E7n3ZxGoqDhb6pAXHUwMDBlsqlcdTAwMDYm1X+qhM34XHUwMDAz8iDmRFxcupFjgUHEXCK1zv6AXHT90mOBv1rEWSa0Q/Rgb95/5Y/w31NHhYZUyUgyXHUwMDAyrCZcXKYporrzfnv16qD9JE+615VcdTAwMGL+VNwtZpLyaT+MxFKvMWskUlx1MDAxOXQhXHJcYlxipXqpyE1cdTAwMTWUVSBcdTAwMDVhlE6Jwm2BZ1xyRFx1MDAxZVB9XCJhnWNOe2Viwl5cZrRcdTAwMDJ0XHUwMDAyrDj1gvaxPNc4xT7Ec79B+pkqM0mbR8/Itk2J30Tqy5PjZ1x1MDAxOJW6UuHMqjT82uXiVXGn8bB0qLLtq6vDLbvfSkoxXlx1MDAxY0vqQSdcdTAwMTlcYvDrwYlcdTAwMWZcdTAwMGXrVpJcdTAwMDdcXPDXpFxmydT8TqOoRr1cdTAwMWR6YvCrh9gxXHUwMDFiXHRiXHUwMDEzykupw1x1MDAxOYyLx3xDN4wpVLeQy2WW15c24kPWlIksxtdedL1P7kPYXHUwMDEzyW4nUFx1MDAwN8M5TZL0Mcvsy81cdTAwMDZbWTncK/VcdTAwMWWuaiumktRcdTAwMGZycaDHJZdjSOxXYk/HXFySx9xsYVx1MDAxNo7ZhF7wc4s7y3Z8ocuW91x1MDAxZfafes2nfrVcbiFVv+aOelxu7O6Mhe7XQnVnXHUwMDEypCZfSYc6XHUwMDE1j3ZX4VRpaYpLm/G7ubBYXHUwMDE1Tlx1MDAwNVwiXHUwMDE5q5yyXb9cdTAwMDSr3MV6nKNgXHUwMDA189ZcIqnP2u/AZJNugsdr+1x1MDAxZu83wSCVVL6W4uRgXHUwMDFlPVx1MDAwYrmBPy+nefDTXHUwMDE5/D1vfoekgZ5pfdtk1LvkS1x1MDAxYlx1MDAwN46F3Z5cIoytWJaP4qT/pPeVLFx1MDAxZa4028vFjaRM+0VCvVx1MDAxZHfMpLhcdTAwMGWohcRXoJ7Ou2Jgz4Khbk4/XHUwMDBmgKnJk1x1MDAxM+5cdTAwMDNcdTAwMDfAXHUwMDBigv15erFzXHUwMDA1ZGZ4p1x1MDAwNj9cdTAwMWFs0pS4TOLNSiT7rGpQRmCaSlx1MDAwMlc3xbzVhyfrJ0fm1LROL8XqUVIn1C+6rvFpoMzQXHKIoVx1MDAwMkKMgsNUtIKLZJSVXHUwMDA3cFx1MDAwYiu48y65XHUwMDAyxmdBaWzc/cxoalx1MDAxNVx1MDAxZERzKdKKtJxcdTAwMWOJx6vtu6fjXHUwMDBl97ZeMmeZzHFm3nUunHGWT1x1MDAxM6NphOBcdTAwMWOK0SphJmbDxYPV/Vx1MDAxZiurR1x1MDAxYsursYT4i9OvwtN5QeFcdTAwMWavK/xn6fb2oI8leuNcdTAwMWZ/3l/WXHUwMDFlsvElbqnK7Vx1MDAxZq9cdTAwMTgmsNRotf/5rz/+9f9cdTAwMDFKsSikIn0= - - - - NFT0x123...456ASSETANCHORANCHOR-TECHNOLOGY§ORACLEPROOF-OF-CONTROLto0xaa..aaERC-69560xaa..aatoken: 2402anchor: 0x123...456{ to: 0xaa..aa, anchor: 0x123..456, signed: § } 0x..gasPayertransferAnchor( )ATTESTATION0xbb...bbsafeTransferFrom( from: 0xbb..bb, to: 0xaa..aa, tokenId: 2402)anchorByToken2402 <> 0x123...456ownerOf(2402) 0xbb...bbOFF-CHAINON-CHAINUSER DEVICE \ No newline at end of file diff --git a/assets/eip-6956/test/ERC6956.ts b/assets/eip-6956/test/ERC6956.ts deleted file mode 100644 index 01bf26982b8e67..00000000000000 --- a/assets/eip-6956/test/ERC6956.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { createHash } from 'node:crypto'; -import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; -import { ERC6956Authorization, ERC6956Role, merkleTestAnchors, NULLADDR, createAttestation} from "./commons"; -import { IERC6956AttestationLimitedInterfaceId, IERC6956InterfaceId, IERC6956FloatableInterfaceId, IERC6956ValidAnchorsInterfaceId} from "./commons"; - - - -export async function minimalAttestationExample() { - // #################################### PRELIMINARIES - /*const merkleTestAnchors = [ - ['0x' + createHash('sha256').update('TestAnchor123').digest('hex')], - ['0x' + createHash('sha256').update('TestAnchor124').digest('hex')], - ['0x' + createHash('sha256').update('TestAnchor125').digest('hex')], - ['0x' + createHash('sha256').update('TestAnchor126').digest('hex')], - ['0x' + createHash('sha256').update('SaltLeave').digest('hex')] // shall never be used on-chain! - ] - const merkleTree = StandardMerkleTree.of(merkleTestAnchors, ["bytes32"]);*/ - - // #################################### ACCOUNTS - // Alice shall get the NFT, oracle signs the attestation off-chain - const [alice, oracle] = await ethers.getSigners(); - - // #################################### CREATE AN ATTESTATION - const to = alice.address; - const anchor = merkleTestAnchors[0][0]; - // const proof = merkleTree.getProof([anchor]); - const attestationTime = Math.floor(Date.now() / 1000.0); // Now in seconds UTC - - const validStartTime = 0; - const validEndTime = attestationTime + 15 * 60; // 15 minutes valid from attestation - - // Hash and sign. In practice, oracle shall only sign when Proof-of-Control is established! - const messageHash = ethers.utils.solidityKeccak256(["address", "bytes32", "uint256", 'uint256', "uint256", "bytes32[]"], [to, anchor, attestationTime, validStartTime, validEndTime, proof]); - const sig = await oracle.signMessage(ethers.utils.arrayify(messageHash)); - // Encode - return ethers.utils.defaultAbiCoder.encode(['address', 'bytes32', 'uint256', 'uint256', 'uint256', 'bytes32[]', 'bytes'], [to, anchor, attestationTime, validStartTime, validStartTime, proof, sig]); -} - -describe("ERC6956: Asset-Bound NFT --- Basics", function () { - // Fixture to deploy the abnftContract contract and assigne roles. - // Besides owner there's user, minter and burner with appropriate roles. - async function deployAbNftFixture() { - // Contracts are deployed using the first signer/account by default - const [owner, maintainer, oracle, alice, bob, mallory, hacker, carl, gasProvider ] = await ethers.getSigners(); - - const AbNftContract = await ethers.getContractFactory("ERC6956"); - //const burnAuthorization = ERC6956Authorization.ALL; - //const approveAuthorization = ERC6956Authorization.ALL; - - const abnftContract = await AbNftContract.connect(owner).deploy("Asset-Bound NFT test", "ABNFT"); - await abnftContract.connect(owner).updateMaintainer(maintainer.address, true); - - await expect(abnftContract.connect(maintainer).updateOracle(oracle.address, true)) - .to.emit(abnftContract, "OracleUpdate") - .withArgs(oracle.address, true); - - - return { abnftContract, owner, maintainer, oracle, alice, bob, mallory, hacker, carl, gasProvider }; - } - - async function deployABTandMintTokenToAlice() { - // Contracts are deployed using the first signer/account by default - const {abnftContract, owner, maintainer, oracle, alice, bob, mallory, hacker, carl, gasProvider} = await deployAbNftFixture(); - - const anchor = merkleTestAnchors[0][0]; - const mintAttestationAlice = await createAttestation(alice.address, anchor, oracle); // Mint to alice - - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes)"](mintAttestationAlice)) - .to.emit(abnftContract, "Transfer") // Standard ERC721 event - .withArgs(NULLADDR, alice.address, 1); - - return { abnftContract, owner, maintainer, oracle, mintAttestationAlice, anchor, alice, bob, mallory, hacker, carl, gasProvider }; - } - - - describe("Deployment & Settings", function () { - it("Should implement EIP-165 support the EIP-6956 interface", async function () { - const { abnftContract } = await loadFixture(deployAbNftFixture); - expect(await abnftContract.supportsInterface(IERC6956InterfaceId)).to.equal(true); - expect(await abnftContract.supportsInterface(IERC6956ValidAnchorsInterfaceId)).to.equal(false); - expect(await abnftContract.supportsInterface(IERC6956FloatableInterfaceId)).to.equal(false); - expect(await abnftContract.supportsInterface(IERC6956AttestationLimitedInterfaceId)).to.equal(false); - }); - }); - - -describe("Authorization Map tests", function () { - it("SHOULD interpret ERC6956Authorization correctly", async function () { - // Create the message to sign - const { abnftContract } = await loadFixture(deployAbNftFixture); - - // OWNER - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.NONE))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ISSUER))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ASSET))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER_AND_ASSET))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER_AND_ISSUER))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ASSET_AND_ISSUER))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.OWNER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ALL))) - .to.be.equal(true); - - // ISSUER - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.NONE))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ISSUER))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ASSET))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER_AND_ASSET))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER_AND_ISSUER))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ASSET_AND_ISSUER))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ISSUER, await abnftContract.createAuthorizationMap(ERC6956Authorization.ALL))) - .to.be.equal(true); - - - // ASSET - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.NONE))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.ISSUER))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.ASSET))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER_AND_ASSET))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.OWNER_AND_ISSUER))) - .to.be.equal(false); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.ASSET_AND_ISSUER))) - .to.be.equal(true); - await expect(await abnftContract.hasAuthorization(ERC6956Role.ASSET, await abnftContract.createAuthorizationMap(ERC6956Authorization.ALL))) - .to.be.equal(true); - }); -}); - - - describe("Attestation-based transfers", function () { - it("SHOULD not allow non-trusted oracles to issue attestation", async function () { - // Create the message to sign - const { abnftContract, oracle, mallory, gasProvider } = await loadFixture(deployAbNftFixture); - - const to = "0x1234567890123456789012345678901234567890"; - const anchor = merkleTestAnchors[0][0]; - const attestation = await createAttestation(to, anchor, oracle); - - const fraudAttestation = await createAttestation(to, anchor, mallory); - await expect(abnftContract['transferAnchor(bytes)'](fraudAttestation)) - .to.be.revertedWith("ERC6956-E8"); - }); - - it("SHOULD allow mint and transfer with valid attestations", async function() { - const { abnftContract, oracle, mintAttestationAlice, anchor, alice, bob, hacker, gasProvider } = await loadFixture(deployABTandMintTokenToAlice); - - const attestationBob = await createAttestation(bob.address, anchor, oracle); // Mint to alice - - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes)"](attestationBob)) - .to.emit(abnftContract, "Transfer") // Standard ERC721 event - .withArgs(alice.address, bob.address, 1) - .to.emit(abnftContract, "AnchorTransfer") - .withArgs(alice.address, bob.address, anchor, 1); - - // Token is now at bob... so alice may hire a hacker quickly and re-use her attestation to get - // the token back from Bob ... which shall of course not work - await expect(abnftContract.connect(hacker)["transferAnchor(bytes)"](mintAttestationAlice)) - .to.revertedWith("ERC6956-E9") // Standard ERC721 event - }) - - - it("SHOULDN'T allow safeTransfer per default", async function() { - const { abnftContract, alice, bob} = await loadFixture(deployABTandMintTokenToAlice); - - await expect(abnftContract.connect(alice).transferFrom(alice.address, bob.address, 1)) - .to.revertedWith("ERC6956-E5"); - }) - - it("SHOULDN'T allow approveAnchor followed by safeTransfer when anchor not floating", async function() { - const { abnftContract, anchor, oracle, alice, bob, gasProvider, mallory,carl} = await loadFixture(deployABTandMintTokenToAlice); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - const attestationBob = await createAttestation(bob.address, anchor, oracle); // Mint to alice - - // somebody approves himself via attestation approves bob to act on her behalf - await expect(abnftContract.connect(gasProvider)["approveAnchor(bytes)"](attestationBob)) - .to.emit(abnftContract, "Approval") // Standard ERC721 event - .withArgs(await abnftContract.ownerOf(tokenId), bob.address, tokenId); - - // Should not allow mallory to transfer, since only bob is approved - await expect(abnftContract.connect(mallory).transferFrom(alice.address, bob.address, 1)) - .to.revertedWith("ERC721: caller is not token owner or approved"); - - // Even though Bob is approved, cannot transfer, since anchor is not floating - await expect(abnftContract.connect(bob).transferFrom(alice.address, carl.address, tokenId)) - .to.revertedWith("ERC6956-E5"); - }) - - it("SHOULDN't allow using attestations before validity ", async function() { - const { abnftContract, maintainer, oracle, alice } = await loadFixture(deployAbNftFixture); - const anchor = merkleTestAnchors[0][0]; - - // Let the oracle create an valid attestation (from the oracle's view) - const curTime = Math.floor(Date.now() / 1000.0); - const twoMinInFuture = curTime + 2 * 60; - const attestationAlice = await createAttestation(alice.address, anchor, oracle, twoMinInFuture); // Mint to alice - await expect(abnftContract.connect(alice)["transferAnchor(bytes)"](attestationAlice)) - .to.revertedWith("ERC6956-E10") - }) - }); - - describe("ERC721Burnable-compatible behavior", function () { - it("SHOULD burn like ERC-721 (direct)", async function() { - const { abnftContract, anchor, alice, bob} = await loadFixture(deployABTandMintTokenToAlice); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - // Let bob try to burn... should not work - await expect(abnftContract.connect(bob).burn(tokenId)) - .to.revertedWith("ERC6956-E2"); - - // Alice then burns, which shall be transaction to 0x0 - await expect(abnftContract.connect(alice).burn(tokenId)) - .to.emit(abnftContract, "Transfer") - .withArgs( alice.address,NULLADDR, tokenId); - }) - it("SHOULD burn like ERC-721 (approved)", async function() { - const { abnftContract, owner, maintainer, oracle, mintAttestationAlice, anchor, alice, bob, mallory, hacker } = await loadFixture(deployABTandMintTokenToAlice); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - // alice approves bob to act on her behalf - await expect(abnftContract.connect(alice).setApprovalForAll(bob.address, true)) - .to.emit(abnftContract, "ApprovalForAll") // Standard ERC721 event - .withArgs(alice.address, bob.address, true); - - // Let mallory try to burn... should not work - await expect(abnftContract.connect(mallory).burn(tokenId)) - .to.revertedWith("ERC6956-E2"); - - // Bob is approved, so bob can burn - await expect(abnftContract.connect(bob).burn(tokenId)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address,NULLADDR, tokenId) - .to.emit(abnftContract, "AnchorTransfer") - .withArgs(alice.address,NULLADDR, anchor, tokenId); - }) - - it("SHOULD allow issuer to burn", async function() { - const { abnftContract, owner, maintainer, oracle, mintAttestationAlice, anchor, alice, bob, mallory, hacker } = await loadFixture(deployABTandMintTokenToAlice); - - const tokenId = await abnftContract.tokenByAnchor(anchor); - - await abnftContract.connect(maintainer).updateBurnAuthorization(ERC6956Authorization.ISSUER); - - // Let mallory try to burn... should not work - await expect(abnftContract.connect(mallory).burn(tokenId)) - .to.revertedWith("ERC6956-E2"); - - // Bob is approved, so bob can burn - await expect(abnftContract.connect(maintainer).burn(tokenId)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address,NULLADDR, tokenId); - }) - - it("SHOULD burn like ERC-721 (via attestation-approved)", async function() { - const { abnftContract, oracle, anchor, alice, bob, mallory, hacker } = await loadFixture(deployABTandMintTokenToAlice); - const tokenId = await abnftContract.tokenByAnchor(anchor); - const attestationBob = await createAttestation(bob.address, anchor, oracle); // Mint to alice - - // somebody approves himself via attestation approves bob to act on her behalf - await expect(abnftContract.connect(hacker)["approveAnchor(bytes)"](attestationBob)) - .to.emit(abnftContract, "Approval") // Standard ERC721 event - .withArgs(await abnftContract.ownerOf(tokenId), bob.address, tokenId); - - // Let mallory try to burn... should not work - await expect(abnftContract.connect(mallory).burn(tokenId)) - .to.revertedWith("ERC6956-E2"); - - // Bob is approved, so bob can burn - await expect(abnftContract.connect(bob).burn(tokenId)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address,NULLADDR, tokenId); - }) - - it("SHOULD burn like ERC-721 (attestation)", async function() { - const { abnftContract, oracle, mintAttestationAlice, anchor, alice, bob, mallory } = await loadFixture(deployABTandMintTokenToAlice); - const tokenId = await abnftContract.tokenByAnchor(anchor); - const burnAttestation = await createAttestation(bob.address, anchor, oracle); // Mint to alice - - // Let mallory try to burn a token based on the creation anchor.. - await expect(abnftContract.connect(mallory)["burnAnchor(bytes)"](mintAttestationAlice)) - .to.revertedWith("ERC6956-E9"); - - // Now, using a fresh attestation, the same guy can burn - await expect(abnftContract.connect(mallory)["burnAnchor(bytes)"](burnAttestation)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address,NULLADDR, tokenId); - }) - - - it("SHOULD use same tokenId when anchor is used again after burning", async function() { - const { abnftContract, oracle, anchor, alice, bob, mallory } = await loadFixture(deployABTandMintTokenToAlice); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - // Alice then burns her token, since she does no longer like it in her wallet. This shall be a transaction to 0x0 - await expect(abnftContract.connect(alice).burn(tokenId)) - .to.emit(abnftContract, "Transfer") - .withArgs( alice.address,NULLADDR, tokenId); - - // Bob gets the ASSET, confirmed by ORACLE. Since Alice burned tokenId 1 before, but we have the same anchor - // it is expected that BOB gets a new NFT with same tokenId - const attestationBob = await createAttestation(bob.address, anchor, oracle); // Mint to alice - await expect(abnftContract.connect(mallory)["transferAnchor(bytes)"](attestationBob)) - .to.emit(abnftContract, "Transfer") // Standard ERC721 event - .withArgs(NULLADDR, bob.address, tokenId); - }) -}); - - -describe("Metadata tests", function () { - it("SHOULD allow only maintainer to update baseURI", async function () { - // Create the message to sign - const { abnftContract, maintainer, mallory } = await loadFixture(deployABTandMintTokenToAlice); - - await expect(abnftContract.connect(mallory).updateBaseURI("http://test.xyz/")) - .to.revertedWith("ERC6956-E1"); - - await abnftContract.connect(maintainer).updateBaseURI("http://test.xyz/"); - // FIXME event would be nice - }); - - it("SHOULD use anchor for tokenURI", async function () { - // Create the message to sign - const { abnftContract, anchor, maintainer, alice, mallory } = await loadFixture(deployABTandMintTokenToAlice); - await abnftContract.connect(maintainer).updateBaseURI("http://test.xyz/collection/"); - - expect(await abnftContract.tokenURI(1)) - .to.be.equal("http://test.xyz/collection/0xaa0c61ccb0c754f1c68c699990a456c6073aaa28109c1bd83880c49dcece3d65"); - }); -}); - - -}); diff --git a/assets/eip-6956/test/ERC6956Full.ts b/assets/eip-6956/test/ERC6956Full.ts deleted file mode 100644 index fd48f5ce7c2267..00000000000000 --- a/assets/eip-6956/test/ERC6956Full.ts +++ /dev/null @@ -1,445 +0,0 @@ -import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { createHash } from 'node:crypto'; -import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; -import { float } from "hardhat/internal/core/params/argumentTypes"; -import { ERC6956Authorization, merkleTestAnchors, NULLADDR, AttestedTransferLimitUpdatePolicy, invalidAnchor, createAttestationWithData} from "./commons"; -import { IERC6956AttestationLimitedInterfaceId, IERC6956InterfaceId, IERC6956FloatableInterfaceId, IERC6956ValidAnchorsInterfaceId} from "./commons"; - -export enum FloatState { - Default, // 0, inherits from floatAll - Floating, // 1 - Anchored // 2 -} - -describe("ERC6956: Asset-Bound NFT --- Full", function () { - // Fixture to deploy the abnftContract contract and assigne roles. - // Besides owner there's user, minter and burner with appropriate roles. - async function deployAbNftFixture() { - // Contracts are deployed using the first signer/account by default - const [owner, maintainer, oracle, alice, bob, mallory, hacker, carl, gasProvider ] = await ethers.getSigners(); - - return actuallyDeploy(10, AttestedTransferLimitUpdatePolicy.FLEXIBLE); - } - - async function deployAbNftAndMintTokenToAliceFixture() { - // Contracts are deployed using the first signer/account by default - const {abnftContract, merkleTree, owner, maintainer, oracle, alice, bob, mallory, hacker, carl, gasProvider} = await deployAbNftFixture(); - - const anchor = merkleTestAnchors[0][0]; - const [mintAttestationAlice, dataAlice] = await createAttestationWithData(alice.address, anchor, oracle, merkleTree); // Mint to alice - - const expectedTokenId = 1; - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](mintAttestationAlice, dataAlice)) - .to.emit(abnftContract, "Transfer") // Standard ERC721 event - .withArgs(NULLADDR, alice.address, expectedTokenId) - - return { abnftContract, merkleTree, owner, maintainer, oracle, mintAttestationAlice, anchor, alice, bob, mallory, hacker, carl, gasProvider }; - } - - async function actuallyDeploy(attestationLimitPerAnchor: number, limitUpdatePolicy: AttestedTransferLimitUpdatePolicy) { - const [owner, maintainer, oracle, alice, bob, mallory, hacker, carl, gasProvider ] = await ethers.getSigners(); - - const AbNftContract = await ethers.getContractFactory("ERC6956Full"); - - const abnftContract = await AbNftContract.connect(owner).deploy("Asset-Bound NFT test", "ABNFT", limitUpdatePolicy); - await abnftContract.connect(owner).updateMaintainer(maintainer.address, true); - - // set attestation Limit per anchor - await abnftContract.connect(maintainer).updateGlobalAttestationLimit(attestationLimitPerAnchor); - - // Create Merkle Tree - const merkleTree = StandardMerkleTree.of(merkleTestAnchors, ["bytes32"]); - await abnftContract.connect(maintainer).updateValidAnchors(merkleTree.root); - - await expect(abnftContract.connect(maintainer).updateOracle(oracle.address, true)) - .to.emit(abnftContract, "OracleUpdate") - .withArgs(oracle.address, true); - - // Uncomment to see the merkle tree. - // console.log(merkleTree.dump()); - - return { abnftContract, merkleTree, owner, maintainer, oracle, alice, bob, mallory, hacker, carl, gasProvider }; - } - - async function deployForAttestationLimit(limit: number, policy: AttestedTransferLimitUpdatePolicy) { - return actuallyDeploy(limit, policy); - } - - describe("Deployment & Settings", function () { - it("Should implement EIP-165 support the EIP-6956 interface", async function () { - const { abnftContract } = await loadFixture(deployAbNftFixture); - - expect(await abnftContract.supportsInterface(IERC6956InterfaceId)).to.equal(true); - expect(await abnftContract.supportsInterface(IERC6956FloatableInterfaceId)).to.equal(true); - expect(await abnftContract.supportsInterface(IERC6956ValidAnchorsInterfaceId)).to.equal(true); - expect(await abnftContract.supportsInterface(IERC6956AttestationLimitedInterfaceId)).to.equal(true); - }); - }); - - -describe("Valid Anchors (merkle-trees)", function () { - it("SHOULDN't allow attesting arbitrary anchors", async function() { - const { abnftContract, merkleTree, maintainer, oracle, alice, hacker } = await loadFixture(deployAbNftFixture); - - // Publish root node of a made up tree, s.t. all proofs we use are from a different tree - const madeUpRootNode = '0xaaaaaaaab0c754f1c68c699990a456c6073aaa28109c1bd83880c49dcece3f65'; // random string - abnftContract.connect(maintainer).updateValidAnchors(madeUpRootNode) - const anchor = merkleTestAnchors[0][0]; - - // Let the oracle create an valid attestation (from the oracle's view) - const [attestationAlice, dataAlice] = await createAttestationWithData(alice.address, anchor, oracle, merkleTree); // Mint to alice - await expect(abnftContract.connect(hacker)["transferAnchor(bytes,bytes)"](attestationAlice, dataAlice)) - .to.revertedWith("ERC6956-E26") - }); -}); - -describe("Anchor-Floating", function () { - it("SHOULD only allow maintainer to specify canStartFloating and canStopFloating", async function () { - const { abnftContract, merkleTree, owner, maintainer, mallory } = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - - await expect(abnftContract.connect(mallory).updateFloatingAuthorization(ERC6956Authorization.ALL, ERC6956Authorization.ALL)) - .to.revertedWith("ERC6956-E1"); - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.ISSUER)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.ISSUER, maintainer.address); - }); - - it("SHOULD only allow maintainer to modify floatAll behavior w/o affecting previous tokens", async function () { - const { abnftContract, merkleTree, anchor, owner, maintainer, alice, bob, oracle, mallory, gasProvider } = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - - // --------------------------------------------------------------------------------- ALL FLOATING = FALSE - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.ISSUER)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.ISSUER, maintainer.address); - - // anchor does not float by default - // FLOATING ALL, so both have to float - expect(await abnftContract.floating(anchor)) - .to.be.equal(false); // one was used to mint - - // Now alice, as the owner decides to make it explicitely floatable - expect(await abnftContract.connect(alice).float(anchor, FloatState.Floating)) - .to.emit(abnftContract, "FloatingStateChange") - .withArgs(anchor, 1, FloatState.Floating, alice.address) - - // It is now explicitely floatable - expect(await abnftContract.floating(anchor)) - .to.be.equal(true); // one was used to mint - - - // Mallory mustnot be able to change default behavior - await expect(abnftContract.connect(mallory).floatAll(true)) - .to.revertedWith("ERC6956-E1"); - - // --------------------------------------------------------------------------------- ALL FLOATING = TRUE - // Maintainer must be able to update - await expect(abnftContract.connect(maintainer).floatAll(true)) - .to.emit(abnftContract, "FloatingAllStateChange") - .withArgs(true, maintainer.address); - - const implicitFloatingAnchor = merkleTestAnchors[1][0]; - const [mintToBobAttestation, mintToBobData] = await createAttestationWithData(bob.address, implicitFloatingAnchor, oracle, merkleTree); // Mint to alice - const expectedTokenId = 2; - - // Mint a new token... - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](mintToBobAttestation, mintToBobData)) - .to.emit(abnftContract, "Transfer") - .withArgs(NULLADDR, bob.address, expectedTokenId) - - // FLOATING ALL, so both have to float - expect(await abnftContract.floating(anchor)) - .to.be.equal(true); // one was used to mint - - expect(await abnftContract.floating(implicitFloatingAnchor)) - .to.be.equal(true); // one was used to mint - - // --------------------------------------------------------------------------------- ALL FLOATING = FALSE - // REVERT THE FLOATING .... Maintainer must be able to update - await expect(abnftContract.connect(maintainer).floatAll(false)) - .to.emit(abnftContract, "FloatingAllStateChange") - .withArgs(false, maintainer.address); - - - // we expect the original anchor for alice to not be floating, and the new anchor from float to be floating - expect(await abnftContract.floating(anchor)) - .to.be.equal(true); - - expect(await abnftContract.floating(implicitFloatingAnchor)) - .to.be.equal(false); // this was only floating because of floatAll at the time of mint... - }); - - it("SHOULD allow owner to float token only when OWNER is allowed", async function () { - const { abnftContract, anchor, maintainer, alice, mallory } = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.ASSET_AND_ISSUER, ERC6956Authorization.ASSET_AND_ISSUER)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.ASSET_AND_ISSUER, ERC6956Authorization.ASSET_AND_ISSUER, maintainer.address); - - await expect(abnftContract.connect(alice).float(anchor, FloatState.Floating)) - .to.revertedWith("ERC6956-E21") - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.OWNER_AND_ASSET)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.OWNER_AND_ASSET, maintainer.address); - - await expect(abnftContract.connect(alice).float(anchor, FloatState.Floating)) - .to.emit(abnftContract, "FloatingStateChange") - .withArgs(anchor, tokenId, FloatState.Floating, alice.address); - }); - - it("SHOULD only allow owner to transfer token when floating", async function () { - const { abnftContract, anchor, maintainer, alice, bob, mallory } = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.OWNER_AND_ASSET)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.OWNER_AND_ASSET, maintainer.address); - - await expect(abnftContract.connect(alice).float(anchor, FloatState.Floating)) - .to.emit(abnftContract, "FloatingStateChange") - .withArgs(anchor, tokenId, FloatState.Floating, alice.address); - - await expect(abnftContract.connect(mallory).transferFrom(alice.address, mallory.address, tokenId)) - .to.revertedWith("ERC721: caller is not token owner or approved"); - - await expect(abnftContract.connect(alice).transferFrom(alice.address, bob.address, tokenId)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address,bob.address, tokenId); - }); - - it("SHOULDN'T allow owner to transfer token when explicitely marked anchored", async function () { - const { abnftContract, anchor, maintainer, alice, bob, mallory } = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.ISSUER, ERC6956Authorization.ISSUER)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.ISSUER, ERC6956Authorization.ISSUER, maintainer.address); - - // Maintainer must be able to update - await expect(abnftContract.connect(maintainer).floatAll(true)) - .to.emit(abnftContract, "FloatingAllStateChange") - .withArgs(true, maintainer.address); - - expect(await abnftContract.floating(anchor)) - .to.be.equal(true); // one was used to mint - - await expect(abnftContract.connect(maintainer).float(anchor, FloatState.Anchored)) - .to.emit(abnftContract, "FloatingStateChange") - .withArgs(anchor, tokenId, FloatState.Anchored, maintainer.address); - - expect(await abnftContract.floating(anchor)) - .to.be.equal(false); // one was used to mint - - await expect(abnftContract.connect(alice).transferFrom(alice.address, bob.address, tokenId)) - .to.revertedWith("ERC6956-E5") - }); - - - it("SHOULD allow maintainer to float ANY token only when ISSUER is allowed", async function () { - const { abnftContract, anchor, maintainer, mallory } = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.OWNER_AND_ASSET)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.OWNER_AND_ASSET, ERC6956Authorization.OWNER_AND_ASSET, maintainer.address); - - await expect(abnftContract.connect(maintainer).float(anchor, FloatState.Floating)) - .to.revertedWith("ERC6956-E21") - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.ISSUER, ERC6956Authorization.ISSUER)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.ISSUER, ERC6956Authorization.ISSUER, maintainer.address); - - await expect(abnftContract.connect(maintainer).float(anchor, FloatState.Floating)) - .to.emit(abnftContract, "FloatingStateChange") - .withArgs(anchor, tokenId, FloatState.Floating, maintainer.address); - }); - - it("SHOULD allow maintainer to float HIS OWN token when OWNER is allowed", async function () { - const { abnftContract, anchor, alice, maintainer, oracle, merkleTree, gasProvider } = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - const tokenId = await abnftContract.tokenByAnchor(anchor); - - // Anchor should not be floating by default... - expect(await abnftContract.floating(anchor)) - .to.be.equal(false); // one was used to mint - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.OWNER, ERC6956Authorization.OWNER)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.OWNER, ERC6956Authorization.OWNER, maintainer.address); - - await expect(abnftContract.connect(maintainer).float(anchor, FloatState.Floating)) - .to.revertedWith("ERC6956-E21") - - const [attestationMaintainer, dataMaintainer] = await createAttestationWithData(maintainer.address, anchor, oracle, merkleTree); - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](attestationMaintainer, dataMaintainer)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address, maintainer.address, tokenId) - - // Now maintainer owns the token, hence he is owner and can indeed change floating - await expect(abnftContract.connect(maintainer).float(anchor, FloatState.Floating)) - .to.emit(abnftContract, "FloatingStateChange") - .withArgs(anchor, tokenId, FloatState.Floating, maintainer.address); - - expect(await abnftContract.floating(anchor)) - .to.be.equal(true); // one was used to mint - }); - - it("SHOULD allow approveAnchor followed by safeTransfer when anchor IS floating", async function() { - const { abnftContract, anchor, maintainer, oracle, merkleTree, alice, bob, gasProvider, mallory,carl} = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - const tokenId = await abnftContract.tokenByAnchor(anchor); - const [attestationBob, dataBob] = await createAttestationWithData(bob.address, anchor, oracle, merkleTree); // Transfer to bob - - await expect(abnftContract.connect(maintainer).updateFloatingAuthorization(ERC6956Authorization.OWNER, ERC6956Authorization.OWNER)) - .to.emit(abnftContract, "FloatingAuthorizationChange") - .withArgs(ERC6956Authorization.OWNER, ERC6956Authorization.OWNER, maintainer.address); - - // somebody approves himself via attestation approves bob to act on her behalf - await expect(abnftContract.connect(gasProvider)["approveAnchor(bytes,bytes)"](attestationBob,dataBob)) - .to.emit(abnftContract, "Approval") // Standard ERC721 event - .withArgs(await abnftContract.ownerOf(tokenId), bob.address, tokenId); - - // Should not allow mallory to transfer, since only bob is approved - await expect(abnftContract.connect(mallory).transferFrom(alice.address, bob.address, 1)) - .to.revertedWith("ERC721: caller is not token owner or approved"); - - // Bob makes it floatable (which is possible, because he is approved) - await expect(abnftContract.connect(bob).float(anchor, FloatState.Floating)) - .to.emit(abnftContract, "FloatingStateChange") - .withArgs(anchor, tokenId, FloatState.Floating, bob.address); - - // Bob transfers it... - await expect(abnftContract.connect(bob).transferFrom(alice.address, carl.address, tokenId)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address,carl.address, tokenId); - }) -}); - -describe("Attested Transfer Limits", function () { - it("SHOULD count attested transfers (transfer, burn, approve)", async function () { - const { abnftContract, anchor, maintainer, oracle, merkleTree, alice, bob, gasProvider, mallory,carl} = await loadFixture(deployAbNftAndMintTokenToAliceFixture); - const tokenId = await abnftContract.tokenByAnchor(anchor); - const [attestationBob, dataBob] = await createAttestationWithData(bob.address, anchor, oracle, merkleTree); // Mint to alice - const [attestationCarl, dataCarl] = await createAttestationWithData(carl.address, anchor, oracle, merkleTree); // Mint to alice - - - // Transfers shall be counted - also the one from the fixture - expect(await abnftContract.attestationsUsedByAnchor(anchor)) - .to.be.equal(1); - - // Should increase count by 1 - await expect(abnftContract["approveAnchor(bytes,bytes)"](attestationBob, dataBob)) - .to.emit(abnftContract, "Approval") // Standard ERC721 event - .withArgs(await abnftContract.ownerOf(tokenId), bob.address, tokenId); - - // Should increase count by 1 - await expect(abnftContract["burnAnchor(bytes,bytes)"](attestationCarl, dataCarl)) - .to.emit(abnftContract, "Transfer") - .withArgs(alice.address, NULLADDR, tokenId); - - // InitialMint + Approve + Burns shall also be counted - also the one from the fixture - expect(await abnftContract.attestationsUsedByAnchor(anchor)) - .to.be.equal(3); - - // Should return 0 for invalid anchors - expect(await abnftContract.attestationsUsedByAnchor(invalidAnchor)) - .to.be.equal(0); - }); - - it("SHOULD allow maintainer to update global attestation limit", async function () { - const { abnftContract, maintainer, oracle, merkleTree, alice, bob, gasProvider, mallory,carl} = await deployForAttestationLimit(10, AttestedTransferLimitUpdatePolicy.FLEXIBLE); - - await expect(abnftContract.connect(mallory).updateGlobalAttestationLimit(5)) - .to.revertedWith("ERC6956-E1"); - - // Should be able to update - await expect(abnftContract.connect(maintainer).updateGlobalAttestationLimit(5)) - .to.emit(abnftContract, "GlobalAttestationLimitUpdate") // Standard ERC721 event - .withArgs(5, maintainer.address); - - // Check effect, but requesting transfers left from a non-existent anchor - expect(await abnftContract.attestationUsagesLeft(invalidAnchor)) - .to.be.equal(5); - }); - - it("Should allow maintainer to update anchor-based attestation limit w/o changing global limits", async function () { - const globalLimit = 10; - const specificAnchorLimit = 5; - const { abnftContract, maintainer, oracle, merkleTree, alice, bob, gasProvider, mallory,carl} = await deployForAttestationLimit(globalLimit, AttestedTransferLimitUpdatePolicy.FLEXIBLE); - - const anchor = merkleTestAnchors[0][0]; - const [mintAttestationAlice, mintDataAlice] = await createAttestationWithData(alice.address, anchor, oracle, merkleTree); // Mint to alice - const tokenId = 1; - - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](mintAttestationAlice, mintDataAlice)) - .to.emit(abnftContract, "Transfer") // Standard ERC721 event - .withArgs(NULLADDR, alice.address, tokenId); - - - // Note that an anchor does not need to exist yet for playing with the limits - // Check effect, but requesting transfers left from a non-existent anchor - expect(await abnftContract.attestationUsagesLeft(invalidAnchor)) - .to.be.equal(globalLimit); - - // Should be able to update - await expect(abnftContract.connect(maintainer).updateAttestationLimit(anchor, specificAnchorLimit)) - .to.emit(abnftContract, "AttestationLimitUpdate") // Standard ERC721 event - .withArgs(anchor, tokenId, specificAnchorLimit, maintainer.address); - - // Check unchanged global effect, but requesting transfers left from a non-existent anchor - expect(await abnftContract.attestationUsagesLeft(invalidAnchor)) - .to.be.equal(globalLimit); - - // Check verify effect - expect(await abnftContract.attestationUsagesLeft(anchor)) - .to.be.equal(specificAnchorLimit-1); // 1 has been used to mint - }); - - it("Should enforce anchor limits (global + local)", async function () { - const globalLimit = 2; - const specificAnchorLimit = 1; - const { abnftContract, maintainer, oracle, merkleTree, alice, bob, gasProvider, mallory,carl, hacker} = await deployForAttestationLimit(globalLimit, AttestedTransferLimitUpdatePolicy.FLEXIBLE); - const anchor = merkleTestAnchors[0][0]; // can be transferred twice - const limitedAnchor = merkleTestAnchors[1][0]; // can be transferred once - - const [anchorToAlice, anchorToAliceData] = await createAttestationWithData(alice.address, anchor, oracle, merkleTree); // Mint to alice - const [anchorToBob, anchorToBobData] = await createAttestationWithData(bob.address, anchor, oracle, merkleTree); // Transfer to bob - const [anchorToHacker, anchorToHackerData] = await createAttestationWithData(hacker.address, anchor, oracle, merkleTree); // Limit reached! - - const [limitedAnchorToCarl, limitedAnchorToCarlData] = await createAttestationWithData(carl.address, limitedAnchor, oracle, merkleTree); // Mint to carl - const [limitedAnchorToMallory, limitedAnchorToMalloryData] = await createAttestationWithData(mallory.address, limitedAnchor, oracle, merkleTree); // Limit reached! - - expect(await abnftContract.attestationUsagesLeft(anchor)) - .to.be.equal(globalLimit); - - // ####################################### FIRST ANCHOR - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](anchorToAlice, anchorToAliceData)) - .to.emit(abnftContract, "Transfer"); - - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](anchorToBob, anchorToBobData)) - .to.emit(abnftContract, "Transfer"); - - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](anchorToHacker, anchorToHackerData)) - .to.revertedWith("ERC6956-E24"); - - // ###################################### SECOND ANCHOR - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](limitedAnchorToCarl, limitedAnchorToCarlData)) - .to.emit(abnftContract, "Transfer"); - - // Update anchor based limit - await expect(abnftContract.connect(maintainer).updateAttestationLimit(limitedAnchor, specificAnchorLimit)) - .to.emit(abnftContract, "AttestationLimitUpdate") - .withArgs(limitedAnchor, 2, specificAnchorLimit, maintainer.address); - - expect(await abnftContract.attestationUsagesLeft(limitedAnchor)) - .to.be.equal(specificAnchorLimit-1); // one was used to mint - - await expect(abnftContract.connect(gasProvider)["transferAnchor(bytes,bytes)"](limitedAnchorToMallory, limitedAnchorToMalloryData)) - .to.revertedWith("ERC6956-E24"); - }); -}); - -}); diff --git a/assets/eip-6956/test/commons.ts b/assets/eip-6956/test/commons.ts deleted file mode 100644 index 9b411c3e970bec..00000000000000 --- a/assets/eip-6956/test/commons.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { ethers } from "hardhat"; -import { createHash } from 'node:crypto'; -import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; -import { float } from "hardhat/internal/core/params/argumentTypes"; - - -export enum ERC6956Authorization { - NONE,// = 0, // None of the above - a 1:1 relationship is maintained - OWNER,// = (1 << 1), // The owner of the token, i.e. the digital representation - ISSUER,// = (1 << 2), // The issuer of the tokens, i.e. this smart contract - ASSET,// = (1<< 3), // The asset, i.e. via attestation - OWNER_AND_ISSUER,// = (1<<1) | (1<<2), - OWNER_AND_ASSET,// = (1<<1) | (1<<3), - ASSET_AND_ISSUER,// = (1<<3) | (1<<2), - ALL// = (1<<1) | (1<<2) | (1<<3) // Owner + Issuer + Asset - } - -export enum ERC6956Role { - OWNER, - ISSUER, - ASSET - } - -export enum AttestedTransferLimitUpdatePolicy { - IMMUTABLE, - INCREASE_ONLY, - DECREASE_ONLY, - FLEXIBLE - } - -export const invalidAnchor = '0x' + createHash('sha256').update('TestAnchor1239').digest('hex'); -export const NULLADDR = ethers.utils.getAddress('0x0000000000000000000000000000000000000000'); - - // Needs to be an odd number of anchors to test the edge case of the merkle- - // tree: Nodes with only one leaf. - // Also: When building the tree (see buildMerkleTree fixture) those hashes are - // hashed again. This is intended because of the way Merkle-Proof and our - // smart contract works: - // Proof = H(leave) + H(L1) + H(L0) - // Our contract uses hashed anchor numbers as identifiers. - // Hence if we use direct anchor number checksums, H(leave) would - // be an actually valid anchor number on the smart contract. - export const merkleTestAnchors = [ - ['0x' + createHash('sha256').update('TestAnchor123').digest('hex')], - ['0x' + createHash('sha256').update('TestAnchor124').digest('hex')], - ['0x' + createHash('sha256').update('TestAnchor125').digest('hex')], - ['0x' + createHash('sha256').update('TestAnchor126').digest('hex')], - ['0x' + createHash('sha256').update('TestAnchor127').digest('hex')] - ] - - -export async function createAttestation(to, anchor, signer, validStartTime= 0) { - const attestationTime = Math.floor(Date.now() / 1000.0); // Now in seconds - const expiryTime = attestationTime + 5 * 60; // 5min valid - - const messageHash = ethers.utils.solidityKeccak256(["address", "bytes32", "uint256", 'uint256', "uint256"], [to, anchor, attestationTime, validStartTime, expiryTime]); - const sig = await signer.signMessage(ethers.utils.arrayify(messageHash)); - - return ethers.utils.defaultAbiCoder.encode(['address', 'bytes32', 'uint256', 'uint256', 'uint256', 'bytes'], [to, anchor, attestationTime, validStartTime, expiryTime, sig]); -} - - -export async function createAttestationWithData(to, anchor, signer, merkleTree, validStartTime= 0) { - - const attestation = await createAttestation(to, anchor, signer, validStartTime); // Now in seconds - - const proof = merkleTree.getProof([anchor]); - const data = ethers.utils.defaultAbiCoder.encode(['bytes32[]'], [proof]) - - return [attestation, data]; -} - - -export const IERC6956InterfaceId = '0xa9cf7635'; -export const IERC6956AttestationLimitedInterfaceId ='0x75a2e933' -export const IERC6956FloatableInterfaceId = '0xf82773f7'; -export const IERC6956ValidAnchorsInterfaceId = '0x051c9bd8'; diff --git a/assets/eip-6960/eip-6960-dual-layer-token-dlt.png b/assets/eip-6960/eip-6960-dual-layer-token-dlt.png deleted file mode 100644 index d4db3c29e9b2ec..00000000000000 Binary files a/assets/eip-6960/eip-6960-dual-layer-token-dlt.png and /dev/null differ diff --git a/assets/eip-6982/.gitignore b/assets/eip-6982/.gitignore deleted file mode 100644 index b102f6f5f59ebe..00000000000000 --- a/assets/eip-6982/.gitignore +++ /dev/null @@ -1,68 +0,0 @@ -*.swp -*.swo - -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed -allFiredEvents -scTopics - -# Coverage directory used by tools like istanbul -coverage -coverage.json -coverageEnv - -# node-waf configuration -.lock-wscript - -# Dependency directory -node_modules - -# Debug log from npm -npm-debug.log - -# local env variables -.env -.env.* -env.* - -# truffle build directory -build/ - -# macOS -.DS_Store - -# truffle -.node-xmlhttprequest-* - -# IntelliJ IDE -.idea - -# docs artifacts -docs/modules/api - -# only used to package @openzeppelin/contracts -contracts/build/ -contracts/README.md - -# temporary artifact from solidity-coverage -.coverage_artifacts -.coverage_cache -.coverage_contracts - -# hardhat -cache -artifacts - -#local data -db -/etherscan -.openzeppelin -/test/fixtures/csv -/export -pnpm-lock.yaml diff --git a/assets/eip-6982/README.md b/assets/eip-6982/README.md deleted file mode 100644 index abec2293f500db..00000000000000 --- a/assets/eip-6982/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# EIP 6982 implementation - -As a reference implementation of EIP-6982 we use the Nduja Labs ERC721Lockable contract. - -To run the tests, run the following commands: - -```shell -npm i -g pnpm -pnpm i -pnpm test -``` diff --git a/assets/eip-6982/contracts/ERC721Lockable.sol b/assets/eip-6982/contracts/ERC721Lockable.sol deleted file mode 100644 index c6a02c47fab2df..00000000000000 --- a/assets/eip-6982/contracts/ERC721Lockable.sol +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -// Authors: Francesco Sullo - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./IERC721Lockable.sol"; -import "./IERC6982.sol"; - -// This is an example of lockable ERC721 using IERC6982 as basic interface - -contract ERC721Lockable is IERC6982, IERC721Lockable, Ownable, ERC721, ERC721Enumerable { - using Address for address; - - mapping(address => bool) private _locker; - mapping(uint256 => address) private _lockedBy; - - bool internal _defaultLocked; - - modifier onlyLocker() { - require(_locker[_msgSender()], "Not a locker"); - _; - } - - constructor( - string memory name, - string memory symbol, - bool defaultLocked_ - ) ERC721(name, symbol) { - updateDefaultLocked(defaultLocked_); - } - - function defaultLocked() external view override returns (bool) { - return _defaultLocked; - } - - function updateDefaultLocked(bool defaultLocked_) public onlyOwner { - _defaultLocked = defaultLocked_; - emit DefaultLocked(defaultLocked_); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId, - uint256 batchSize - ) internal override(ERC721, ERC721Enumerable) { - require( - // during minting - from == address(0) || - // later - !locked(tokenId), - "Token is locked" - ); - super._beforeTokenTransfer(from, to, tokenId, batchSize); - } - - function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { - return - interfaceId == type(IERC6982).interfaceId || - interfaceId == type(IERC721Lockable).interfaceId || - super.supportsInterface(interfaceId); - } - - function locked(uint256 tokenId) public view virtual override returns (bool) { - require(_exists(tokenId), "Token does not exist"); - return _lockedBy[tokenId] != address(0); - } - - function lockerOf(uint256 tokenId) public view virtual override returns (address) { - return _lockedBy[tokenId]; - } - - function isLocker(address locker) public view virtual override returns (bool) { - return _locker[locker]; - } - - function setLocker(address locker) external virtual override onlyOwner { - require(locker.isContract(), "Locker not a contract"); - _locker[locker] = true; - emit LockerSet(locker); - } - - function removeLocker(address locker) external virtual override onlyOwner { - require(_locker[locker], "Not an active locker"); - delete _locker[locker]; - emit LockerRemoved(locker); - } - - function hasLocks(address owner) public view virtual override returns (bool) { - uint256 balance = balanceOf(owner); - for (uint256 i = 0; i < balance; i++) { - uint256 id = tokenOfOwnerByIndex(owner, i); - if (locked(id)) { - return true; - } - } - return false; - } - - function lock(uint256 tokenId) external virtual override onlyLocker { - // locker must be approved to mark the token as locked - require(isLocker(_msgSender()), "Not an authorized locker"); - require(getApproved(tokenId) == _msgSender() || isApprovedForAll(ownerOf(tokenId), _msgSender()), "Locker not approved"); - _lockedBy[tokenId] = _msgSender(); - emit Locked(tokenId, true); - } - - function unlock(uint256 tokenId) external virtual override onlyLocker { - // will revert if token does not exist - require(_lockedBy[tokenId] == _msgSender(), "Wrong locker"); - delete _lockedBy[tokenId]; - emit Locked(tokenId, false); - } - - // emergency function in case a compromised locker is removed - function unlockIfRemovedLocker(uint256 tokenId) external virtual override { - require(locked(tokenId), "Not a locked tokenId"); - require(!_locker[_lockedBy[tokenId]], "Locker is still active"); - require(ownerOf(tokenId) == _msgSender(), "Not the asset owner"); - delete _lockedBy[tokenId]; - emit ForcefullyUnlocked(tokenId); - } - - // manage approval - - function approve(address to, uint256 tokenId) public virtual override(IERC721, ERC721) { - require(!locked(tokenId), "Locked asset"); - super.approve(to, tokenId); - } - - function getApproved(uint256 tokenId) public view virtual override(IERC721, ERC721) returns (address) { - if (locked(tokenId) && lockerOf(tokenId) != _msgSender()) { - return address(0); - } - return super.getApproved(tokenId); - } - - function setApprovalForAll(address operator, bool approved) public virtual override(IERC721, ERC721) { - require(!approved || !hasLocks(_msgSender()), "At least one asset is locked"); - super.setApprovalForAll(operator, approved); - } - - function isApprovedForAll(address owner, address operator) public view virtual override(IERC721, ERC721) returns (bool) { - if (hasLocks(owner)) { - return false; - } - return super.isApprovedForAll(owner, operator); - } -} diff --git a/assets/eip-6982/contracts/IERC6982.sol b/assets/eip-6982/contracts/IERC6982.sol deleted file mode 100644 index 06ac78132b978e..00000000000000 --- a/assets/eip-6982/contracts/IERC6982.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.9; - -// ERC165 interfaceId 0x6b61a747 -interface IERC6982 { - // This event MUST be emitted upon deployment of the contract, establishing - // the default lock status for any tokens that will be minted in the future. - // If the default lock status changes for any reason, this event - // MUST be re-emitted to update the default status for all tokens. - // Note that emitting a new DefaultLocked event does not affect the lock - // status of any tokens for which a Locked event has previously been emitted. - event DefaultLocked(bool locked); - - // This event MUST be emitted whenever the lock status of a specific token - // changes, effectively overriding the default lock status for this token. - event Locked(uint256 indexed tokenId, bool locked); - - // This function returns the current default lock status for tokens. - // It reflects the value set by the latest DefaultLocked event. - function defaultLocked() external view returns (bool); - - // This function returns the lock status of a specific token. - // If no Locked event has been emitted for a given tokenId, it MUST return - // the value that defaultLocked() returns, which represents the default - // lock status. - // This function MUST revert if the token does not exist. - function locked(uint256 tokenId) external view returns (bool); -} diff --git a/assets/eip-6982/contracts/IERC721Lockable.sol b/assets/eip-6982/contracts/IERC721Lockable.sol deleted file mode 100644 index 77b2ff89e092f6..00000000000000 --- a/assets/eip-6982/contracts/IERC721Lockable.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -// Author: -// Francesco Sullo - -interface IERC721Lockable { - event LockerSet(address locker); - event LockerRemoved(address locker); - event ForcefullyUnlocked(uint256 tokenId); - - // tells if a token is locked. Removed to extend IERC5192 - // function locked(uint256 tokenID) external view returns (bool); - - // tells the address of the contract which is locking a token - function lockerOf(uint256 tokenID) external view returns (address); - - // tells if a contract is a locker - function isLocker(address _locker) external view returns (bool); - - // set a locker, if the actor that is locking it is a contract, it - // should be approved - // It should emit a LockerSet event - function setLocker(address pool) external; - - // remove a locker - // It should emit a LockerRemoved event - function removeLocker(address pool) external; - - // tells if an NFT has any locks on it - // The function is called internally and externally - function hasLocks(address owner) external view returns (bool); - - // locks an NFT - // It should emit a Locked event - function lock(uint256 tokenID) external; - - // unlocks an NFT - // It should emit a Unlocked event - function unlock(uint256 tokenID) external; - - // unlock an NFT if the locker is removed - // This is an emergency function called by the token owner or a DAO - // It should emit a ForcefullyUnlocked event - function unlockIfRemovedLocker(uint256 tokenID) external; -} diff --git a/assets/eip-6982/contracts/mocks/ERC721LockableMock.sol b/assets/eip-6982/contracts/mocks/ERC721LockableMock.sol deleted file mode 100644 index 94b62167c20f4b..00000000000000 --- a/assets/eip-6982/contracts/mocks/ERC721LockableMock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -// Authors: Francesco Sullo - -import "../ERC721Lockable.sol"; - -contract ERC721LockableMock is ERC721Lockable { - - uint public latestTokenId; - - constructor(string memory name, string memory symbol) ERC721Lockable(name, symbol, false) {} - - function mint (address to, uint256 amount) public { - for (uint256 i = 0; i < amount; i++) { - // inefficient, but this is a mock :-) - _safeMint(to, ++latestTokenId); - } - } -} diff --git a/assets/eip-6982/contracts/mocks/MyLocker.sol b/assets/eip-6982/contracts/mocks/MyLocker.sol deleted file mode 100644 index 5ceff78ac7359c..00000000000000 --- a/assets/eip-6982/contracts/mocks/MyLocker.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -// Authors: Francesco Sullo - -import "../IERC721Lockable.sol"; - -contract MyLocker { - function lock(address asset, uint256 id) public { - IERC721Lockable(asset).lock(id); - } - - function unlock(address asset, uint256 id) public { - IERC721Lockable(asset).unlock(id); - } -} diff --git a/assets/eip-6982/hardhat.config.js b/assets/eip-6982/hardhat.config.js deleted file mode 100644 index 0d430b5335f8c0..00000000000000 --- a/assets/eip-6982/hardhat.config.js +++ /dev/null @@ -1,24 +0,0 @@ -require("@nomiclabs/hardhat-waffle"); - -// You need to export an object to set up your config -// Go to https://hardhat.org/config/ to learn more - -/** - * @type import('hardhat/config').HardhatUserConfig - */ -module.exports = { - solidity: { - version: "0.8.19", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, - networks: { - hardhat: { - blockGasLimit: 10000000, - } - }, -}; diff --git a/assets/eip-6982/package.json b/assets/eip-6982/package.json deleted file mode 100644 index b7fd956dc41b0e..00000000000000 --- a/assets/eip-6982/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "erc6982-example", - "version": "0.1.0", - "scripts": { - "test": "npx hardhat test", - "compile": "npx hardhat compile" - }, - "author": { - "name": "Francesco Sullo", - "email": "francesco@sullo.co" - }, - "license": "MIT", - "devDependencies": { - "@nomiclabs/hardhat-ethers": "^2.2.3", - "@nomiclabs/hardhat-waffle": "^2.0.5", - "@openzeppelin/hardhat-upgrades": "^1.25.3", - "chai": "^4.3.7", - "ethereum-waffle": "^3.4.4", - "ethers": "^5.7.2", - "hardhat": "^2.14.0", - "typescript": "^4.9.5" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.8.3", - "@openzeppelin/contracts-upgradeable": "^4.8.3" - } -} diff --git a/assets/eip-6982/test/Lockable.test.js b/assets/eip-6982/test/Lockable.test.js deleted file mode 100644 index 279f13839b93e1..00000000000000 --- a/assets/eip-6982/test/Lockable.test.js +++ /dev/null @@ -1,42 +0,0 @@ -const {expect} = require("chai"); -const { deployContractUpgradeable, deployContract} = require("./helpers"); - -describe("ERC721Lockable", function () { - let myToken; - let myLocker; - - let owner, holder, holder2; - - before(async function () { - [owner, holder, holder2] = await ethers.getSigners(); - }); - - beforeEach(async function () { - // myPool = await deployContract("MyPlayer"); - myToken = await deployContract("ERC721LockableMock", "My token", "NFT"); - myLocker = await deployContract("MyLocker") - }); - - it("should verify the flow", async function () { - - expect(await myToken.supportsInterface("0x2e4e0d27")).equal(true); - - await myToken.mint(holder.address, 5); - - await myToken.setLocker(myLocker.address); - expect(await myToken.isLocker(myLocker.address)).equal(true); - - await expect(myLocker.lock(myToken.address, 2)).revertedWith("Locker not approved"); - - await myToken.connect(holder).approve(myLocker.address, 2); - await myLocker.lock(myToken.address, 2); - - expect(await myToken.locked(2)).equal(true); - - await expect(myToken.connect(holder).transferFrom(holder.address, holder2.address, 2)).revertedWith("Token is locked"); - - await expect(myToken.connect(holder).transferFrom(holder.address, holder2.address, 3)).emit(myToken, "Transfer").withArgs(holder.address, holder2.address, 3); - - }); - -}); diff --git a/assets/eip-6982/test/helpers.js b/assets/eip-6982/test/helpers.js deleted file mode 100644 index 8aab232fdeec10..00000000000000 --- a/assets/eip-6982/test/helpers.js +++ /dev/null @@ -1,67 +0,0 @@ -const {assert} = require("chai"); - -const Helpers = { - initEthers(ethers) { - this.ethers = ethers; - }, - - async assertThrowsMessage(promise, message) { - try { - await promise; - console.log("It did not throw :-("); - assert.isTrue(false); - } catch (e) { - const shouldBeTrue = e.message.indexOf(message) > -1; - if (!shouldBeTrue) { - console.error("Expected:", message); - console.error("Returned:", e.message); - // console.log(e) - } - assert.isTrue(shouldBeTrue); - } - }, - - async deployContractBy(contractName, owner, ...args) { - const Contract = await this.ethers.getContractFactory(contractName); - const contract = await Contract.connect(owner).deploy(...args); - await contract.deployed(); - return contract; - }, - - async deployContract(contractName, ...args) { - const Contract = await this.ethers.getContractFactory(contractName); - const contract = await Contract.deploy(...args); - await contract.deployed(); - return contract; - }, - - async deployContractUpgradeable(contractName, args = []) { - const Contract = await this.ethers.getContractFactory(contractName); - const contract = await upgrades.deployProxy(Contract, args); - await contract.deployed(); - return contract; - }, - - async signPackedData( - hash, - // hardhat account #4, starting from #0 - privateKey = "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a" - ) { - const signingKey = new this.ethers.utils.SigningKey(privateKey); - const signedDigest = signingKey.signDigest(hash); - return this.ethers.utils.joinSignature(signedDigest); - }, - - async getTimestamp() { - return (await this.ethers.provider.getBlock()).timestamp; - }, - - addr0: "0x0000000000000000000000000000000000000000", - - async increaseBlockTimestampBy(offset) { - await this.ethers.provider.send("evm_increaseTime", [offset]); - await this.ethers.provider.send("evm_mine"); - }, -}; - -module.exports = Helpers; diff --git a/assets/eip-7007/.gitignore b/assets/eip-7007/.gitignore deleted file mode 100644 index 00dad773492c83..00000000000000 --- a/assets/eip-7007/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -node_modules -.env -coverage -coverage.json -typechain -typechain-types - -# Hardhat files -cache -artifacts - diff --git a/assets/eip-7007/README.md b/assets/eip-7007/README.md deleted file mode 100644 index cc8e3a0f2fb7c8..00000000000000 --- a/assets/eip-7007/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# ERC-7007 Reference Implementation - -This is a WIP implementation of ERC-7007 based on the discussions in the [EIP-7007 issue thread](https://github.com/ethereum/EIPs/issues/7007). - -## Setup -Run `npm install` in the root directory. - -## Testing -Try running some of the following tasks: - -```shell -npx hardhat help -npx hardhat test -REPORT_GAS=true npx hardhat test -``` - -## Metadata Standard - -```json -{ - "title": "AIGC Metadata", - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Identifies the asset to which this NFT represents" - }, - "description": { - "type": "string", - "description": "Describes the asset to which this NFT represents" - }, - "image": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - }, - - "prompt": { - "type": "string", - "description": "Identifies the prompt from which this AIGC NFT generated" - }, - "seed": { - "type": "uint256", - "description": "Identifies the seed from which this AIGC NFT generated" - }, - "aigc_type": { - "type": "string", - "description": "image/video/audio..." - }, - "aigc_data": { - "type": "string", - "description": "A URI pointing to a resource with mime type image/* representing the asset to which this AIGC NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." - } - } -} -``` \ No newline at end of file diff --git a/assets/eip-7007/contracts/ERC7007.sol b/assets/eip-7007/contracts/ERC7007.sol deleted file mode 100644 index 5610225acc64b1..00000000000000 --- a/assets/eip-7007/contracts/ERC7007.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "./IERC7007.sol"; -import "./IVerifier.sol"; - -/** - * @dev Implementation of the {IERC7007} interface. - */ -contract ERC7007 is ERC165, IERC7007, ERC721URIStorage { - address public immutable verifier; - - /** - * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. - */ - constructor( - string memory name_, - string memory symbol_, - address verifier_ - ) ERC721(name_, symbol_) { - verifier = verifier_; - } - - /** - * @dev See {IERC7007-mint}. - */ - function mint( - bytes calldata prompt, - bytes calldata aigcData, - string calldata uri, - bytes calldata proof - ) public virtual override returns (uint256 tokenId) { - require(verify(prompt, aigcData, proof), "ERC7007: invalid proof"); - tokenId = uint256(keccak256(prompt)); - _safeMint(msg.sender, tokenId); - string memory tokenUri = string( - abi.encodePacked( - "{", - uri, - ', "prompt": "', - string(prompt), - '", "aigc_data": "', - string(aigcData), - '"}' - ) - ); - _setTokenURI(tokenId, tokenUri); - emit Mint(tokenId, prompt, aigcData, uri, proof); - } - - /** - * @dev See {IERC7007-verify}. - */ - function verify( - bytes calldata prompt, - bytes calldata aigcData, - bytes calldata proof - ) public view virtual override returns (bool success) { - return - IVerifier(verifier).verifyProof( - proof, - abi.encodePacked(prompt, aigcData) - ); - } - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC165, ERC721, IERC165) returns (bool) { - return - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC721Metadata).interfaceId || - super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-7007/contracts/ERC7007Enumerable.sol b/assets/eip-7007/contracts/ERC7007Enumerable.sol deleted file mode 100644 index d8baed3210bb53..00000000000000 --- a/assets/eip-7007/contracts/ERC7007Enumerable.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -import "./ERC7007.sol"; -import "./IERC7007Enumerable.sol"; - -/** - * @dev Implementation of the {IERC7007Enumerable} interface. - */ -abstract contract ERC7007Enumerable is ERC7007, IERC7007Enumerable { - /** - * @dev See {IERC7007Enumerable-tokenId}. - */ - mapping(uint256 => string) public prompt; - - - /** - * @dev See {IERC7007Enumerable-prompt}. - */ - mapping(bytes => uint256) public tokenId; - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(IERC165, ERC7007) returns (bool) { - return - interfaceId == type(IERC7007Enumerable).interfaceId || - super.supportsInterface(interfaceId); - } - - /** - * @dev See {IERC7007-mint}. - */ - function mint( - bytes calldata prompt_, - bytes calldata aigcData, - string calldata uri, - bytes calldata proof - ) public virtual override(ERC7007, IERC7007) returns (uint256 tokenId_) { - tokenId_ = ERC7007.mint(prompt_, aigcData, uri, proof); - prompt[tokenId_] = string(prompt_); - tokenId[prompt_] = tokenId_; - } -} - -contract MockERC7007Enumerable is ERC7007Enumerable { - constructor( - string memory name_, - string memory symbol_, - address verifier_ - ) ERC7007(name_, symbol_, verifier_) {} -} \ No newline at end of file diff --git a/assets/eip-7007/contracts/IERC7007.sol b/assets/eip-7007/contracts/IERC7007.sol deleted file mode 100644 index 359b6b626d6318..00000000000000 --- a/assets/eip-7007/contracts/IERC7007.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -/** - * @dev Required interface of an ERC7007 compliant contract. - * Note: the ERC-165 identifier for this interface is 0x7e52e423. - */ -interface IERC7007 is IERC165, IERC721 { - /** - * @dev Emitted when `tokenId` token is minted. - */ - event Mint( - uint256 indexed tokenId, - bytes indexed prompt, - bytes indexed aigcData, - string uri, - bytes proof - ); - - /** - * @dev Mint token at `tokenId` given `prompt`, `aigcData`, `uri` and `proof`. - * - * Requirements: - * - `tokenId` must not exist.' - * - verify(`prompt`, `aigcData`, `proof`) must return true. - * - * Optional: - * - `proof` should not include `aigcData` to save gas. - */ - function mint( - bytes calldata prompt, - bytes calldata aigcData, - string calldata uri, - bytes calldata proof - ) external returns (uint256 tokenId); - - /** - * @dev Verify the `prompt`, `aigcData` and `proof`. - */ - function verify( - bytes calldata prompt, - bytes calldata aigcData, - bytes calldata proof - ) external view returns (bool success); -} diff --git a/assets/eip-7007/contracts/IERC7007Enumerable.sol b/assets/eip-7007/contracts/IERC7007Enumerable.sol deleted file mode 100644 index 6055402b80ef5d..00000000000000 --- a/assets/eip-7007/contracts/IERC7007Enumerable.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -import "./IERC7007.sol"; - -/** - * @title ERC7007 Token Standard, optional enumeration extension - * Note: the ERC-165 identifier for this interface is 0xfa1a557a. - */ -interface IERC7007Enumerable is IERC7007 { - /** - * @dev Returns the token ID given `prompt`. - */ - function tokenId(bytes calldata prompt) external view returns (uint256); - - /** - * @dev Returns the prompt given `tokenId`. - */ - function prompt(uint256 tokenId) external view returns (string calldata); -} diff --git a/assets/eip-7007/contracts/IVerifier.sol b/assets/eip-7007/contracts/IVerifier.sol deleted file mode 100644 index 3d918c70ba0c3c..00000000000000 --- a/assets/eip-7007/contracts/IVerifier.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -interface IVerifier { - function verifyProof( - bytes calldata proof, - bytes calldata public_inputs - ) external view returns (bool valid); -} diff --git a/assets/eip-7007/contracts/MockVerifier.sol b/assets/eip-7007/contracts/MockVerifier.sol deleted file mode 100644 index 4c1a7712729900..00000000000000 --- a/assets/eip-7007/contracts/MockVerifier.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.18; - -import "./IVerifier.sol"; - -contract MockVerifier is IVerifier { - function verifyProof( - bytes calldata proof, - bytes calldata public_inputs - ) external pure override returns (bool valid) { - return - public_inputs.length > 0 && keccak256(proof) == keccak256("valid"); - } -} diff --git a/assets/eip-7007/hardhat.config.js b/assets/eip-7007/hardhat.config.js deleted file mode 100644 index d5dd5f4e8ddd41..00000000000000 --- a/assets/eip-7007/hardhat.config.js +++ /dev/null @@ -1,6 +0,0 @@ -require("@nomicfoundation/hardhat-toolbox"); - -/** @type import('hardhat/config').HardhatUserConfig */ -module.exports = { - solidity: "0.8.18", -}; diff --git a/assets/eip-7007/package-lock.json b/assets/eip-7007/package-lock.json deleted file mode 100644 index db6776ab742300..00000000000000 --- a/assets/eip-7007/package-lock.json +++ /dev/null @@ -1,16534 +0,0 @@ -{ - "name": "erc7007", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "erc7007", - "version": "1.0.0", - "license": "ISC", - "devDependencies": { - "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@openzeppelin/contracts": "^4.8.3", - "hardhat": "^2.14.0" - } - }, - "node_modules/@chainsafe/as-sha256": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", - "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", - "dev": true - }, - "node_modules/@chainsafe/persistent-merkle-tree": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", - "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "node_modules/@chainsafe/ssz": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", - "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.4.2", - "case": "^1.6.3" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/abstract-provider": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", - "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0" - } - }, - "node_modules/@ethersproject/abstract-signer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", - "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "node_modules/@ethersproject/address": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", - "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/rlp": "^5.7.0" - } - }, - "node_modules/@ethersproject/base64": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", - "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0" - } - }, - "node_modules/@ethersproject/basex": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", - "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "node_modules/@ethersproject/bignumber": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", - "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "bn.js": "^5.2.1" - } - }, - "node_modules/@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/constants": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", - "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0" - } - }, - "node_modules/@ethersproject/contracts": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", - "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0" - } - }, - "node_modules/@ethersproject/hash": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", - "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/hdnode": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", - "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "node_modules/@ethersproject/json-wallets": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", - "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "aes-js": "3.0.0", - "scrypt-js": "3.0.1" - } - }, - "node_modules/@ethersproject/keccak256": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", - "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "js-sha3": "0.8.0" - } - }, - "node_modules/@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ] - }, - "node_modules/@ethersproject/networks": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", - "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/pbkdf2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", - "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/sha2": "^5.7.0" - } - }, - "node_modules/@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/providers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", - "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0", - "bech32": "1.1.4", - "ws": "7.4.6" - } - }, - "node_modules/@ethersproject/providers/node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@ethersproject/random": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", - "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/rlp": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", - "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/sha2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", - "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "bn.js": "^5.2.1", - "elliptic": "6.5.4", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/solidity": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", - "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/strings": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", - "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/transactions": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", - "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0" - } - }, - "node_modules/@ethersproject/units": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", - "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/wallet": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", - "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/json-wallets": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "node_modules/@ethersproject/web": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", - "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/wordlists": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", - "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, - "peer": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@metamask/eth-sig-util": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", - "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", - "dev": true, - "dependencies": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@morgan-stanley/ts-mocking-bird": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@morgan-stanley/ts-mocking-bird/-/ts-mocking-bird-0.6.4.tgz", - "integrity": "sha512-57VJIflP8eR2xXa9cD1LUawh+Gh+BVQfVu0n6GALyg/AqV/Nz25kDRvws3i9kIe1PTrbsZZOYpsYp6bXPd6nVA==", - "dev": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.16", - "uuid": "^7.0.3" - }, - "peerDependencies": { - "jasmine": "2.x || 3.x || 4.x", - "jest": "26.x || 27.x || 28.x", - "typescript": ">=4.2" - }, - "peerDependenciesMeta": { - "jasmine": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/@morgan-stanley/ts-mocking-bird/node_modules/uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "dev": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nomicfoundation/ethereumjs-block": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz", - "integrity": "sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-block/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-blockchain": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz", - "integrity": "sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-ethash": "3.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "level": "^8.0.0", - "lru-cache": "^5.1.1", - "memory-level": "^1.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-blockchain/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz", - "integrity": "sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-util": "9.0.1", - "crc-32": "^1.2.0" - } - }, - "node_modules/@nomicfoundation/ethereumjs-ethash": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz", - "integrity": "sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "bigint-crypto-utils": "^3.0.23", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-ethash/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-evm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz", - "integrity": "sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==", - "dev": true, - "dependencies": { - "@ethersproject/providers": "^5.7.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-evm/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz", - "integrity": "sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==", - "dev": true, - "bin": { - "rlp": "bin/rlp" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-statemanager": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz", - "integrity": "sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1", - "js-sdsl": "^4.1.4" - } - }, - "node_modules/@nomicfoundation/ethereumjs-statemanager/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-trie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz", - "integrity": "sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@types/readable-stream": "^2.3.13", - "ethereum-cryptography": "0.1.3", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-trie/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz", - "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==", - "dev": true, - "dependencies": { - "@chainsafe/ssz": "^0.9.2", - "@ethersproject/providers": "^5.7.2", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz", - "integrity": "sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==", - "dev": true, - "dependencies": { - "@chainsafe/ssz": "^0.10.0", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/persistent-merkle-tree": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", - "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/@chainsafe/ssz": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", - "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", - "dev": true, - "dependencies": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.5.0" - } - }, - "node_modules/@nomicfoundation/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/ethereumjs-vm": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz", - "integrity": "sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@nomicfoundation/ethereumjs-vm/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/hardhat-chai-matchers": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.6.tgz", - "integrity": "sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ==", - "dev": true, - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@types/chai-as-promised": "^7.1.3", - "chai-as-promised": "^7.1.1", - "deep-eql": "^4.0.1", - "ordinal": "^1.0.3" - }, - "peerDependencies": { - "@nomiclabs/hardhat-ethers": "^2.0.0", - "chai": "^4.2.0", - "ethers": "^5.0.0", - "hardhat": "^2.9.4" - } - }, - "node_modules/@nomicfoundation/hardhat-network-helpers": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.8.tgz", - "integrity": "sha512-MNqQbzUJZnCMIYvlniC3U+kcavz/PhhQSsY90tbEtUyMj/IQqsLwIRZa4ctjABh3Bz0KCh9OXUZ7Yk/d9hr45Q==", - "dev": true, - "peer": true, - "dependencies": { - "ethereumjs-util": "^7.1.4" - }, - "peerDependencies": { - "hardhat": "^2.9.5" - } - }, - "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/@nomicfoundation/hardhat-network-helpers/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-toolbox": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.2.tgz", - "integrity": "sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg==", - "dev": true, - "peerDependencies": { - "@ethersproject/abi": "^5.4.7", - "@ethersproject/providers": "^5.4.7", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", - "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomiclabs/hardhat-ethers": "^2.0.0", - "@nomiclabs/hardhat-etherscan": "^3.0.0", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.2.0", - "@types/mocha": ">=9.1.0", - "@types/node": ">=12.0.0", - "chai": "^4.2.0", - "ethers": "^5.4.7", - "hardhat": "^2.11.0", - "hardhat-gas-reporter": "^1.0.8", - "solidity-coverage": "^0.8.1", - "ts-node": ">=8.0.0", - "typechain": "^8.1.0", - "typescript": ">=4.5.0" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", - "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==", - "dev": true, - "engines": { - "node": ">= 12" - }, - "optionalDependencies": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz", - "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", - "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", - "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", - "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", - "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", - "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", - "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", - "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", - "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", - "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomiclabs/hardhat-ethers": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", - "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==", - "dev": true, - "peer": true, - "peerDependencies": { - "ethers": "^5.0.0", - "hardhat": "^2.0.0" - } - }, - "node_modules/@nomiclabs/hardhat-etherscan": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.7.tgz", - "integrity": "sha512-tZ3TvSgpvsQ6B6OGmo1/Au6u8BrAkvs1mIC/eURA3xgIfznUZBhmpne8hv7BXUzw9xNL3fXdpOYgOQlVMTcoHQ==", - "dev": true, - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@ethersproject/address": "^5.0.2", - "cbor": "^8.1.0", - "chalk": "^2.4.2", - "debug": "^4.1.1", - "fs-extra": "^7.0.1", - "lodash": "^4.17.11", - "semver": "^6.3.0", - "table": "^6.8.0", - "undici": "^5.14.0" - }, - "peerDependencies": { - "hardhat": "^2.0.4" - } - }, - "node_modules/@openzeppelin/contracts": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.3.tgz", - "integrity": "sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg==", - "dev": true - }, - "node_modules/@scure/base": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", - "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/minimal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", - "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/node": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", - "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", - "dev": true, - "dependencies": { - "@sentry/core": "5.30.0", - "@sentry/hub": "5.30.0", - "@sentry/tracing": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/tracing": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", - "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@solidity-parser/parser": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", - "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", - "dev": true, - "peer": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "peer": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "peer": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "peer": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true, - "peer": true - }, - "node_modules/@typechain/ethers-v5": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-10.2.0.tgz", - "integrity": "sha512-ikaq0N/w9fABM+G01OFmU3U3dNnyRwEahkdvi9mqy1a3XwKiPZaF/lu54OcNaEWnpvEYyhhS0N7buCtLQqC92w==", - "dev": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.15", - "ts-essentials": "^7.0.1" - }, - "peerDependencies": { - "@ethersproject/abi": "^5.0.0", - "@ethersproject/bytes": "^5.0.0", - "@ethersproject/providers": "^5.0.0", - "ethers": "^5.1.3", - "typechain": "^8.1.1", - "typescript": ">=4.3.0" - } - }, - "node_modules/@typechain/hardhat": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-6.1.5.tgz", - "integrity": "sha512-lg7LW4qDZpxFMknp3Xool61Fg6Lays8F8TXdFGBG+MxyYcYU5795P1U2XdStuzGq9S2Dzdgh+1jGww9wvZ6r4Q==", - "dev": true, - "peer": true, - "dependencies": { - "fs-extra": "^9.1.0" - }, - "peerDependencies": { - "@ethersproject/abi": "^5.4.7", - "@ethersproject/providers": "^5.4.7", - "@typechain/ethers-v5": "^10.2.0", - "ethers": "^5.4.7", - "hardhat": "^2.9.9", - "typechain": "^8.1.1" - } - }, - "node_modules/@typechain/hardhat/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "peer": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typechain/hardhat/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@typechain/hardhat/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", - "dev": true, - "peer": true - }, - "node_modules/@types/chai-as-promised": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", - "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/chai": "*" - } - }, - "node_modules/@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true, - "peer": true - }, - "node_modules/@types/mocha": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", - "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", - "dev": true, - "peer": true - }, - "node_modules/@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", - "dev": true - }, - "node_modules/@types/pbkdf2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", - "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true, - "peer": true - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true, - "peer": true - }, - "node_modules/@types/readable-stream": { - "version": "2.3.15", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", - "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "safe-buffer": "~5.1.1" - } - }, - "node_modules/@types/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true, - "peer": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/abstract-level": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", - "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", - "dev": true, - "dependencies": { - "buffer": "^6.0.3", - "catering": "^2.1.0", - "is-buffer": "^2.0.5", - "level-supports": "^4.0.0", - "level-transcoder": "^1.0.1", - "module-error": "^1.0.1", - "queue-microtask": "^1.2.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true, - "engines": { - "node": ">=0.3.0" - } - }, - "node_modules/aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", - "dev": true - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/antlr4ts": { - "version": "0.5.0-alpha.4", - "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true, - "peer": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "peer": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "peer": true - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "peer": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true, - "peer": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "peer": true - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true, - "peer": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "peer": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/bcrypt-pbkdf/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "peer": true - }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", - "dev": true - }, - "node_modules/bigint-crypto-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", - "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", - "dev": true, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "node_modules/browser-level": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", - "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", - "dev": true, - "dependencies": { - "abstract-level": "^1.0.2", - "catering": "^2.1.1", - "module-error": "^1.0.2", - "run-parallel-limit": "^1.1.0" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dev": true, - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dev": true, - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dev": true, - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/case": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", - "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "peer": true - }, - "node_modules/catering": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cbor": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", - "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", - "dev": true, - "peer": true, - "dependencies": { - "nofilter": "^3.1.0" - }, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "peer": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "peer": true, - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 5" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/classic-level": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", - "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "abstract-level": "^1.0.2", - "catering": "^2.1.0", - "module-error": "^1.0.1", - "napi-macros": "^2.2.2", - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "peer": true, - "dependencies": { - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "colors": "^1.1.2" - } - }, - "node_modules/cli-table3/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "peer": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "peer": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true - }, - "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "peer": true - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "dev": true, - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "peer": true - }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "peer": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/death": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", - "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", - "dev": true, - "peer": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "peer": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "peer": true - }, - "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "peer": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", - "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", - "dev": true, - "peer": true, - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/difflib": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", - "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", - "dev": true, - "peer": true, - "dependencies": { - "heap": ">= 0.2.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "peer": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "peer": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, - "peer": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-abstract/node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true, - "peer": true - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "peer": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", - "dev": true, - "peer": true, - "dependencies": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=0.12.0" - }, - "optionalDependencies": { - "source-map": "~0.2.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eth-gas-reporter": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz", - "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==", - "dev": true, - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.0.0-beta.146", - "@solidity-parser/parser": "^0.14.0", - "cli-table3": "^0.5.0", - "colors": "1.4.0", - "ethereum-cryptography": "^1.0.3", - "ethers": "^4.0.40", - "fs-readdir-recursive": "^1.1.0", - "lodash": "^4.17.14", - "markdown-table": "^1.1.3", - "mocha": "^7.1.1", - "req-cwd": "^2.0.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.5", - "sha1": "^1.1.1", - "sync-request": "^6.0.0" - }, - "peerDependencies": { - "@codechecks/client": "^0.1.0" - }, - "peerDependenciesMeta": { - "@codechecks/client": { - "optional": true - } - } - }, - "node_modules/eth-gas-reporter/node_modules/ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/eth-gas-reporter/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.1" - } - }, - "node_modules/eth-gas-reporter/node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "peer": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "peer": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eth-gas-reporter/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/eth-gas-reporter/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eth-gas-reporter/node_modules/ethers": { - "version": "4.0.49", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", - "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", - "dev": true, - "peer": true, - "dependencies": { - "aes-js": "3.0.0", - "bn.js": "^4.11.9", - "elliptic": "6.5.4", - "hash.js": "1.1.3", - "js-sha3": "0.5.7", - "scrypt-js": "2.0.4", - "setimmediate": "1.0.4", - "uuid": "2.0.1", - "xmlhttprequest": "1.8.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "peer": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "peer": true, - "dependencies": { - "is-buffer": "~2.0.3" - }, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/eth-gas-reporter/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eth-gas-reporter/node_modules/hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eth-gas-reporter/node_modules/js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eth-gas-reporter/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "peer": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eth-gas-reporter/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eth-gas-reporter/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/eth-gas-reporter/node_modules/mocha": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", - "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/eth-gas-reporter/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eth-gas-reporter/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "peer": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "peer": true, - "dependencies": { - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/eth-gas-reporter/node_modules/scrypt-js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", - "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/setimmediate": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", - "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "peer": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/uuid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eth-gas-reporter/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "peer": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/eth-gas-reporter/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "peer": true, - "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", - "dev": true, - "peer": true, - "dependencies": { - "js-sha3": "^0.8.0" - } - }, - "node_modules/ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" - } - }, - "node_modules/ethereumjs-abi": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", - "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - } - }, - "node_modules/ethereumjs-abi/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "dependencies": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/ethereumjs-util/node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/ethereumjs-util/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/ethereumjs-util/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/ethers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", - "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abi": "5.7.0", - "@ethersproject/abstract-provider": "5.7.0", - "@ethersproject/abstract-signer": "5.7.0", - "@ethersproject/address": "5.7.0", - "@ethersproject/base64": "5.7.0", - "@ethersproject/basex": "5.7.0", - "@ethersproject/bignumber": "5.7.0", - "@ethersproject/bytes": "5.7.0", - "@ethersproject/constants": "5.7.0", - "@ethersproject/contracts": "5.7.0", - "@ethersproject/hash": "5.7.0", - "@ethersproject/hdnode": "5.7.0", - "@ethersproject/json-wallets": "5.7.0", - "@ethersproject/keccak256": "5.7.0", - "@ethersproject/logger": "5.7.0", - "@ethersproject/networks": "5.7.1", - "@ethersproject/pbkdf2": "5.7.0", - "@ethersproject/properties": "5.7.0", - "@ethersproject/providers": "5.7.2", - "@ethersproject/random": "5.7.0", - "@ethersproject/rlp": "5.7.0", - "@ethersproject/sha2": "5.7.0", - "@ethersproject/signing-key": "5.7.0", - "@ethersproject/solidity": "5.7.0", - "@ethersproject/strings": "5.7.0", - "@ethersproject/transactions": "5.7.0", - "@ethersproject/units": "5.7.0", - "@ethersproject/wallet": "5.7.0", - "@ethersproject/web": "5.7.1", - "@ethersproject/wordlists": "5.7.0" - } - }, - "node_modules/ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/ethjs-unit/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "peer": true - }, - "node_modules/ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", - "dev": true, - "dependencies": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "peer": true - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "peer": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "peer": true - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "peer": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "peer": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "peer": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^3.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "peer": true, - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "peer": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/fp-ts": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", - "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true, - "peer": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "peer": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/ghost-testrpc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", - "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^2.4.2", - "node-emoji": "^1.10.0" - }, - "bin": { - "testrpc-sc": "index.js" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "peer": true, - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "peer": true, - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "peer": true, - "dependencies": { - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "peer": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/hardhat": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", - "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@nomicfoundation/ethereumjs-vm": "7.0.1", - "@nomicfoundation/solidity-analyzer": "^0.1.0", - "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", - "abort-controller": "^3.0.0", - "adm-zip": "^0.4.16", - "aggregate-error": "^3.0.0", - "ansi-escapes": "^4.3.0", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", - "ci-info": "^2.0.0", - "debug": "^4.1.1", - "enquirer": "^2.3.0", - "env-paths": "^2.2.0", - "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", - "fp-ts": "1.19.3", - "fs-extra": "^7.0.1", - "glob": "7.2.0", - "immutable": "^4.0.0-rc.12", - "io-ts": "1.10.4", - "keccak": "^3.0.2", - "lodash": "^4.17.11", - "mnemonist": "^0.38.0", - "mocha": "^10.0.0", - "p-map": "^4.0.0", - "qs": "^6.7.0", - "raw-body": "^2.4.1", - "resolve": "1.17.0", - "semver": "^6.3.0", - "solc": "0.7.3", - "source-map-support": "^0.5.13", - "stacktrace-parser": "^0.1.10", - "tsort": "0.0.1", - "undici": "^5.14.0", - "uuid": "^8.3.2", - "ws": "^7.4.6" - }, - "bin": { - "hardhat": "internal/cli/bootstrap.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "ts-node": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/hardhat-gas-reporter": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz", - "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==", - "dev": true, - "peer": true, - "dependencies": { - "array-uniq": "1.0.3", - "eth-gas-reporter": "^0.2.25", - "sha1": "^1.1.1" - }, - "peerDependencies": { - "hardhat": "^2.0.2" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "peer": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "dev": true, - "peer": true - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dev": true, - "peer": true, - "dependencies": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "^10.0.3" - } - }, - "node_modules/http-response-object/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true, - "peer": true - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "peer": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immutable": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", - "dev": true - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "peer": true - }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/io-ts": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", - "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", - "dev": true, - "dependencies": { - "fp-ts": "^1.0.0" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "peer": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "engines": { - "node": ">=4" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "peer": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", - "dev": true, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "peer": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "peer": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "peer": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "peer": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "peer": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "peer": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "peer": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "peer": true - }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "peer": true - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "peer": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "peer": true - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "peer": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/keccak": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", - "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", - "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", - "dev": true, - "dependencies": { - "browser-level": "^1.0.1", - "classic-level": "^1.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/level" - } - }, - "node_modules/level-supports": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", - "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/level-transcoder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", - "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", - "dev": true, - "dependencies": { - "buffer": "^6.0.3", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "peer": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "peer": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "peer": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "peer": true - }, - "node_modules/markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true, - "peer": true - }, - "node_modules/mcl-wasm": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", - "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==", - "dev": true, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/memory-level": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz", - "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", - "dev": true, - "dependencies": { - "abstract-level": "^1.0.0", - "functional-red-black-tree": "^1.0.1", - "module-error": "^1.0.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "peer": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "peer": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mnemonist": { - "version": "0.38.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", - "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", - "dev": true, - "dependencies": { - "obliterator": "^2.0.0" - } - }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/module-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", - "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-macros": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", - "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true - }, - "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "peer": true, - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node_modules/node-environment-flags/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "peer": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/number-to-bn/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "peer": true - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "peer": true, - "dependencies": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", - "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", - "dev": true, - "peer": true, - "dependencies": { - "array.prototype.reduce": "^1.0.5", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.21.2", - "safe-array-concat": "^1.0.0" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obliterator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "peer": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ordinal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", - "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", - "dev": true, - "peer": true - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true, - "peer": true - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, - "peer": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "peer": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "peer": true - }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dev": true, - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "peer": true - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "peer": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dev": true, - "peer": true, - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/req-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", - "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", - "dev": true, - "peer": true, - "dependencies": { - "req-from": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/req-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", - "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", - "dev": true, - "peer": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "peer": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", - "dev": true, - "peer": true, - "dependencies": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "peer": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "peer": true - }, - "node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "peer": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.0" - }, - "bin": { - "rlp": "bin/rlp" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true, - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/run-parallel-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", - "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rustbn.js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", - "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", - "dev": true - }, - "node_modules/safe-array-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", - "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "peer": true - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sc-istanbul": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", - "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", - "dev": true, - "peer": true, - "dependencies": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "istanbul": "lib/cli.js" - } - }, - "node_modules/sc-istanbul/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/sc-istanbul/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "dev": true, - "peer": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/sc-istanbul/node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sc-istanbul/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/sc-istanbul/node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sc-istanbul/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true, - "peer": true - }, - "node_modules/sc-istanbul/node_modules/supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^1.0.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true - }, - "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "peer": true - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", - "dev": true, - "peer": true, - "dependencies": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "peer": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true - }, - "node_modules/solc": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", - "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", - "dev": true, - "dependencies": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "bin": { - "solcjs": "solcjs" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/solc/node_modules/fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/solc/node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/solc/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/solidity-coverage": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.2.tgz", - "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", - "dev": true, - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.14.1", - "chalk": "^2.4.2", - "death": "^1.1.0", - "detect-port": "^1.3.0", - "difflib": "^0.2.4", - "fs-extra": "^8.1.0", - "ghost-testrpc": "^0.0.2", - "global-modules": "^2.0.0", - "globby": "^10.0.1", - "jsonschema": "^1.2.4", - "lodash": "^4.17.15", - "mocha": "7.1.2", - "node-emoji": "^1.10.0", - "pify": "^4.0.1", - "recursive-readdir": "^2.2.2", - "sc-istanbul": "^0.4.5", - "semver": "^7.3.4", - "shelljs": "^0.8.3", - "web3-utils": "^1.3.6" - }, - "bin": { - "solidity-coverage": "plugins/bin.js" - }, - "peerDependencies": { - "hardhat": "^2.11.0" - } - }, - "node_modules/solidity-coverage/node_modules/ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/solidity-coverage/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.1" - } - }, - "node_modules/solidity-coverage/node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "peer": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/solidity-coverage/node_modules/debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "peer": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/solidity-coverage/node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/solidity-coverage/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/solidity-coverage/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "peer": true - }, - "node_modules/solidity-coverage/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/solidity-coverage/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "peer": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "peer": true, - "dependencies": { - "is-buffer": "~2.0.3" - }, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/solidity-coverage/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/solidity-coverage/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/solidity-coverage/node_modules/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/solidity-coverage/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/solidity-coverage/node_modules/js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/solidity-coverage/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "peer": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/solidity-coverage/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/solidity-coverage/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/solidity-coverage/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/solidity-coverage/node_modules/mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/solidity-coverage/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true, - "peer": true - }, - "node_modules/solidity-coverage/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/solidity-coverage/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "peer": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "peer": true, - "dependencies": { - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/solidity-coverage/node_modules/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", - "dev": true, - "peer": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/solidity-coverage/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "peer": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/solidity-coverage/node_modules/supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/solidity-coverage/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "peer": true - }, - "node_modules/solidity-coverage/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "peer": true - }, - "node_modules/solidity-coverage/node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "peer": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/solidity-coverage/node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/solidity-coverage/node_modules/yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "peer": true, - "dependencies": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "peer": true - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "peer": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sshpk/node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "peer": true - }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", - "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", - "dev": true, - "peer": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", - "dev": true, - "dependencies": { - "is-hex-prefixed": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dev": true, - "peer": true, - "dependencies": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dev": true, - "peer": true, - "dependencies": { - "get-port": "^3.1.0" - } - }, - "node_modules/table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "peer": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table-layout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/table-layout/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true - }, - "node_modules/then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/then-request/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", - "dev": true, - "peer": true - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "peer": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/ts-command-line-args": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.0.tgz", - "integrity": "sha512-Ff7Xt04WWCjj/cmPO9eWTJX3qpBZWuPWyQYG1vnxJao+alWWYjwJBc5aYz3h5p5dE08A6AnpkgiCtP/0KXXBYw==", - "dev": true, - "peer": true, - "dependencies": { - "@morgan-stanley/ts-mocking-bird": "^0.6.2", - "chalk": "^4.1.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "string-format": "^2.0.0" - }, - "bin": { - "write-markdown": "dist/write-markdown.js" - } - }, - "node_modules/ts-command-line-args/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-command-line-args/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-command-line-args/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-command-line-args/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true - }, - "node_modules/ts-command-line-args/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-command-line-args/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-essentials": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", - "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", - "dev": true, - "peer": true, - "peerDependencies": { - "typescript": ">=3.7.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "peer": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tsort": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", - "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "node_modules/tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "peer": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typechain": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.1.1.tgz", - "integrity": "sha512-uF/sUvnXTOVF2FHKhQYnxHk4su4JjZR8vr4mA2mBaRwHTbwh0jIlqARz9XJr1tA0l7afJGvEa1dTSi4zt039LQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/prettier": "^2.1.1", - "debug": "^4.3.1", - "fs-extra": "^7.0.0", - "glob": "7.1.7", - "js-sha3": "^0.8.0", - "lodash": "^4.17.15", - "mkdirp": "^1.0.4", - "prettier": "^2.3.1", - "ts-command-line-args": "^2.2.0", - "ts-essentials": "^7.0.1" - }, - "bin": { - "typechain": "dist/cli/cli.js" - }, - "peerDependencies": { - "typescript": ">=4.3.0" - } - }, - "node_modules/typechain/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typechain/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "peer": true - }, - "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", - "dev": true, - "dependencies": { - "busboy": "^1.6.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "peer": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true, - "peer": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "peer": true - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "peer": true, - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/web3-utils": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.9.0.tgz", - "integrity": "sha512-p++69rCNNfu2jM9n5+VD/g26l+qkEOQ1m6cfRQCbH8ZRrtquTmrirJMgTmyOoax5a5XRYOuws14aypCOs51pdQ==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-utils/node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/web3-utils/node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "peer": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "peer": true - }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "peer": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "peer": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/wide-align/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "peer": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/wide-align/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "peer": true - }, - "node_modules/wordwrapjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "dev": true, - "peer": true, - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@chainsafe/as-sha256": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz", - "integrity": "sha512-hldFFYuf49ed7DAakWVXSJODuq3pzJEguD8tQ7h+sGkM18vja+OFoJI9krnGmgzyuZC2ETX0NOIcCTy31v2Mtg==", - "dev": true - }, - "@chainsafe/persistent-merkle-tree": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.4.2.tgz", - "integrity": "sha512-lLO3ihKPngXLTus/L7WHKaw9PnNJWizlOF1H9NNzHP6Xvh82vzg9F2bzkXhYIFshMZ2gTCEz8tq6STe7r5NDfQ==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "@chainsafe/ssz": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.9.4.tgz", - "integrity": "sha512-77Qtg2N1ayqs4Bg/wvnWfg5Bta7iy7IRh8XqXh7oNMeP2HBbBwx8m6yTpA8p0EHItWPEBkgZd5S5/LSlp3GXuQ==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.4.2", - "case": "^1.6.3" - } - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "dev": true, - "requires": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/abstract-provider": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", - "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "dev": true, - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0" - } - }, - "@ethersproject/abstract-signer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", - "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", - "dev": true, - "requires": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "@ethersproject/address": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", - "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "dev": true, - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/rlp": "^5.7.0" - } - }, - "@ethersproject/base64": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", - "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0" - } - }, - "@ethersproject/basex": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", - "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "@ethersproject/bignumber": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", - "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "bn.js": "^5.2.1" - } - }, - "@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "dev": true, - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/constants": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", - "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "dev": true, - "requires": { - "@ethersproject/bignumber": "^5.7.0" - } - }, - "@ethersproject/contracts": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", - "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0" - } - }, - "@ethersproject/hash": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", - "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "dev": true, - "requires": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/hdnode": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", - "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", - "dev": true, - "requires": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "@ethersproject/json-wallets": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", - "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", - "dev": true, - "requires": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "aes-js": "3.0.0", - "scrypt-js": "3.0.1" - } - }, - "@ethersproject/keccak256": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", - "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "js-sha3": "0.8.0" - } - }, - "@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", - "dev": true - }, - "@ethersproject/networks": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", - "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "dev": true, - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/pbkdf2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", - "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/sha2": "^5.7.0" - } - }, - "@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "dev": true, - "requires": { - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/providers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", - "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", - "dev": true, - "requires": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0", - "bech32": "1.1.4", - "ws": "7.4.6" - }, - "dependencies": { - "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true, - "requires": {} - } - } - }, - "@ethersproject/random": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", - "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/rlp": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", - "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/sha2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", - "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "hash.js": "1.1.7" - } - }, - "@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "bn.js": "^5.2.1", - "elliptic": "6.5.4", - "hash.js": "1.1.7" - } - }, - "@ethersproject/solidity": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", - "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", - "dev": true, - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/strings": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", - "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/transactions": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", - "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", - "dev": true, - "requires": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0" - } - }, - "@ethersproject/units": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", - "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", - "dev": true, - "requires": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "@ethersproject/wallet": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", - "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", - "dev": true, - "requires": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/json-wallets": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "@ethersproject/web": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", - "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "dev": true, - "requires": { - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@ethersproject/wordlists": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", - "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", - "dev": true, - "requires": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "peer": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true, - "peer": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "peer": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@metamask/eth-sig-util": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", - "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", - "dev": true, - "requires": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" - } - }, - "@morgan-stanley/ts-mocking-bird": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@morgan-stanley/ts-mocking-bird/-/ts-mocking-bird-0.6.4.tgz", - "integrity": "sha512-57VJIflP8eR2xXa9cD1LUawh+Gh+BVQfVu0n6GALyg/AqV/Nz25kDRvws3i9kIe1PTrbsZZOYpsYp6bXPd6nVA==", - "dev": true, - "peer": true, - "requires": { - "lodash": "^4.17.16", - "uuid": "^7.0.3" - }, - "dependencies": { - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==", - "dev": true, - "peer": true - } - } - }, - "@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true - }, - "@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "peer": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "peer": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "peer": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@nomicfoundation/ethereumjs-block": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-5.0.1.tgz", - "integrity": "sha512-u1Yioemi6Ckj3xspygu/SfFvm8vZEO8/Yx5a1QLzi6nVU0jz3Pg2OmHKJ5w+D9Ogk1vhwRiqEBAqcb0GVhCyHw==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-blockchain": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.1.tgz", - "integrity": "sha512-NhzndlGg829XXbqJEYrF1VeZhAwSPgsK/OB7TVrdzft3y918hW5KNd7gIZ85sn6peDZOdjBsAXIpXZ38oBYE5A==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-ethash": "3.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "level": "^8.0.0", - "lru-cache": "^5.1.1", - "memory-level": "^1.0.0" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-common": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.1.tgz", - "integrity": "sha512-OBErlkfp54GpeiE06brBW/TTbtbuBJV5YI5Nz/aB2evTDo+KawyEzPjBlSr84z/8MFfj8wS2wxzQX1o32cev5g==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-util": "9.0.1", - "crc-32": "^1.2.0" - } - }, - "@nomicfoundation/ethereumjs-ethash": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-3.0.1.tgz", - "integrity": "sha512-KDjGIB5igzWOp8Ik5I6QiRH5DH+XgILlplsHR7TEuWANZA759G6krQ6o8bvj+tRUz08YygMQu/sGd9mJ1DYT8w==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "abstract-level": "^1.0.3", - "bigint-crypto-utils": "^3.0.23", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-evm": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-2.0.1.tgz", - "integrity": "sha512-oL8vJcnk0Bx/onl+TgQOQ1t/534GKFaEG17fZmwtPFeH8S5soiBYPCLUrvANOl4sCp9elYxIMzIiTtMtNNN8EQ==", - "dev": true, - "requires": { - "@ethersproject/providers": "^5.7.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.1.tgz", - "integrity": "sha512-xtxrMGa8kP4zF5ApBQBtjlSbN5E2HI8m8FYgVSYAnO6ssUoY5pVPGy2H8+xdf/bmMa22Ce8nWMH3aEW8CcqMeQ==", - "dev": true - }, - "@nomicfoundation/ethereumjs-statemanager": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-2.0.1.tgz", - "integrity": "sha512-B5ApMOnlruVOR7gisBaYwFX+L/AP7i/2oAahatssjPIBVDF6wTX1K7Qpa39E/nzsH8iYuL3krkYeUFIdO3EMUQ==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "ethers": "^5.7.1", - "js-sdsl": "^4.1.4" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-trie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.1.tgz", - "integrity": "sha512-A64It/IMpDVODzCgxDgAAla8jNjNtsoQZIzZUfIV5AY6Coi4nvn7+VReBn5itlxMiL2yaTlQr9TRWp3CSI6VoA==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@types/readable-stream": "^2.3.13", - "ethereum-cryptography": "0.1.3", - "readable-stream": "^3.6.0" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-tx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.1.tgz", - "integrity": "sha512-0HwxUF2u2hrsIM1fsasjXvlbDOq1ZHFV2dd1yGq8CA+MEYhaxZr8OTScpVkkxqMwBcc5y83FyPl0J9MZn3kY0w==", - "dev": true, - "requires": { - "@chainsafe/ssz": "^0.9.2", - "@ethersproject/providers": "^5.7.2", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-util": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.1.tgz", - "integrity": "sha512-TwbhOWQ8QoSCFhV/DDfSmyfFIHjPjFBj957219+V3jTZYZ2rf9PmDtNOeZWAE3p3vlp8xb02XGpd0v6nTUPbsA==", - "dev": true, - "requires": { - "@chainsafe/ssz": "^0.10.0", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "ethereum-cryptography": "0.1.3" - }, - "dependencies": { - "@chainsafe/persistent-merkle-tree": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@chainsafe/persistent-merkle-tree/-/persistent-merkle-tree-0.5.0.tgz", - "integrity": "sha512-l0V1b5clxA3iwQLXP40zYjyZYospQLZXzBVIhhr9kDg/1qHZfzzHw0jj4VPBijfYCArZDlPkRi1wZaV2POKeuw==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1" - } - }, - "@chainsafe/ssz": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@chainsafe/ssz/-/ssz-0.10.2.tgz", - "integrity": "sha512-/NL3Lh8K+0q7A3LsiFq09YXS9fPE+ead2rr7vM2QK8PLzrNsw3uqrif9bpRX5UxgeRjM+vYi+boCM3+GM4ovXg==", - "dev": true, - "requires": { - "@chainsafe/as-sha256": "^0.3.1", - "@chainsafe/persistent-merkle-tree": "^0.5.0" - } - }, - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/ethereumjs-vm": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-7.0.1.tgz", - "integrity": "sha512-rArhyn0jPsS/D+ApFsz3yVJMQ29+pVzNZ0VJgkzAZ+7FqXSRtThl1C1prhmlVr3YNUlfpZ69Ak+RUT4g7VoOuQ==", - "dev": true, - "requires": { - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "debug": "^4.3.3", - "ethereum-cryptography": "0.1.3", - "mcl-wasm": "^0.7.1", - "rustbn.js": "~0.2.0" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "@nomicfoundation/hardhat-chai-matchers": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-1.0.6.tgz", - "integrity": "sha512-f5ZMNmabZeZegEfuxn/0kW+mm7+yV7VNDxLpMOMGXWFJ2l/Ct3QShujzDRF9cOkK9Ui/hbDeOWGZqyQALDXVCQ==", - "dev": true, - "peer": true, - "requires": { - "@ethersproject/abi": "^5.1.2", - "@types/chai-as-promised": "^7.1.3", - "chai-as-promised": "^7.1.1", - "deep-eql": "^4.0.1", - "ordinal": "^1.0.3" - } - }, - "@nomicfoundation/hardhat-network-helpers": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.8.tgz", - "integrity": "sha512-MNqQbzUJZnCMIYvlniC3U+kcavz/PhhQSsY90tbEtUyMj/IQqsLwIRZa4ctjABh3Bz0KCh9OXUZ7Yk/d9hr45Q==", - "dev": true, - "peer": true, - "requires": { - "ethereumjs-util": "^7.1.4" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "peer": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - } - } - }, - "@nomicfoundation/hardhat-toolbox": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-2.0.2.tgz", - "integrity": "sha512-vnN1AzxbvpSx9pfdRHbUzTRIXpMLPXnUlkW855VaDk6N1pwRaQ2gNzEmFAABk4lWf11E00PKwFd/q27HuwYrYg==", - "dev": true, - "requires": {} - }, - "@nomicfoundation/solidity-analyzer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", - "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==", - "dev": true, - "requires": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" - } - }, - "@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz", - "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", - "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-freebsd-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", - "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", - "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", - "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", - "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", - "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", - "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", - "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", - "dev": true, - "optional": true - }, - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", - "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", - "dev": true, - "optional": true - }, - "@nomiclabs/hardhat-ethers": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-ethers/-/hardhat-ethers-2.2.3.tgz", - "integrity": "sha512-YhzPdzb612X591FOe68q+qXVXGG2ANZRvDo0RRUtimev85rCrAlv/TLMEZw5c+kq9AbzocLTVX/h2jVIFPL9Xg==", - "dev": true, - "peer": true, - "requires": {} - }, - "@nomiclabs/hardhat-etherscan": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@nomiclabs/hardhat-etherscan/-/hardhat-etherscan-3.1.7.tgz", - "integrity": "sha512-tZ3TvSgpvsQ6B6OGmo1/Au6u8BrAkvs1mIC/eURA3xgIfznUZBhmpne8hv7BXUzw9xNL3fXdpOYgOQlVMTcoHQ==", - "dev": true, - "peer": true, - "requires": { - "@ethersproject/abi": "^5.1.2", - "@ethersproject/address": "^5.0.2", - "cbor": "^8.1.0", - "chalk": "^2.4.2", - "debug": "^4.1.1", - "fs-extra": "^7.0.1", - "lodash": "^4.17.11", - "semver": "^6.3.0", - "table": "^6.8.0", - "undici": "^5.14.0" - } - }, - "@openzeppelin/contracts": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.8.3.tgz", - "integrity": "sha512-bQHV8R9Me8IaJoJ2vPG4rXcL7seB7YVuskr4f+f5RyOStSZetwzkWtoqDMl5erkBJy0lDRUnIR2WIkPiC0GJlg==", - "dev": true - }, - "@scure/base": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", - "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", - "dev": true - }, - "@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "requires": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" - } - }, - "@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "requires": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" - } - }, - "@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", - "dev": true, - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", - "dev": true, - "requires": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/minimal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", - "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", - "dev": true, - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/node": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", - "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", - "dev": true, - "requires": { - "@sentry/core": "5.30.0", - "@sentry/hub": "5.30.0", - "@sentry/tracing": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - } - }, - "@sentry/tracing": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", - "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", - "dev": true, - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", - "dev": true - }, - "@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", - "dev": true, - "requires": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@solidity-parser/parser": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", - "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", - "dev": true, - "peer": true, - "requires": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true, - "peer": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "peer": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "peer": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true, - "peer": true - }, - "@typechain/ethers-v5": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@typechain/ethers-v5/-/ethers-v5-10.2.0.tgz", - "integrity": "sha512-ikaq0N/w9fABM+G01OFmU3U3dNnyRwEahkdvi9mqy1a3XwKiPZaF/lu54OcNaEWnpvEYyhhS0N7buCtLQqC92w==", - "dev": true, - "peer": true, - "requires": { - "lodash": "^4.17.15", - "ts-essentials": "^7.0.1" - } - }, - "@typechain/hardhat": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-6.1.5.tgz", - "integrity": "sha512-lg7LW4qDZpxFMknp3Xool61Fg6Lays8F8TXdFGBG+MxyYcYU5795P1U2XdStuzGq9S2Dzdgh+1jGww9wvZ6r4Q==", - "dev": true, - "peer": true, - "requires": { - "fs-extra": "^9.1.0" - }, - "dependencies": { - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "peer": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, - "peer": true - } - } - }, - "@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/chai": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", - "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", - "dev": true, - "peer": true - }, - "@types/chai-as-promised": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.5.tgz", - "integrity": "sha512-jStwss93SITGBwt/niYrkf2C+/1KTeZCZl1LaeezTlqppAKeoQC7jxyqYuP72sxBGKCIbw7oHgbYssIRzT5FCQ==", - "dev": true, - "peer": true, - "requires": { - "@types/chai": "*" - } - }, - "@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, - "@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, - "@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "peer": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true - }, - "@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true, - "peer": true - }, - "@types/mocha": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", - "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", - "dev": true, - "peer": true - }, - "@types/node": { - "version": "18.16.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", - "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", - "dev": true - }, - "@types/pbkdf2": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz", - "integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/prettier": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", - "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", - "dev": true, - "peer": true - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true, - "peer": true - }, - "@types/readable-stream": { - "version": "2.3.15", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", - "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", - "dev": true, - "requires": { - "@types/node": "*", - "safe-buffer": "~5.1.1" - } - }, - "@types/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-Da66lEIFeIz9ltsdMZcpQvmrmmoqrfju8pm1BH8WbYjZSwUgCwXLb9C+9XYogwBITnbsSaMdVPb2ekf7TV+03w==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true, - "peer": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "abstract-level": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/abstract-level/-/abstract-level-1.0.3.tgz", - "integrity": "sha512-t6jv+xHy+VYwc4xqZMn2Pa9DjcdzvzZmQGRjTFc8spIbRGHgBrEKbPq+rYXc7CCo0lxgYvSgKVg9qZAhpVQSjA==", - "dev": true, - "requires": { - "buffer": "^6.0.3", - "catering": "^2.1.0", - "is-buffer": "^2.0.5", - "level-supports": "^4.0.0", - "level-transcoder": "^1.0.1", - "module-error": "^1.0.1", - "queue-microtask": "^1.2.3" - } - }, - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "peer": true - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true, - "peer": true - }, - "address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, - "peer": true - }, - "adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true - }, - "aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "peer": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "optional": true, - "peer": true - }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "antlr4ts": { - "version": "0.5.0-alpha.4", - "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true, - "peer": true - }, - "anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "peer": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "peer": true - }, - "array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - } - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "peer": true - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "peer": true - }, - "array.prototype.reduce": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", - "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "peer": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "peer": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "peer": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "peer": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "peer": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true, - "peer": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "peer": true - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "peer": true - }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, - "peer": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "peer": true - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true, - "peer": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "peer": true, - "requires": { - "tweetnacl": "^0.14.3" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "peer": true - } - } - }, - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", - "dev": true - }, - "bigint-crypto-utils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/bigint-crypto-utils/-/bigint-crypto-utils-3.2.2.tgz", - "integrity": "sha512-U1RbE3aX9ayCUVcIPHuPDPKcK3SFOXf93J1UK/iHlJuQB7bhagPIX06/CLpLEsDThJ7KA4Dhrnzynl+d2weTiw==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true - }, - "bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "browser-level": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browser-level/-/browser-level-1.0.1.tgz", - "integrity": "sha512-XECYKJ+Dbzw0lbydyQuJzwNXtOpbMSq737qxJN11sIRTErOMShvDpbzTlgju7orJKvx4epULolZAuJGLzCmWRQ==", - "dev": true, - "requires": { - "abstract-level": "^1.0.2", - "catering": "^2.1.1", - "module-error": "^1.0.2", - "run-parallel-limit": "^1.1.0" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dev": true, - "requires": { - "base-x": "^3.0.2" - } - }, - "bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dev": true, - "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dev": true, - "requires": { - "streamsearch": "^1.1.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "case": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/case/-/case-1.6.3.tgz", - "integrity": "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "peer": true - }, - "catering": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", - "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", - "dev": true - }, - "cbor": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", - "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", - "dev": true, - "peer": true, - "requires": { - "nofilter": "^3.1.0" - } - }, - "chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "peer": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "peer": true, - "requires": { - "check-error": "^1.0.2" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true, - "peer": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "peer": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "classic-level": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/classic-level/-/classic-level-1.3.0.tgz", - "integrity": "sha512-iwFAJQYtqRTRM0F6L8h4JCt00ZSGdOyqh7yVrhhjrOpFhmBjNlRUey64MCiyo6UmQHMJ+No3c81nujPv+n9yrg==", - "dev": true, - "requires": { - "abstract-level": "^1.0.2", - "catering": "^2.1.0", - "module-error": "^1.0.1", - "napi-macros": "^2.2.2", - "node-gyp-build": "^4.3.0" - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "peer": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "peer": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "peer": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "peer": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "peer": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true - }, - "command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "peer": true, - "requires": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - } - }, - "command-line-usage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "dev": true, - "peer": true, - "requires": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, - "dependencies": { - "array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "peer": true - }, - "typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true - } - } - }, - "commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "peer": true - }, - "crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "dev": true - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "peer": true - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true, - "peer": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "peer": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "death": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", - "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", - "dev": true, - "peer": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "peer": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "peer": true - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "peer": true - }, - "define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, - "peer": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "peer": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "detect-port": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.5.1.tgz", - "integrity": "sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==", - "dev": true, - "peer": true, - "requires": { - "address": "^1.0.1", - "debug": "4" - } - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "difflib": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", - "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", - "dev": true, - "peer": true, - "requires": { - "heap": ">= 0.2.0" - } - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "peer": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "peer": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, - "es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", - "dev": true, - "peer": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - }, - "dependencies": { - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - } - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true, - "peer": true - }, - "es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "peer": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "peer": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", - "dev": true, - "peer": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "peer": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", - "dev": true, - "peer": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "peer": true - }, - "eth-gas-reporter": { - "version": "0.2.25", - "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz", - "integrity": "sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==", - "dev": true, - "peer": true, - "requires": { - "@ethersproject/abi": "^5.0.0-beta.146", - "@solidity-parser/parser": "^0.14.0", - "cli-table3": "^0.5.0", - "colors": "1.4.0", - "ethereum-cryptography": "^1.0.3", - "ethers": "^4.0.40", - "fs-readdir-recursive": "^1.1.0", - "lodash": "^4.17.14", - "markdown-table": "^1.1.3", - "mocha": "^7.1.1", - "req-cwd": "^2.0.0", - "request": "^2.88.0", - "request-promise-native": "^1.0.5", - "sha1": "^1.1.1", - "sync-request": "^6.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true, - "peer": true - }, - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "peer": true - }, - "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "peer": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "peer": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "peer": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "peer": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "peer": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "peer": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "peer": true - }, - "ethers": { - "version": "4.0.49", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-4.0.49.tgz", - "integrity": "sha512-kPltTvWiyu+OktYy1IStSO16i2e7cS9D9OxZ81q2UUaiNPVrm/RTcbxamCXF9VUSKzJIdJV68EAIhTEVBalRWg==", - "dev": true, - "peer": true, - "requires": { - "aes-js": "3.0.0", - "bn.js": "^4.11.9", - "elliptic": "6.5.4", - "hash.js": "1.1.3", - "js-sha3": "0.5.7", - "scrypt-js": "2.0.4", - "setimmediate": "1.0.4", - "uuid": "2.0.1", - "xmlhttprequest": "1.8.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "peer": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true, - "peer": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true - }, - "js-sha3": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz", - "integrity": "sha512-GII20kjaPX0zJ8wzkTbNDYMY7msuZcTWk8S5UOh6806Jq/wz1J8/bnr8uGU0DAUmYDjj2Mr4X1cW8v/GLYnR+g==", - "dev": true, - "peer": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "peer": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "peer": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "peer": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "peer": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", - "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", - "dev": true, - "peer": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true, - "peer": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "peer": true - }, - "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "peer": true, - "requires": { - "picomatch": "^2.0.4" - } - }, - "scrypt-js": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-2.0.4.tgz", - "integrity": "sha512-4KsaGcPnuhtCZQCxFxN3GVYIhKFPTdLd8PLC552XwbMndtD0cjRFAhDuuydXQ0h08ZfPgzqe6EKHozpuH74iDw==", - "dev": true, - "peer": true - }, - "setimmediate": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.4.tgz", - "integrity": "sha512-/TjEmXQVEzdod/FFskf3o7oOAsGhHf2j1dZqRFbDzq4F3mvvxflIIi4Hd3bLQE9y/CpwqfSQam5JakI/mi3Pog==", - "dev": true, - "peer": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "peer": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "uuid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz", - "integrity": "sha512-nWg9+Oa3qD2CQzHIP4qKUqwNfzKn8P0LtFhotaCTFchsV7ZfDhAybeip/HZVeMIpZi9JgY1E3nUlwaCmZT1sEg==", - "dev": true, - "peer": true - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "peer": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "peer": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "peer": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - } - } - }, - "ethereum-bloom-filters": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.10.tgz", - "integrity": "sha512-rxJ5OFN3RwjQxDcFP2Z5+Q9ho4eIdEmSc2ht0fCu8Se9nbXjZ7/031uXoUYJ87KHCOdVeiUuwSnoS7hmYAGVHA==", - "dev": true, - "peer": true, - "requires": { - "js-sha3": "^0.8.0" - } - }, - "ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", - "dev": true, - "requires": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" - } - }, - "ethereumjs-abi": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", - "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", - "dev": true, - "requires": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "requires": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - }, - "dependencies": { - "@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - } - } - }, - "ethers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", - "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", - "dev": true, - "requires": { - "@ethersproject/abi": "5.7.0", - "@ethersproject/abstract-provider": "5.7.0", - "@ethersproject/abstract-signer": "5.7.0", - "@ethersproject/address": "5.7.0", - "@ethersproject/base64": "5.7.0", - "@ethersproject/basex": "5.7.0", - "@ethersproject/bignumber": "5.7.0", - "@ethersproject/bytes": "5.7.0", - "@ethersproject/constants": "5.7.0", - "@ethersproject/contracts": "5.7.0", - "@ethersproject/hash": "5.7.0", - "@ethersproject/hdnode": "5.7.0", - "@ethersproject/json-wallets": "5.7.0", - "@ethersproject/keccak256": "5.7.0", - "@ethersproject/logger": "5.7.0", - "@ethersproject/networks": "5.7.1", - "@ethersproject/pbkdf2": "5.7.0", - "@ethersproject/properties": "5.7.0", - "@ethersproject/providers": "5.7.2", - "@ethersproject/random": "5.7.0", - "@ethersproject/rlp": "5.7.0", - "@ethersproject/sha2": "5.7.0", - "@ethersproject/signing-key": "5.7.0", - "@ethersproject/solidity": "5.7.0", - "@ethersproject/strings": "5.7.0", - "@ethersproject/transactions": "5.7.0", - "@ethersproject/units": "5.7.0", - "@ethersproject/wallet": "5.7.0", - "@ethersproject/web": "5.7.1", - "@ethersproject/wordlists": "5.7.0" - } - }, - "ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "peer": true - } - } - }, - "ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", - "dev": true, - "requires": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - } - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "peer": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "peer": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "peer": true - }, - "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "peer": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "peer": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "peer": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "peer": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dev": true, - "peer": true, - "requires": { - "array-back": "^3.0.1" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "peer": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "peer": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "peer": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fp-ts": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", - "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", - "dev": true - }, - "fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true, - "peer": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "peer": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "peer": true - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", - "dev": true, - "peer": true - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "peer": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "ghost-testrpc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", - "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", - "dev": true, - "peer": true, - "requires": { - "chalk": "^2.4.2", - "node-emoji": "^1.10.0" - } - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "peer": true, - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "peer": true, - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - } - }, - "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "peer": true, - "requires": { - "define-properties": "^1.1.3" - } - }, - "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "peer": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "peer": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "peer": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "peer": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "peer": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "peer": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "hardhat": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.14.0.tgz", - "integrity": "sha512-73jsInY4zZahMSVFurSK+5TNCJTXMv+vemvGia0Ac34Mm19fYp6vEPVGF3sucbumszsYxiTT2TbS8Ii2dsDSoQ==", - "dev": true, - "requires": { - "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/ethereumjs-block": "5.0.1", - "@nomicfoundation/ethereumjs-blockchain": "7.0.1", - "@nomicfoundation/ethereumjs-common": "4.0.1", - "@nomicfoundation/ethereumjs-evm": "2.0.1", - "@nomicfoundation/ethereumjs-rlp": "5.0.1", - "@nomicfoundation/ethereumjs-statemanager": "2.0.1", - "@nomicfoundation/ethereumjs-trie": "6.0.1", - "@nomicfoundation/ethereumjs-tx": "5.0.1", - "@nomicfoundation/ethereumjs-util": "9.0.1", - "@nomicfoundation/ethereumjs-vm": "7.0.1", - "@nomicfoundation/solidity-analyzer": "^0.1.0", - "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", - "abort-controller": "^3.0.0", - "adm-zip": "^0.4.16", - "aggregate-error": "^3.0.0", - "ansi-escapes": "^4.3.0", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", - "ci-info": "^2.0.0", - "debug": "^4.1.1", - "enquirer": "^2.3.0", - "env-paths": "^2.2.0", - "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", - "fp-ts": "1.19.3", - "fs-extra": "^7.0.1", - "glob": "7.2.0", - "immutable": "^4.0.0-rc.12", - "io-ts": "1.10.4", - "keccak": "^3.0.2", - "lodash": "^4.17.11", - "mnemonist": "^0.38.0", - "mocha": "^10.0.0", - "p-map": "^4.0.0", - "qs": "^6.7.0", - "raw-body": "^2.4.1", - "resolve": "1.17.0", - "semver": "^6.3.0", - "solc": "0.7.3", - "source-map-support": "^0.5.13", - "stacktrace-parser": "^0.1.10", - "tsort": "0.0.1", - "undici": "^5.14.0", - "uuid": "^8.3.2", - "ws": "^7.4.6" - } - }, - "hardhat-gas-reporter": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.9.tgz", - "integrity": "sha512-INN26G3EW43adGKBNzYWOlI3+rlLnasXTwW79YNnUhXPDa+yHESgt639dJEs37gCjhkbNKcRRJnomXEuMFBXJg==", - "dev": true, - "peer": true, - "requires": { - "array-uniq": "1.0.3", - "eth-gas-reporter": "^0.2.25", - "sha1": "^1.1.1" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "peer": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "peer": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, - "peer": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "peer": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "dev": true, - "peer": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dev": true, - "peer": true, - "requires": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - } - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "^10.0.3" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true, - "peer": true - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "peer": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "peer": true - }, - "immutable": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "peer": true - }, - "internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "peer": true, - "requires": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "peer": true - }, - "io-ts": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", - "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", - "dev": true, - "requires": { - "fp-ts": "^1.0.0" - } - }, - "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "peer": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "peer": true - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "peer": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "peer": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "peer": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "peer": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "peer": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "peer": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true, - "peer": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "peer": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "peer": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true, - "peer": true - }, - "js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true - }, - "js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "peer": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "peer": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "peer": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "peer": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", - "dev": true, - "peer": true - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "peer": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "keccak": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.3.tgz", - "integrity": "sha512-JZrLIAJWuZxKbCilMpNz5Vj7Vtb4scDG3dMXLOsbzBmQGyjwE61BbW7bJkfKKCShXiQZt3T6sBgALRtmd+nZaQ==", - "dev": true, - "requires": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "peer": true - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", - "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", - "dev": true, - "requires": { - "browser-level": "^1.0.1", - "classic-level": "^1.2.0" - } - }, - "level-supports": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-4.0.1.tgz", - "integrity": "sha512-PbXpve8rKeNcZ9C1mUicC9auIYFyGpkV9/i6g76tLgANwWhtG2v7I4xNBUlkn3lE2/dZF3Pi0ygYGtLc4RXXdA==", - "dev": true - }, - "level-transcoder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/level-transcoder/-/level-transcoder-1.0.1.tgz", - "integrity": "sha512-t7bFwFtsQeD8cl8NIoQ2iwxA0CL/9IFw7/9gAjOonH0PWTTiRfY7Hq+Ejbsxh86tXobDQ6IOiddjNYIfOBs06w==", - "dev": true, - "requires": { - "buffer": "^6.0.3", - "module-error": "^1.0.1" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "peer": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "peer": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "peer": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", - "dev": true - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "peer": true - }, - "markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true, - "peer": true - }, - "mcl-wasm": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.7.9.tgz", - "integrity": "sha512-iJIUcQWA88IJB/5L15GnJVnSQJmf/YaxxV6zRavv83HILHaJQb6y0iFyDMdDO0gN8X37tdxmAOrH/P8B6RB8sQ==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "memory-level": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/memory-level/-/memory-level-1.0.0.tgz", - "integrity": "sha512-UXzwewuWeHBz5krr7EvehKcmLFNoXxGcvuYhC41tRnkrTbJohtS7kVn9akmgirtRygg+f7Yjsfi8Uu5SGSQ4Og==", - "dev": true, - "requires": { - "abstract-level": "^1.0.0", - "functional-red-black-tree": "^1.0.1", - "module-error": "^1.0.1" - } - }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "peer": true - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "peer": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "peer": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "peer": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "peer": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "peer": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "mnemonist": { - "version": "0.38.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", - "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", - "dev": true, - "requires": { - "obliterator": "^2.0.0" - } - }, - "mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "module-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/module-error/-/module-error-1.0.2.tgz", - "integrity": "sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, - "napi-macros": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.2.2.tgz", - "integrity": "sha512-hmEVtAGYzVQpCKdbQea4skABsdXW4RUh5t5mJ2zzqowJS2OyXZTU1KhDVFhx+NlWZ4ap9mqR9TcDO3LTTttd+g==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true - }, - "node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true - }, - "node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "peer": true, - "requires": { - "lodash": "^4.17.21" - } - }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "peer": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "peer": true - } - } - }, - "node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "dev": true - }, - "nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true, - "peer": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "peer": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "peer": true - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "peer": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "peer": true - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "peer": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "peer": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.6.tgz", - "integrity": "sha512-lq+61g26E/BgHv0ZTFgRvi7NMEPuAxLkFU7rukXjc/AlwH4Am5xXVnIXy3un1bg/JPbXHrixRkK1itUzzPiIjQ==", - "dev": true, - "peer": true, - "requires": { - "array.prototype.reduce": "^1.0.5", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.21.2", - "safe-array-concat": "^1.0.0" - } - }, - "obliterator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "peer": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "ordinal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", - "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", - "dev": true, - "peer": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true - }, - "parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true, - "peer": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "peer": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "peer": true - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true, - "peer": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "peer": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "peer": true - }, - "prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "peer": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "peer": true - }, - "promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dev": true, - "peer": true, - "requires": { - "asap": "~2.0.6" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "peer": true - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "peer": true - }, - "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "peer": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dev": true, - "peer": true, - "requires": { - "minimatch": "^3.0.5" - } - }, - "reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "peer": true - }, - "regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - } - }, - "req-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", - "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", - "dev": true, - "peer": true, - "requires": { - "req-from": "^2.0.0" - } - }, - "req-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", - "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", - "dev": true, - "peer": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "peer": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "peer": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "peer": true - } - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "peer": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "dev": true, - "peer": true, - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true, - "peer": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true, - "peer": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "peer": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "dev": true, - "requires": { - "bn.js": "^5.2.0" - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "peer": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "run-parallel-limit": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz", - "integrity": "sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "rustbn.js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/rustbn.js/-/rustbn.js-0.2.0.tgz", - "integrity": "sha512-4VlvkRUuCJvr2J6Y0ImW7NvTCriMi7ErOAqWk1y69vAdoNIzCF3yPmgeNzx+RQTLEDFq5sHfscn1MwHxP9hNfA==", - "dev": true - }, - "safe-array-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", - "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "dependencies": { - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "peer": true - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sc-istanbul": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", - "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", - "dev": true, - "peer": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "dev": true, - "peer": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true, - "peer": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "peer": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "peer": true - } - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true - }, - "secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "dev": true, - "requires": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "peer": true - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", - "dev": true, - "peer": true, - "requires": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" - } - }, - "shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "peer": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "peer": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true - } - } - }, - "solc": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", - "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", - "dev": true, - "requires": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "dependencies": { - "fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "solidity-coverage": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.2.tgz", - "integrity": "sha512-cv2bWb7lOXPE9/SSleDO6czkFiMHgP4NXPj+iW9W7iEKLBk7Cj0AGBiNmGX3V1totl9wjPrT0gHmABZKZt65rQ==", - "dev": true, - "peer": true, - "requires": { - "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.14.1", - "chalk": "^2.4.2", - "death": "^1.1.0", - "detect-port": "^1.3.0", - "difflib": "^0.2.4", - "fs-extra": "^8.1.0", - "ghost-testrpc": "^0.0.2", - "global-modules": "^2.0.0", - "globby": "^10.0.1", - "jsonschema": "^1.2.4", - "lodash": "^4.17.15", - "mocha": "7.1.2", - "node-emoji": "^1.10.0", - "pify": "^4.0.1", - "recursive-readdir": "^2.2.2", - "sc-istanbul": "^0.4.5", - "semver": "^7.3.4", - "shelljs": "^0.8.3", - "web3-utils": "^1.3.6" - }, - "dependencies": { - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true, - "peer": true - }, - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "peer": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "peer": true - }, - "chokidar": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", - "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", - "dev": true, - "peer": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "peer": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "peer": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "peer": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "peer": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true, - "peer": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "peer": true - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "peer": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "peer": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true, - "peer": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "peer": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "peer": true, - "requires": { - "chalk": "^2.4.2" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "peer": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "peer": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "peer": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.2.tgz", - "integrity": "sha512-o96kdRKMKI3E8U0bjnfqW4QMk12MwZ4mhdBTf+B5a1q9+aq2HRnj+3ZdJu0B/ZhJeK78MgYuv6L8d/rA5AeBJA==", - "dev": true, - "peer": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "chokidar": "3.3.0", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "3.0.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.5", - "ms": "2.1.1", - "node-environment-flags": "1.0.6", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true, - "peer": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "peer": true - }, - "readdirp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", - "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", - "dev": true, - "peer": true, - "requires": { - "picomatch": "^2.0.4" - } - }, - "semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", - "dev": true, - "peer": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "peer": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true, - "peer": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "peer": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "peer": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "peer": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "peer": true - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "peer": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "peer": true - } - } - }, - "stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "dev": true - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", - "dev": true, - "peer": true - }, - "streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "string-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", - "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", - "dev": true, - "peer": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", - "dev": true, - "requires": { - "is-hex-prefixed": "1.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dev": true, - "peer": true, - "requires": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - } - }, - "sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dev": true, - "peer": true, - "requires": { - "get-port": "^3.1.0" - } - }, - "table": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", - "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", - "dev": true, - "peer": true, - "requires": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "peer": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true - } - } - }, - "table-layout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "dev": true, - "peer": true, - "requires": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "dependencies": { - "array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "peer": true - }, - "typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true - } - } - }, - "then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dev": true, - "peer": true, - "requires": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "dependencies": { - "@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", - "dev": true, - "peer": true - } - } - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "peer": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "ts-command-line-args": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.0.tgz", - "integrity": "sha512-Ff7Xt04WWCjj/cmPO9eWTJX3qpBZWuPWyQYG1vnxJao+alWWYjwJBc5aYz3h5p5dE08A6AnpkgiCtP/0KXXBYw==", - "dev": true, - "peer": true, - "requires": { - "@morgan-stanley/ts-mocking-bird": "^0.6.2", - "chalk": "^4.1.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "string-format": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "ts-essentials": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", - "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", - "dev": true, - "peer": true, - "requires": {} - }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "dev": true, - "peer": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "peer": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsort": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", - "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", - "dev": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "peer": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "peer": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "typechain": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.1.1.tgz", - "integrity": "sha512-uF/sUvnXTOVF2FHKhQYnxHk4su4JjZR8vr4mA2mBaRwHTbwh0jIlqARz9XJr1tA0l7afJGvEa1dTSi4zt039LQ==", - "dev": true, - "peer": true, - "requires": { - "@types/prettier": "^2.1.1", - "debug": "^4.3.1", - "fs-extra": "^7.0.0", - "glob": "7.1.7", - "js-sha3": "^0.8.0", - "lodash": "^4.17.15", - "mkdirp": "^1.0.4", - "prettier": "^2.3.1", - "ts-command-line-args": "^2.2.0", - "ts-essentials": "^7.0.1" - }, - "dependencies": { - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "peer": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "peer": true - } - } - }, - "typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "peer": true - }, - "typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", - "dev": true, - "peer": true - }, - "typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "peer": true - }, - "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "peer": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "undici": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.0.tgz", - "integrity": "sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==", - "dev": true, - "requires": { - "busboy": "^1.6.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "peer": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true, - "peer": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "peer": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "peer": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "web3-utils": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.9.0.tgz", - "integrity": "sha512-p++69rCNNfu2jM9n5+VD/g26l+qkEOQ1m6cfRQCbH8ZRrtquTmrirJMgTmyOoax5a5XRYOuws14aypCOs51pdQ==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereumjs-util": "^7.1.0", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "dependencies": { - "ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "peer": true, - "requires": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "peer": true, - "requires": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - } - } - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "peer": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "peer": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true, - "peer": true - }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", - "dev": true, - "peer": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "peer": true, - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "peer": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "peer": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "peer": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "peer": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "peer": true - }, - "wordwrapjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "dev": true, - "peer": true, - "requires": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" - }, - "dependencies": { - "typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true - } - } - }, - "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "requires": {} - }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true, - "peer": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "peer": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } - } -} diff --git a/assets/eip-7007/package.json b/assets/eip-7007/package.json deleted file mode 100644 index 0c5b28f233e4fd..00000000000000 --- a/assets/eip-7007/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "erc7007", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@openzeppelin/contracts": "^4.8.3", - "hardhat": "^2.14.0" - } -} diff --git a/assets/eip-7007/test/test.js b/assets/eip-7007/test/test.js deleted file mode 100644 index 31777ecb3ec2f6..00000000000000 --- a/assets/eip-7007/test/test.js +++ /dev/null @@ -1,90 +0,0 @@ -const { - time, - loadFixture, -} = require("@nomicfoundation/hardhat-network-helpers"); -const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs"); -const { expect } = require("chai"); -const { BigNumber } = require("ethers"); - -async function deployVerifierFixture() { - const Verifier = await ethers.getContractFactory("MockVerifier"); - const verifier = await Verifier.deploy(); - await verifier.deployed(); - return verifier; -} -const prompt = ethers.utils.toUtf8Bytes("test"); -const aigcData = ethers.utils.toUtf8Bytes("test"); -const uri = '"name": "test", "description": "test", "image": "test", "aigc_type": "test"'; -const validProof = ethers.utils.toUtf8Bytes("valid"); -const invalidProof = ethers.utils.toUtf8Bytes("invalid"); -const tokenId = BigNumber.from("70622639689279718371527342103894932928233838121221666359043189029713682937432"); - -describe("ERC7007.sol", function () { - - async function deployERC7007Fixture() { - const verifier = await deployVerifierFixture(); - - const ERC7007 = await ethers.getContractFactory("ERC7007"); - const erc7007 = await ERC7007.deploy("testing", "TEST", verifier.address); - await erc7007.deployed(); - return erc7007; - } - - describe("mint", function () { - it("should mint a token", async function () { - const erc7007 = await deployERC7007Fixture(); - const [owner] = await ethers.getSigners(); - await erc7007.mint(prompt, aigcData, uri, validProof); - expect(await erc7007.balanceOf(owner.address)).to.equal(1); - }); - - it("should not mint a token with invalid proof", async function () { - const erc7007 = await deployERC7007Fixture(); - await expect(erc7007.mint(prompt, aigcData, uri, invalidProof)).to.be.revertedWith("ERC7007: invalid proof"); - }); - - it("should not mint a token with same data twice", async function () { - const erc7007 = await deployERC7007Fixture(); - await erc7007.mint(prompt, aigcData, uri, validProof); - await expect(erc7007.mint(prompt, aigcData, uri, validProof)).to.be.revertedWith("ERC721: token already minted"); - }); - - it("should emit a Mint event", async function () { - const erc7007 = await deployERC7007Fixture(); - await expect(erc7007.mint(prompt, aigcData, uri, validProof)) - .to.emit(erc7007, "Mint") - }); - }); - - describe("metadata", function () { - it("should return token metadata", async function () { - const erc7007 = await deployERC7007Fixture(); - await erc7007.mint(prompt, aigcData, uri, validProof); - expect(await erc7007.tokenURI(tokenId)).to.equal('{"name": "test", "description": "test", "image": "test", "aigc_type": "test", "prompt": "test", "aigc_data": "test"}'); - }); - }); -}); - -describe("ERC7007Enumerable.sol", function () { - - async function deployERC7007EnumerableFixture() { - const verifier = await deployVerifierFixture(); - - const ERC7007Enumerable = await ethers.getContractFactory("MockERC7007Enumerable"); - const erc7007Enumerable = await ERC7007Enumerable.deploy("testing", "TEST", verifier.address); - await erc7007Enumerable.deployed(); - await erc7007Enumerable.mint(prompt, aigcData, uri, validProof); - return erc7007Enumerable; - } - - it("should return token id by prompt", async function () { - const erc7007Enumerable = await deployERC7007EnumerableFixture(); - expect(await erc7007Enumerable.tokenId(prompt)).to.equal(tokenId); - }); - - it("should return token prompt by id", async function () { - const erc7007Enumerable = await deployERC7007EnumerableFixture(); - expect(await erc7007Enumerable.prompt(tokenId)).to.equal("test"); - }); - -}); \ No newline at end of file diff --git a/assets/eip-7015/contracts/DelegatedErc721.sol b/assets/eip-7015/contracts/DelegatedErc721.sol deleted file mode 100644 index 4dbd47308bd960..00000000000000 --- a/assets/eip-7015/contracts/DelegatedErc721.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -import "./EIP7015.sol"; - -contract DelegatedErc721 is ERC7015, ERC721, ERC721URIStorage, Ownable { - error AlreadyMinted(); - error NotAuthorized(); - - uint256 private _nextTokenId; - - bytes32 public constant TYPEHASH = - keccak256("CreatorAttribution(string uri,uint256 nonce)"); - - // mapping of signature nonce to if it has been minted - mapping(uint256 => bool) public minted; - - constructor( - address initialOwner - ) EIP712("ERC7015", "1") ERC721("My Token", "TKN") Ownable(initialOwner) {} - - function delegatedSafeMint( - address to, - string memory uri, - uint256 nonce, - address creator, - bytes calldata signature - ) external { - uint256 tokenId = _nextTokenId++; - - if (!isAuthorizedToCreate(creator)) revert NotAuthorized(); - - // validate that the nonce has not been used - if (minted[nonce]) revert AlreadyMinted(); - minted[nonce] = true; - - bytes32 structHash = keccak256( - abi.encode(TYPEHASH, keccak256(bytes(uri)), nonce) - ); - - _validateSignature(structHash, creator, signature); - - _safeMint(to, tokenId); - _setTokenURI(tokenId, uri); - } - - // override required function to define if a signer is authorized to create - function isAuthorizedToCreate(address signer) internal view returns (bool) { - return signer == owner(); - } - - // The following functions are overrides required by Solidity. - function tokenURI( - uint256 tokenId - ) public view override(ERC721, ERC721URIStorage) returns (string memory) { - return super.tokenURI(tokenId); - } - - function supportsInterface( - bytes4 interfaceId - ) public view override(ERC721, ERC721URIStorage) returns (bool) { - return super.supportsInterface(interfaceId); - } -} diff --git a/assets/eip-7015/contracts/EIP7015.sol b/assets/eip-7015/contracts/EIP7015.sol deleted file mode 100644 index 7e591764a83cb3..00000000000000 --- a/assets/eip-7015/contracts/EIP7015.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity 0.8.20; -import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/interfaces/IERC1271.sol"; - -abstract contract ERC7015 is EIP712 { - error Invalid_Signature(); - event CreatorAttribution( - bytes32 structHash, - string domainName, - string version, - address creator, - bytes signature - ); - - /// @notice Define magic value to verify smart contract signatures (ERC1271). - bytes4 internal constant MAGIC_VALUE = - bytes4(keccak256("isValidSignature(bytes32,bytes)")); - - function _validateSignature( - bytes32 structHash, - address creator, - bytes memory signature - ) internal { - if (!_isValid(structHash, creator, signature)) revert Invalid_Signature(); - emit CreatorAttribution(structHash, "ERC7015", "1", creator, signature); - } - - function _isValid( - bytes32 structHash, - address signer, - bytes memory signature - ) internal view returns (bool) { - require(signer != address(0), "cannot validate"); - - bytes32 digest = _hashTypedDataV4(structHash); - - // if smart contract is the signer, verify using ERC-1271 smart-contract - /// signature verification method - if (signer.code.length != 0) { - try IERC1271(signer).isValidSignature(digest, signature) returns ( - bytes4 magicValue - ) { - return MAGIC_VALUE == magicValue; - } catch { - return false; - } - } - - // otherwise, recover signer and validate that it matches the expected - // signer - address recoveredSigner = ECDSA.recover(digest, signature); - return recoveredSigner == signer; - } -} diff --git a/assets/eip-7015/js-test/abi.ts b/assets/eip-7015/js-test/abi.ts deleted file mode 100644 index fb1cdb0d0c2810..00000000000000 --- a/assets/eip-7015/js-test/abi.ts +++ /dev/null @@ -1,419 +0,0 @@ -export const delegatedErc721ABI = [ - { - stateMutability: 'nonpayable', - type: 'constructor', - inputs: [ - { name: 'initialOwner', internalType: 'address', type: 'address' }, - ], - }, - { type: 'error', inputs: [], name: 'AlreadyMinted' }, - { type: 'error', inputs: [], name: 'ECDSAInvalidSignature' }, - { - type: 'error', - inputs: [{ name: 'length', internalType: 'uint256', type: 'uint256' }], - name: 'ECDSAInvalidSignatureLength', - }, - { - type: 'error', - inputs: [{ name: 's', internalType: 'bytes32', type: 'bytes32' }], - name: 'ECDSAInvalidSignatureS', - }, - { - type: 'error', - inputs: [ - { name: 'sender', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - { name: 'owner', internalType: 'address', type: 'address' }, - ], - name: 'ERC721IncorrectOwner', - }, - { - type: 'error', - inputs: [ - { name: 'operator', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - ], - name: 'ERC721InsufficientApproval', - }, - { - type: 'error', - inputs: [{ name: 'approver', internalType: 'address', type: 'address' }], - name: 'ERC721InvalidApprover', - }, - { - type: 'error', - inputs: [{ name: 'operator', internalType: 'address', type: 'address' }], - name: 'ERC721InvalidOperator', - }, - { - type: 'error', - inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], - name: 'ERC721InvalidOwner', - }, - { - type: 'error', - inputs: [{ name: 'receiver', internalType: 'address', type: 'address' }], - name: 'ERC721InvalidReceiver', - }, - { - type: 'error', - inputs: [{ name: 'sender', internalType: 'address', type: 'address' }], - name: 'ERC721InvalidSender', - }, - { - type: 'error', - inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], - name: 'ERC721NonexistentToken', - }, - { type: 'error', inputs: [], name: 'InvalidShortString' }, - { type: 'error', inputs: [], name: 'Invalid_Signature' }, - { - type: 'error', - inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], - name: 'OwnableInvalidOwner', - }, - { - type: 'error', - inputs: [{ name: 'account', internalType: 'address', type: 'address' }], - name: 'OwnableUnauthorizedAccount', - }, - { - type: 'error', - inputs: [{ name: 'str', internalType: 'string', type: 'string' }], - name: 'StringTooLong', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { - name: 'owner', - internalType: 'address', - type: 'address', - indexed: true, - }, - { - name: 'approved', - internalType: 'address', - type: 'address', - indexed: true, - }, - { - name: 'tokenId', - internalType: 'uint256', - type: 'uint256', - indexed: true, - }, - ], - name: 'Approval', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { - name: 'owner', - internalType: 'address', - type: 'address', - indexed: true, - }, - { - name: 'operator', - internalType: 'address', - type: 'address', - indexed: true, - }, - { name: 'approved', internalType: 'bool', type: 'bool', indexed: false }, - ], - name: 'ApprovalForAll', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { - name: '_fromTokenId', - internalType: 'uint256', - type: 'uint256', - indexed: false, - }, - { - name: '_toTokenId', - internalType: 'uint256', - type: 'uint256', - indexed: false, - }, - ], - name: 'BatchMetadataUpdate', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { - name: 'structHash', - internalType: 'bytes32', - type: 'bytes32', - indexed: false, - }, - { - name: 'domainName', - internalType: 'string', - type: 'string', - indexed: false, - }, - { - name: 'version', - internalType: 'string', - type: 'string', - indexed: false, - }, - { - name: 'creator', - internalType: 'address', - type: 'address', - indexed: false, - }, - { - name: 'signature', - internalType: 'bytes', - type: 'bytes', - indexed: false, - }, - ], - name: 'CreatorAttribution', - }, - { type: 'event', anonymous: false, inputs: [], name: 'EIP712DomainChanged' }, - { - type: 'event', - anonymous: false, - inputs: [ - { - name: '_tokenId', - internalType: 'uint256', - type: 'uint256', - indexed: false, - }, - ], - name: 'MetadataUpdate', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { - name: 'previousOwner', - internalType: 'address', - type: 'address', - indexed: true, - }, - { - name: 'newOwner', - internalType: 'address', - type: 'address', - indexed: true, - }, - ], - name: 'OwnershipTransferred', - }, - { - type: 'event', - anonymous: false, - inputs: [ - { name: 'from', internalType: 'address', type: 'address', indexed: true }, - { name: 'to', internalType: 'address', type: 'address', indexed: true }, - { - name: 'tokenId', - internalType: 'uint256', - type: 'uint256', - indexed: true, - }, - ], - name: 'Transfer', - }, - { - stateMutability: 'view', - type: 'function', - inputs: [], - name: 'TYPEHASH', - outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - ], - name: 'approve', - outputs: [], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'owner', internalType: 'address', type: 'address' }], - name: 'balanceOf', - outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'uri', internalType: 'string', type: 'string' }, - { name: 'nonce', internalType: 'uint256', type: 'uint256' }, - { name: 'signature', internalType: 'bytes', type: 'bytes' }, - ], - name: 'delegatedSafeMint', - outputs: [], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [], - name: 'eip712Domain', - outputs: [ - { name: 'fields', internalType: 'bytes1', type: 'bytes1' }, - { name: 'name', internalType: 'string', type: 'string' }, - { name: 'version', internalType: 'string', type: 'string' }, - { name: 'chainId', internalType: 'uint256', type: 'uint256' }, - { name: 'verifyingContract', internalType: 'address', type: 'address' }, - { name: 'salt', internalType: 'bytes32', type: 'bytes32' }, - { name: 'extensions', internalType: 'uint256[]', type: 'uint256[]' }, - ], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], - name: 'getApproved', - outputs: [{ name: '', internalType: 'address', type: 'address' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [ - { name: 'owner', internalType: 'address', type: 'address' }, - { name: 'operator', internalType: 'address', type: 'address' }, - ], - name: 'isApprovedForAll', - outputs: [{ name: '', internalType: 'bool', type: 'bool' }], - }, - { - stateMutability: 'pure', - type: 'function', - inputs: [ - { name: 'uri', internalType: 'string', type: 'string' }, - { name: 'nonce', internalType: 'uint256', type: 'uint256' }, - ], - name: 'makeAttributionStructHash', - outputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], - name: 'minted', - outputs: [{ name: '', internalType: 'bool', type: 'bool' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [], - name: 'name', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [], - name: 'owner', - outputs: [{ name: '', internalType: 'address', type: 'address' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], - name: 'ownerOf', - outputs: [{ name: '', internalType: 'address', type: 'address' }], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [], - name: 'renounceOwnership', - outputs: [], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'from', internalType: 'address', type: 'address' }, - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - ], - name: 'safeTransferFrom', - outputs: [], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'from', internalType: 'address', type: 'address' }, - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - { name: 'data', internalType: 'bytes', type: 'bytes' }, - ], - name: 'safeTransferFrom', - outputs: [], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'operator', internalType: 'address', type: 'address' }, - { name: 'approved', internalType: 'bool', type: 'bool' }, - ], - name: 'setApprovalForAll', - outputs: [], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'interfaceId', internalType: 'bytes4', type: 'bytes4' }], - name: 'supportsInterface', - outputs: [{ name: '', internalType: 'bool', type: 'bool' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [], - name: 'symbol', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - }, - { - stateMutability: 'view', - type: 'function', - inputs: [{ name: 'tokenId', internalType: 'uint256', type: 'uint256' }], - name: 'tokenURI', - outputs: [{ name: '', internalType: 'string', type: 'string' }], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [ - { name: 'from', internalType: 'address', type: 'address' }, - { name: 'to', internalType: 'address', type: 'address' }, - { name: 'tokenId', internalType: 'uint256', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [], - }, - { - stateMutability: 'nonpayable', - type: 'function', - inputs: [{ name: 'newOwner', internalType: 'address', type: 'address' }], - name: 'transferOwnership', - outputs: [], - }, -] as const \ No newline at end of file diff --git a/assets/eip-7015/js-test/delegatedErc721.test.ts b/assets/eip-7015/js-test/delegatedErc721.test.ts deleted file mode 100644 index 310ebf77791d5d..00000000000000 --- a/assets/eip-7015/js-test/delegatedErc721.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { - createTestClient, - http, - createWalletClient, - createPublicClient, - hashDomain, - Hex, - keccak256, - concat, - recoverAddress, - TypedDataDomain, -} from "viem"; -import { foundry } from "viem/chains"; -import { describe, it, beforeEach, expect } from "vitest"; -import { getTypesForEIP712Domain } from "viem"; -import { - delegatedErc721ABI -} from "./abi"; -import { bytecode as delegatedErc721ByteCode } from "../out/DelegatedErc721.sol/DelegatedErc721.json"; - - -const walletClient = createWalletClient({ - chain: foundry, - transport: http(), -}); - -export const walletClientWithAccount = createWalletClient({ - chain: foundry, - transport: http(), -}); - -const testClient = createTestClient({ - chain: foundry, - mode: "anvil", - transport: http(), -}); - -const publicClient = createPublicClient({ - chain: foundry, - transport: http(), -}); - -type Address = `0x${string}`; - - -const deployContractAndGetAddress = async ( - args: Parameters[0] -) => { - const hash = await walletClient.deployContract(args); - return ( - await publicClient.waitForTransactionReceipt({ - hash, - }) - ).contractAddress!; -}; - -type TestContext = { - creator: Address, - contractAddress: Address, -} - - -// JSON-RPC Account -const [ - creatorAccount, minter, randomAccount -] = (await walletClient.getAddresses()) as [Address, Address, Address, Address]; - -describe("DelegatedErc721", () => { - beforeEach(async (ctx) => { - const creator = creatorAccount; - - const contractAddress = await deployContractAndGetAddress({ - abi: delegatedErc721ABI, - bytecode: delegatedErc721ByteCode.object as `0x${string}`, - args: [creator], - account: creatorAccount, - }) - - ctx.contractAddress = contractAddress; - ctx.creator = creator; - }, 20 * 1000); - - // skip for now - we need to make this work on zora testnet chain too - it( - "can sign and mint a token and recover the signer from the CreatorAttribution event", - async ({ creator, contractAddress }) => { - - // 1. Have the creator sign a message to create a token - // sign a message for the CreatorAttribution, which has a TYPEHASH of CreatorAttribution(string uri,uint256 nonce) - - const tokenUri = 'ipfs://QmYXJ5Y2FzdC5qcGc'; - const nonce = 1n; - - // eipDomain params - const chainId = await walletClient.getChainId(); - - // have creator sign a message permitting a token to be created on the contract - const signature = await walletClient.signTypedData({ - types: { - CreatorAttribution: [ - { name: "uri", type: "string" }, - { name: "nonce", type: "uint256" }, - ], - }, - primaryType: "CreatorAttribution", - message: { - uri: tokenUri, - nonce, - }, - // signer of the message; the contract requires - // this to match the owner of the contract - account: creator, - domain: { - chainId: await walletClient.getChainId(), - verifyingContract: contractAddress, - // these two need to match the domain name and version in the erc712 contract - name: "ERC7015", - version: "1" - } - }); - - // 2. Have a collector submit the signature and mint the token - - const tx = await walletClient.writeContract({ - abi: delegatedErc721ABI, - address: contractAddress, - account: minter, - functionName: 'delegatedSafeMint', - args: [ - minter, tokenUri, nonce, signature - ] - }); - - const receipt = await publicClient.waitForTransactionReceipt({ - hash: tx - }); - - // check that the transaction succeeded - expect(receipt.status).toBe('success'); - - // 3. Get the CreatorAttribution event from the contract and recover the signer/creator from the emitted signature and params: - - // get the CreatorAttribution event from the erc1155 contract: - const topics = await publicClient.getContractEvents({ - abi: delegatedErc721ABI, - address: contractAddress, - eventName: "CreatorAttribution" - }); - - expect(topics.length).toBe(1); - - const creatorAttributionEventArgs = topics[0]!.args; - - const domain: TypedDataDomain = { - chainId, - name: creatorAttributionEventArgs.domainName!, - verifyingContract: contractAddress, - version: creatorAttributionEventArgs.version! - } - - // hash the eip712 domain based on the parameters emitted from the event: - const hashedDomain = hashDomain({ - domain, - types: { - EIP712Domain: getTypesForEIP712Domain({ domain }) - } - }); - - // re-build the eip-712 typed data hash, consisting of the hashed domain and the structHash emitted from the event: - const parts: Hex[] = ["0x1901", hashedDomain, creatorAttributionEventArgs.structHash!]; - - const hashedTypedData = keccak256(concat(parts)); - - // recover the signer from the hashed typed data and the signature: - const recoveredSigner = await recoverAddress({ - hash: hashedTypedData, - signature: signature!, - }); - - expect(recoveredSigner).toBe(creator); - - }, - 20 * 1000 - ); - -}); diff --git a/assets/eip-7015/package.json b/assets/eip-7015/package.json deleted file mode 100644 index 99802b953284a8..00000000000000 --- a/assets/eip-7015/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.0.1", - "private": true, - "dependencies": { - "@openzeppelin/contracts": "^5.0.0", - "viem": "^1.16.3", - "typescript": "^5.0.4", - "vite": "^4.3.5", - "vitest": "^0.34.6" - } -} \ No newline at end of file diff --git a/assets/eip-7053/digital-media-indexing-system-and-metadata-lookup.jpg b/assets/eip-7053/digital-media-indexing-system-and-metadata-lookup.jpg deleted file mode 100644 index 8afd23d2c4d9e5..00000000000000 Binary files a/assets/eip-7053/digital-media-indexing-system-and-metadata-lookup.jpg and /dev/null differ diff --git a/assets/eip-7066/ERC7066.sol b/assets/eip-7066/ERC7066.sol deleted file mode 100644 index dbabbefbe9d0a1..00000000000000 --- a/assets/eip-7066/ERC7066.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "./IERC7066.sol"; - -/// @title ERC7066: Lockable Extension for ERC721 -/// @dev Implementation for the Lockable extension ERC7066 for ERC721 -/// @author StreamNFT - -abstract contract ERC7066 is ERC721,IERC7066{ - - - /*/////////////////////////////////////////////////////////////// - ERC7066 EXTENSION STORAGE - //////////////////////////////////////////////////////////////*/ - - //Mapping from tokenId to user address for locker - mapping(uint256 => address) internal locker; - - /*/////////////////////////////////////////////////////////////// - ERC7066 LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Returns the locker for the tokenId - * address(0) means token is not locked - * reverts if token does not exist - */ - function lockerOf(uint256 tokenId) public virtual view override returns(address){ - require(_exists(tokenId), "ERC7066: Nonexistent token"); - return locker[tokenId]; - } - - /** - * @dev Public function to lock the token. Verifies if the msg.sender is owner or approved - * reverts otherwise - */ - function lock(uint256 tokenId) public virtual override{ - require(locker[tokenId]==address(0), "ERC7066: Locked"); - require(_isApprovedOrOwner(_msgSender(), tokenId), "Require owner or approved"); - _lock(tokenId,msg.sender); - } - - /** - * @dev Public function to lock the token. Verifies if the msg.sender is owner - * reverts otherwise - */ - function lock(uint256 tokenId, address _locker) public virtual override{ - require(locker[tokenId]==address(0), "ERC7066: Locked"); - require(ownerOf(tokenId)==msg.sender, "ERC7066: Require owner"); - _lock(tokenId,_locker); - } - - /** - * @dev Internal function to lock the token. - */ - function _lock(uint256 tokenId, address _locker) internal { - locker[tokenId]=_locker; - emit Lock(tokenId, _locker); - } - - /** - * @dev Public function to unlock the token. Verifies the msg.sender is locker - * reverts otherwise - */ - function unlock(uint256 tokenId) public virtual override{ - require(locker[tokenId]!=address(0), "ERC7066: Unlocked"); - require(locker[tokenId]==msg.sender); - _unlock(tokenId); - } - - /** - * @dev Internal function to unlock the token. - */ - function _unlock(uint256 tokenId) internal{ - delete locker[tokenId]; - emit Unlock(tokenId); - } - - /** - * @dev Public function to tranfer and lock the token. Reverts if caller is not owner or approved. - * Lock the token and set locker to caller - *. Optionally approve caller if bool setApprove flag is true - */ - function transferAndLock(address from, address to, uint256 tokenId, bool setApprove) public virtual override{ - _transferAndLock(tokenId,from,to,setApprove); - } - - /** - * @dev Internal function to tranfer, update locker/approve and lock the token. - */ - function _transferAndLock(uint256 tokenId, address from, address to, bool setApprove) internal { - transferFrom(from, to, tokenId); - if(setApprove){ - _approve(msg.sender, tokenId); - } - _lock(tokenId,msg.sender); - } - - /*/////////////////////////////////////////////////////////////// - OVERRIDES - //////////////////////////////////////////////////////////////*/ - - /** - * @dev Override approve to make sure token is unlocked - */ - function approve(address to, uint256 tokenId) public virtual override(IERC721, ERC721) { - require (locker[tokenId]==address(0), "ERC7066: Locked"); - super.approve(to, tokenId); - } - - /** - * @dev Override _beforeTokenTransfer to make sure token is unlocked or msg.sender is approved if - * token is lockApproved - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual override { - // if it is a Transfer or Burn, we always deal with one token, that is startTokenId - if (from != address(0)) { - require(locker[startTokenId]==address(0) - || ( locker[startTokenId]==msg.sender && (isApprovedForAll(ownerOf(startTokenId), msg.sender) - || getApproved(startTokenId) == msg.sender)), "ERC7066: Locked" ); - } - super._beforeTokenTransfer(from,to,startTokenId,quantity); - } - - /** - * @dev Override _afterTokenTransfer to make locker is purged - */ - function _afterTokenTransfer( - address from, - address to, - uint256 startTokenId, - uint256 quantity - ) internal virtual override { - // if it is a Transfer or Burn, we always deal with one token, that is startTokenId - if (from != address(0)) { - delete locker[startTokenId]; - } - super._afterTokenTransfer(from,to,startTokenId,quantity); - } - - /*/////////////////////////////////////////////////////////////// - ERC165 LOGIC - //////////////////////////////////////////////////////////////*/ - - /** - * @dev See {IERC165-supportsInterface}. - */ - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { - return - interfaceId == type(IERC7066).interfaceId || - super.supportsInterface(interfaceId); - } -} \ No newline at end of file diff --git a/assets/eip-7066/IERC7066.sol b/assets/eip-7066/IERC7066.sol deleted file mode 100644 index 0dbb08fafe162f..00000000000000 --- a/assets/eip-7066/IERC7066.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -/// @title Lockable Extension for ERC721 -/// @dev Interface for ERC7066 -/// @author StreamNFT - -interface IERC7066 is IERC721{ - - /** - * @dev Emitted when tokenId is locked - */ - event Lock (uint256 indexed tokenId, address _locker); - - /** - * @dev Emitted when tokenId is unlocked - */ - event Unlock (uint256 indexed tokenId); - - /** - * @dev Lock the tokenId if msg.sender is owner or approved and set locker to msg.sender - */ - function lock(uint256 tokenId) external; - - /** - * @dev Lock the tokenId if msg.sender is owner and set locker to _locker - */ - function lock(uint256 tokenId, address _locker) external; - - /** - * @dev Unlocks the tokenId if msg.sender is locker - */ - function unlock(uint256 tokenId) external; - - /** - * @dev Tranfer and lock the token if the msg.sender is owner or approved. - * Lock the token and set locker to caller - * Optionally approve caller if bool setApprove flag is true - */ - function transferAndLock(address from, address to, uint256 tokenId, bool setApprove) external; - - /** - * @dev Returns the wallet, that is stated as unlocking wallet for the tokenId. - * If address(0) returned, that means token is not locked. Any other result means token is locked. - */ - function lockerOf(uint256 tokenId) external view returns (address); -} \ No newline at end of file diff --git a/assets/eip-7066/test/test.js b/assets/eip-7066/test/test.js deleted file mode 100644 index 9cda9e3b867cb2..00000000000000 --- a/assets/eip-7066/test/test.js +++ /dev/null @@ -1,244 +0,0 @@ -const { expect } = require('chai'); -const { ethers } = require('hardhat'); - -describe('NewToken', function () { - let LOCK1 = 'lock(uint256)'; - let LOCK2 = 'lock(uint256,address)'; - async function deployMyNFTFixture() { - const [deployer, acc1, acc2, acc3] = await ethers.getSigners(); - const MyNFT = await ethers.getContractFactory('MyNFT'); - let myNFT = await MyNFT.deploy(); - await myNFT.deployed(); - await myNFT.connect(deployer).mint(); - - return { myNFT, deployer, acc1, acc2, acc3 }; - } - - async function approveUser1() { - const { myNFT, deployer, acc1, acc2, acc3 } = await deployMyNFTFixture(); - await myNFT.connect(deployer).approve(acc1.address, 0); - await myNFT.connect(deployer).setApprovalForAll(acc1.address, true); - return { myNFT, deployer, acc1, acc2, acc3 }; - } - - describe('lockerOf', () => { - it('Should return zero address for an unlocked token', async () => { - const { myNFT } = await deployMyNFTFixture(); - expect(await myNFT.lockerOf(0)).to.equal(ethers.constants.AddressZero); - }); - - it('Should revert as token does not exist', async () => { - const { myNFT } = await deployMyNFTFixture(); - await expect(myNFT.lockerOf(1)).to.be.revertedWith( - 'ERC7066: Nonexistent token' - ); - }); - }); - - describe('lock - with one parameter', () => { - it('Should not lock if already locked', async () => { - const { myNFT, deployer } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK1](0); - await expect(myNFT.connect(deployer)[LOCK1](0)).to.be.revertedWith( - 'ERC7066: Locked' - ); - }); - - it('Should not allow random user to lock', async () => { - const { myNFT, acc1 } = await deployMyNFTFixture(); - await expect(myNFT.connect(acc1)[LOCK1](0)).to.be.revertedWith( - 'Require owner or approved' - ); - }); - - it('Should be able to lock token by owner', async () => { - const { myNFT, deployer } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK1](0); - expect(await myNFT.lockerOf(0)).to.equal(deployer.address); - }); - - it('Should be able to lock token by approved_user', async () => { - const { myNFT, acc1 } = await approveUser1(); - await myNFT.connect(acc1)[LOCK1](0); - expect(await myNFT.lockerOf(0)).to.equal(acc1.address); - }); - }); - - describe('lock - with two parameters', () => { - it('Should not lock token if already locked', async () => { - const { myNFT, deployer } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK2](0, deployer.address); - await expect( - myNFT.connect(deployer)[LOCK2](0, deployer.address) - ).to.be.revertedWith('ERC7066: Locked'); - }); - - it('Should not allow random user to lock', async () => { - const { myNFT, acc1 } = await deployMyNFTFixture(); - await expect( - myNFT.connect(acc1)[LOCK2](0, acc1.address) - ).to.be.revertedWith('ERC7066: Require owner'); - }); - - it('Should not allow approved_user to lock', async () => { - const { myNFT, acc1 } = await approveUser1(); - await expect( - myNFT.connect(acc1)[LOCK2](0, acc1.address) - ).to.be.revertedWith('ERC7066: Require owner'); - }); - - it('Should allow token owner to lock, locker is owner', async () => { - const { myNFT, deployer } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK2](0, deployer.address); - expect(await myNFT.lockerOf(0)).to.equal(deployer.address); - }); - - it('Should allow token owner to lock, locker is zero-address', async () => { - const { myNFT, deployer } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK2](0, ethers.constants.AddressZero); - expect(await myNFT.lockerOf(0)).to.equal(ethers.constants.AddressZero); - }); - }); - - describe('unlock', () => { - it('Should not unlock token if not locked', async () => { - const { myNFT, deployer } = await deployMyNFTFixture(); - await expect(myNFT.connect(deployer).unlock(0)).to.be.revertedWith( - 'ERC7066: Unlocked' - ); - }); - - it('Should not unlock token if msg.sender is not the locker', async () => { - const { myNFT, deployer, acc1 } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK1](0); - await expect(myNFT.connect(acc1).unlock(0)).to.be.reverted; - }); - - it('Should allow owner to unlock', async () => { - const { myNFT, deployer } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK1](0); - await myNFT.connect(deployer).unlock(0); - expect(await myNFT.lockerOf(0)).to.equal(ethers.constants.AddressZero); - }); - - it('Should allow approver to unlock', async () => { - const { myNFT, acc1 } = await approveUser1(); - await myNFT.connect(acc1)[LOCK1](0); - await myNFT.connect(acc1).unlock(0); - expect(await myNFT.lockerOf(0)).to.equal(ethers.constants.AddressZero); - }); - }); - - describe('transferAndLock', () => { - it('Should not allow if the user is not owner or approved', async () => { - const { myNFT, deployer, acc1 } = await deployMyNFTFixture(); - await expect( - myNFT - .connect(acc1) - .transferAndLock(0, deployer.address, acc1.address, false) - ).to.be.reverted; - }); - - it('Should transfer and lock, msg.sender - owner ,setApproval - true', async () => { - const { myNFT, deployer, acc1 } = await deployMyNFTFixture(); - await myNFT - .connect(deployer) - .transferAndLock(0, deployer.address, acc1.address, true); - expect(await myNFT.ownerOf(0)).to.equal(acc1.address); - expect(await myNFT.lockerOf(0)).to.equal(deployer.address); - expect(await myNFT.getApproved(0)).to.equal(deployer.address); - }); - - it('Should transfer and lock,msg.sender - owner, setApproval - false', async () => { - const { myNFT, deployer, acc1 } = await deployMyNFTFixture(); - await myNFT - .connect(deployer) - .transferAndLock(0, deployer.address, acc1.address, false); - expect(await myNFT.ownerOf(0)).to.equal(acc1.address); - expect(await myNFT.lockerOf(0)).to.equal(deployer.address); - expect(await myNFT.getApproved(0)).to.equal(ethers.constants.AddressZero); - }); - - it('Should transfer and lock, msg.sender - approved_user, setApproval - true', async () => { - const { myNFT, deployer, acc1 } = await approveUser1(); - await myNFT - .connect(acc1) - .transferAndLock(0, deployer.address, acc1.address, true); - expect(await myNFT.ownerOf(0)).to.equal(acc1.address); - expect(await myNFT.lockerOf(0)).to.equal(acc1.address); - expect(await myNFT.getApproved(0)).to.equal(acc1.address); - }); - - it('Should transfer and lock, msg.sender - approved_user,setApproval - false', async () => { - const { myNFT, deployer, acc1 } = await approveUser1(); - await myNFT - .connect(acc1) - .transferAndLock(0, deployer.address, acc1.address, false); - expect(await myNFT.ownerOf(0)).to.equal(acc1.address); - expect(await myNFT.lockerOf(0)).to.equal(acc1.address); - expect(await myNFT.getApproved(0)).to.equal(ethers.constants.AddressZero); - }); - }); - - describe('approve', () => { - it('Should not allow if the user is not owner/approved_user', async () => { - const { myNFT, acc1 } = await deployMyNFTFixture(); - await expect(myNFT.connect(acc1).approve(acc1.address, 0)).to.be.reverted; - }); - - it('Should not allow if token is locked', async () => { - const { myNFT, deployer, acc1 } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK1](0); - await expect( - myNFT.connect(deployer).approve(acc1.address, 0) - ).to.be.revertedWith('ERC7066: Locked'); - }); - - it('Should allow user with isApprovedForAll to approve', async () => { - const { myNFT, acc1, acc2 } = await approveUser1(); - await myNFT.connect(acc1).approve(acc2.address, 0); - expect(await myNFT.getApproved(0)).to.equal(acc2.address); - }); - }); - - describe('transferFrom', () => { - describe('beforeTokenTransfer', () => { - it('Should not allow transfer if the token is locked', async () => { - const { myNFT, deployer, acc1 } = await deployMyNFTFixture(); - await myNFT.connect(deployer)[LOCK1](0); - await expect( - myNFT - .connect(deployer) - .transferFrom(deployer.address, acc1.address, 0) - ).to.be.revertedWith('ERC7066: Locked'); - }); - - it('Should not allow transfer if user is not owner/approved_user', async () => { - const { myNFT, deployer, acc1, acc2 } = await approveUser1(); - await expect( - myNFT.connect(acc2).transferFrom(deployer.address, acc1.address, 0) - ).to.be.revertedWith('ERC721: caller is not token owner or approved'); - }); - }); - - describe('afterTokenTransfer', async () => { - it('Should check if token has a locker', async () => { - const { myNFT, deployer, acc1 } = await deployMyNFTFixture(); - await myNFT - .connect(deployer) - .transferFrom(deployer.address, acc1.address, 0); - expect(await myNFT.lockerOf(0)).to.equal(ethers.constants.AddressZero); - expect(await myNFT.ownerOf(0)).to.equal(acc1.address); - }); - - it('Should be able to transfer by approved_user', async () => { - const { myNFT, deployer, acc1 } = await approveUser1(); - await myNFT - .connect(acc1) - .transferFrom(deployer.address, acc1.address, 0); - expect(await myNFT.lockerOf(0)).to.equal(ethers.constants.AddressZero); - expect(await myNFT.ownerOf(0)).to.equal(acc1.address); - }); - }); - }); -}); diff --git a/assets/eip-7092/BondStorage.sol b/assets/eip-7092/BondStorage.sol deleted file mode 100644 index d68928ac5ed636..00000000000000 --- a/assets/eip-7092/BondStorage.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.18; - -contract BondStorage { - struct Bond { - string isin; - string name; - string symbol; - address currency; - address currencyOfCoupon; - uint8 decimals; - uint256 denomination; - uint256 issueVolume; - uint256 couponRate; - uint256 couponType; - uint256 couponFrequency; - uint256 issueDate; - uint256 maturityDate; - uint256 dayCountBasis; - } - - struct Issuer { - string name; - string email; - string country; - string headquarters; - string issuerType; - string creditRating; - uint256 carbonCredit; - address issuerAddress; - } - - struct IssueData { - address investor; - uint256 principal; - } - - enum BondStatus {UNREGISTERED, SUBMITTED, ISSUED, REDEEMED} - - mapping(string => Bond) internal _bond; - mapping(string => Issuer) internal _issuer; - mapping(address => uint256) internal _principals; - mapping(address => mapping(address => uint256)) internal _approvals; - - string internal bondISIN; - string internal _countryOfIssuance; - - BondStatus internal _bondStatus; - IssueData[] internal _listOfInvestors; - - address internal _bondManager; - - modifier onlyBondManager { - require(msg.sender == _bondManager, "BondStorage: ONLY_BOND_MANAGER"); - _; - } - - event BondIssued(IssueData[] _issueData, Bond _bond); - event BondRedeemed(); - - function bondStatus() external view returns(BondStatus) { - return _bondStatus; - } - - function listOfInvestors() external view returns(IssueData[] memory) { - return _listOfInvestors; - } - - function bondInfo() public view returns(Bond memory) { - return _bond[bondISIN]; - } - - function issuerInfo() public view returns(Issuer memory) { - return _issuer[bondISIN]; - } -} diff --git a/assets/eip-7092/ERC7092.sol b/assets/eip-7092/ERC7092.sol deleted file mode 100644 index cf410ed8edb7c3..00000000000000 --- a/assets/eip-7092/ERC7092.sol +++ /dev/null @@ -1,298 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "./IERC7092.sol"; -import "./BondStorage.sol"; - -/** -* @notice Minimum implementation of the ERC7092 -*/ -contract ERC7092 is IERC7092, BondStorage { - constructor( - string memory _bondISIN, - Issuer memory _issuerInfo - ) { - bondISIN = _bondISIN; - _bondManager = msg.sender; - _issuer[_bondISIN] = _issuerInfo; - } - - function issue( - IssueData[] memory _issueData, - Bond memory _bond - ) external onlyBondManager { - _issue(_issueData, _bond); - } - - function redeem() external onlyBondManager { - _redeem(_listOfInvestors); - } - - function _issue(IssueData[] memory _issueData, Bond memory _bondInfo) internal virtual { - uint256 volume; - uint256 _issueVolume = _bondInfo.issueVolume; - - for(uint256 i; i < _issueData.length; i++) { - address investor = _issueData[i].investor; - uint256 principal = _issueData[i].principal; - uint256 _denomination = _bondInfo.denomination; - - require(investor != address(0), "ERC7092: ZERO_ADDRESS_INVESTOR"); - require(principal != 0 && (principal * _denomination) % _denomination == 0, "ERC: INVALID_PRINCIPAL_AMOUNT"); - - volume += principal; - _principals[investor] = principal; - _listOfInvestors.push(IssueData({investor:investor, principal:principal})); - } - - _bond[bondISIN] = _bondInfo; - _bond[bondISIN].issueDate = block.timestamp; - _bondStatus = BondStatus.ISSUED; - - uint256 _maturityDate = _bond[bondISIN].maturityDate; - - require(_maturityDate > block.timestamp, "ERC7092: INVALID_MATURITY_DATE"); - require(volume == _issueVolume, "ERC7092: INVALID_ISSUE_VOLUME"); - - emit BondIssued(_issueData, _bondInfo); - } - - function _redeem(IssueData[] memory _bondsData) internal virtual { - uint256 _maturityDate = _bond[bondISIN].maturityDate; - require(block.timestamp > _maturityDate, "ERC2721: WAIT_MATURITY"); - - for(uint256 i; i < _bondsData.length; i++) { - if(_principals[_bondsData[i].investor] != 0) { - _principals[_bondsData[i].investor] = 0; - } - } - - _bondStatus = BondStatus.REDEEMED; - emit BondRedeemed(); - } - - function isin() external view returns(string memory) { - return _bond[bondISIN].isin; - } - - function name() external view returns(string memory) { - return _bond[bondISIN].name; - } - - function symbol() external view returns(string memory) { - return _bond[bondISIN].symbol; - } - - function decimals() external view returns(uint8) { - return _bond[bondISIN].decimals; - } - - function currency() external view returns(address) { - return _bond[bondISIN].currency; - } - - function currencyOfCoupon() external view returns(address) { - return _bond[bondISIN].currencyOfCoupon; - } - - function denomination() public view returns(uint256) { - return _bond[bondISIN].denomination; - } - - function issueVolume() external view returns(uint256) { - return _bond[bondISIN].issueVolume; - } - - function couponRate() external view returns(uint256) { - return _bond[bondISIN].couponRate; - } - - function couponType() external view returns(uint256) { - return _bond[bondISIN].couponType; - } - - function couponFrequency() external view returns(uint256) { - return _bond[bondISIN].couponFrequency; - } - - function issueDate() external view returns(uint256) { - return _bond[bondISIN].issueDate; - } - - function maturityDate() public view returns(uint256) { - return _bond[bondISIN].maturityDate; - } - - function dayCountBasis() external view returns(uint256) { - return _bond[bondISIN].dayCountBasis; - } - - function principalOf(address _account) external view returns(uint256) { - return _principals[_account]; - } - - function balanceOf(address _account) public view returns(uint256) { - require(_bondStatus == BondStatus.ISSUED, "ERC7092: NOT_ISSUED_OR_REDEEMED"); - - return _principals[_account] / _bond[bondISIN].denomination; - } - - function totalSupply() public view returns(uint256) { - require(_bondStatus == BondStatus.ISSUED, "ERC7092: NOT_ISSUED_OR_REDEEMED"); - - return _bond[bondISIN].issueVolume / _bond[bondISIN].denomination; - } - - function approval(address _owner, address _spender) external view returns(uint256) { - return _approvals[_owner][_spender]; - } - - function approve(address _spender, uint256 _amount) external returns(bool) { - address _owner = msg.sender; - - _approve(_owner, _spender, _amount); - - return true; - } - - function approveAll(address _spender) external external returns(bool) { - address _owner = msg.sender; - uint256 _amount = _principals[_owner]; - - _approve(_owner, _spender, _amount); - - return true; - } - - function decreaseAllowance(address _spender, uint256 _amount) external returns(bool) { - address _owner = msg.sender; - - _decreaseAllowance(_owner, _spender, _amount); - - return true; - } - - function decreaseAllowanceForAll(address _spender) external returns(bool) { - address _owner = msg.sender; - uint256 _amount = _principals[_owner]; - - _decreaseAllowance(_owner, _spender, _amount); - - return true; - } - - function transfer(address _to, uint256 _amount, bytes calldata _data) external returns(bool) { - address _from = msg.sender; - - _transfer(_from, _to, _amount, _data); - - return true; - } - - function transferAll(address _to, bytes calldata _data) external returns(bool) { - address _from = msg.sender; - uint256 _amount = _principals[_from]; - - _transfer(_from, _to, _amount, _data); - - return true; - } - - function transferFrom(address _from, address _to, uint256 _amount, bytes calldata _data) external returns(bool) { - address _spender = msg.sender; - - _spendApproval(_from, _spender, _amount); - - _transfer(_from, _to, _amount, _data); - - return true; - } - - function transferAllFrom(address _from, address _to, bytes calldata _data) external returns(bool) { - address _spender = msg.sender; - uint256 _amount = _principals[_from]; - - _spendApproval(_from, _spender, _amount); - - _transfer(_from, _to, _amount, _data); - - return true; - } - - function _approve(address _owner, address _spender, uint256 _amount) internal virtual { - require(_owner != address(0), "ERC7092: OWNER_ZERO_ADDRESS"); - require(_spender != address(0), "ERC7092: SPENDER_ZERO_ADDRESS"); - require(_amount > 0, "ERC7092: INVALID_AMOUNT"); - - uint256 principal = _principals[_owner]; - uint256 _approval = _approvals[_owner][_spender]; - uint256 _denomination = denomination(); - uint256 _maturityDate = maturityDate(); - - require(block.timestamp < _maturityDate, "ERC7092: BONDS_MATURED"); - require(_amount <= principal, "ERC7092: INSUFFICIENT_BALANCE"); - require(_amount % _denomination == 0, "ERC7092: INVALID_AMOUNT"); - - _approvals[_owner][_spender] = _approval + _amount; - - emit Approved(_owner, _spender, _amount); - } - - function _decreaseAllowance(address _owner, address _spender, uint256 _amount) internal virtual { - require(_owner != address(0), "ERC7092: OWNER_ZERO_ADDRESS"); - require(_spender != address(0), "ERC7092: SPENDER_ZERO_ADDRESS"); - require(_amount > 0, "ERC7092: INVALID_AMOUNT"); - - uint256 _approval = _approvals[_owner][_spender]; - uint256 _denomination = denomination(); - uint256 _maturityDate = maturityDate(); - - require(block.timestamp < _maturityDate, "ERC7092: BONDS_MATURED"); - require(_amount <= _approval, "ERC7092: NOT_ENOUGH_APPROVAL"); - require(_amount % _denomination == 0, "ERC7092: INVALID_AMOUNT"); - - _approvals[_owner][_spender] = _approval - _amount; - - emit AllowanceDecreased(_owner, _spender, _amount); - } - - function _transfer(address _from, address _to, uint256 _amount, bytes calldata _data) internal virtual { - require(_from != address(0), "ERC7092: OWNER_ZERO_ADDRESS"); - require(_to != address(0), "ERC7092: SPENDER_ZERO_ADDRESS"); - require(_amount > 0, "ERC7092: INVALID_AMOUNT"); - - uint256 principal = _principals[_from]; - uint256 _denomination = denomination(); - uint256 _maturityDate = maturityDate(); - - require(block.timestamp < _maturityDate, "ERC7092: BONDS_MATURED"); - require(_amount <= principal, "ERC7092: INSUFFICIENT_BALANCE"); - require(_amount % _denomination == 0, "ERC7092: INVALID_AMOUNT"); - - _beforeBondTransfer(_from, _to, _amount, _data); - - uint256 principalTo = _principals[_to]; - - unchecked { - _principals[_from] = principal - _amount; - _principals[_to] = principalTo + _amount; - } - - emit Transferred(_from, _to, _amount, _data); - - _afterBondTransfer(_from, _to, _amount, _data); - } - - function _spendApproval(address _from, address _spender, uint256 _amount) internal virtual { - uint256 currentApproval = _approvals[_from][_spender]; - require(_amount <= currentApproval, "ERC7092: INSUFFICIENT_ALLOWANCE"); - - unchecked { - _approvals[_from][_spender] = currentApproval - _amount; - } - } - - function _beforeBondTransfer(address _from, address _to, uint256 _amount, bytes calldata _data) internal virtual {} - - function _afterBondTransfer(address _from, address _to, uint256 _amount, bytes calldata _data) internal virtual {} -} diff --git a/assets/eip-7092/IERC7092.sol b/assets/eip-7092/IERC7092.sol deleted file mode 100644 index bae6f376f88732..00000000000000 --- a/assets/eip-7092/IERC7092.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -/** -* @title ERC-7092 Financial Bonds tandard -*/ -interface IERC7092 { - /** - * @notice Returns the bond isin - */ - function isin() external view returns(string memory); - - /** - * @notice Returns the bond name - */ - function name() external view returns(string memory); - - /** - * @notice Returns the bond symbol - */ - function symbol() external view returns(string memory); - - /** - * @notice Returns the number of decimals the bond uses - e.g `10`, means to divide the token amount by `10000000000` - * - * OPTIONAL - */ - function decimals() external view returns(uint8); - - /** - * @notice Returns the bond currency. This is the contract address of the token used to pay and return the bond principal - */ - function currency() external view returns(address); - - /** - * @notice Returns the copoun currency. This is the contract address of the token used to pay coupons. It can be same as the the one used for the principal - */ - function currencyOfCoupon() external view returns(address); - - /** - * @notice Returns the bond denominiation. This is the minimum amount in which the Bonds may be issued. It must be expressend in unit of the principal currency - * ex: If the denomination is equal to 1,000 and the currency is USDC, then the bond denomination is equal to 1,000 USDC - */ - function denomination() external view returns(uint256); - - /** - * @notice Returns the issue volume (total debt amount). It is RECOMMENDED to express the issue volume in denomination unit. - * ex: if denomination = $1,000, and the total debt is $5,000,000 - * then, issueVolume() = $5,000, 000 / $1,000 = 5,000 bonds - */ - function issueVolume() external view returns(uint256); - - /** - * @notice Returns the bond interest rate. It is RECOMMENDED to express the interest rate in basis point unit. - * 1 basis point = 0.01% = 0.0001 - * ex: if interest rate = 5%, then coupon() => 500 basis points - */ - function couponRate() external view returns(uint256); - - /** - * @notice Returns the coupon type - * ex: 0: Zero coupon, 1: Fixed Rate, 2: Floating Rate, etc... - */ - function couponType() external view returns(uint256); - - /** - * @notice Returns the coupon frequency, i.e. the number of times coupons are paid in a year. - */ - function couponFrequency() external view returns(uint256); - - /** - * @notice Returns the date when bonds were issued to investors. This is a Unix Timestamp like the one returned by block.timestamp - */ - function issueDate() external view returns(uint256); - - /** - * @notice Returns the bond maturity date, i.e, the date when the pricipal is repaid. This is a Unix Timestamp like the one returned by block.timestamp - * The maturity date MUST be greater than the issue date - */ - function maturityDate() external view returns(uint256); - - /** - * @notice Returns the day count basis - * Ex: 0: actual/actual, 1: actual/360, etc... - */ - function dayCountBasis() external view returns(uint256); - - /** - * @notice Returns the principal of an account. It is RECOMMENDED to express the principal in denomination unit. - * Ex: if denomination = $1,000, and the user has invested $5,000 - * then principalOf(_account) = 5,000/1,000 = 5 - * @param _account account address - */ - function principalOf(address _account) external view returns(uint256); - - /** - * @notice Returns the amount of tokens the `_spender` account has been authorized by the `_owner`` - * acount to manage their bonds - * @param _owner the bondholder address - * @param _spender the address that has been authorized by the bondholder - */ - function approval(address _owner, address _spender) external view returns(uint256); - - /** - * @notice Authorizes `_spender` account to manage `_amount`of their bonds - * @param _spender the address to be authorized by the bondholder - * @param _amount amount of bond to approve. _amount MUST be a multiple of denomination - */ - function approve(address _spender, uint256 _amount) external; - - /** - * @notice Authorizes the `_spender` account to manage all their bonds - * @param _spender the address to be authorized by the bondholder - */ - function approveAll(address _spender) external; - - /** - * @notice Lowers the allowance of `_spender` by `_amount` - * @param _spender the address to be authorized by the bondholder - * @param _amount amount of bond to remove approval; _amount MUST be a multiple of denomination - */ - function decreaseAllowance(address _spender, uint256 _amount) external; - - /** - * @notice Removes the allowance for `_spender` - * @param _spender the address to remove the authorization by from - */ - function decreaseAllowanceForAll(address _spender) external; - - /** - * @notice Moves `_amount` bonds to address `_to` - * @param _to the address to send the bonds to - * @param _amount amount of bond to transfer. _amount MUST be a multiple of denomination - * @param _data additional information provided by the token holder - */ - function transfer(address _to, uint256 _amount, bytes calldata _data) external; - - /** - * @notice Moves all bonds to address `_to` - * @param _to the address to send the bonds to - * @param _data additional information provided by the token holder - */ - function transferAll(address _to, bytes calldata _data) external; - - /** - * @notice Moves `_amount` bonds from an account that has authorized the caller through the approve function - * @param _from the bondholder address - * @param _to the address to transfer bonds to - * @param _amount amount of bond to transfer. _amount MUST be a multiple of denomination - * @param _data additional information provided by the token holder - */ - function transferFrom(address _from, address _to, uint256 _amount, bytes calldata _data) external; - - /** - * @notice Moves all bonds from `_from` to `_to`. The caller must have been authorized through the approve function - * @param _from the bondholder address - * @param _to the address to transfer bonds to - * @param _data additional information provided by the token holder - */ - function transferAllFrom(address _from, address _to, bytes calldata _data) external; - - /** - * @notice MUST be emitted when bonds are transferred - * @param _from the account that owns bonds - * @param _to the account that receives the bond - * @param _amount the amount of bonds to be transferred - * @param _data additional information provided by the token holder - */ - event Transferred(address _from, address _to, uint256 _amount, bytes _data); - - /** - * @notice MUST be emitted when an account is approved - * @param _owner the bonds owner - * @param _spender the account to be allowed to spend bonds - * @param _amount the amount allowed by _owner to be spent by _spender. - */ - event Approved(address _owner, address _spender, uint256 _amount); - - /** - * @notice MUST be emmitted when the `_owner` decreases allowance from `_sepnder` by quantity `_amount` - * @param _owner the bonds owner - * @param _spender the account that has been allowed to spend bonds - * @param _amount the amount of tokens to disapprove - */ - event AllowanceDecreased(address _owner, address _spender, uint256 _amount); -} diff --git a/assets/eip-7092/coupons/CouponMath.sol b/assets/eip-7092/coupons/CouponMath.sol deleted file mode 100644 index 458b42d5d2c914..00000000000000 --- a/assets/eip-7092/coupons/CouponMath.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.0; - -import "../IERC7092.sol"; - -library CouponMath { - /** - * @notice Returns the annual interest earned by an account in bips - * @param _investor the investor account address - * @param _bondContract the bond contract address - */ - function annualInterest( - address _investor, - address _bondContract - ) external view returns(uint256) { - uint256 couponRate = IERC7092(_bondContract).couponRate(); - uint256 denomination = IERC7092(_bondContract).denomination(); - uint256 principal = IERC7092(_bondContract).principalOf(_investor); - - return principal * denomination * couponRate; - } - - /** - * @notice Returns the interest earned by an account in period `_duration` in bips - * @param _investor the investor account address - * @param _duration time ellapsed since the last coupon payment - * @param _bondContract the bond contract address - */ - function interest( - address _investor, - uint256 _duration, - address _bondContract - ) external view returns(uint256) { - uint256 couponRate = IERC7092(_bondContract).couponRate(); - uint256 denomination = IERC7092(_bondContract).denomination(); - uint256 principal = IERC7092(_bondContract).principalOf(_investor); - uint256 frequency = IERC7092(_bondContract).couponFrequency(); - uint256 numberOfDays = _numberOfDays(_bondContract); - - return principal * denomination * couponRate * _duration / (frequency * numberOfDays); - } - - /** - * @notice Returns the number of days in a year based on the base count basis used. - * Here we just check for two values. - */ - function _numberOfDays(address _bondContract) private view returns(uint256) { - uint256 dayCountBasis = IERC7092(_bondContract).dayCountBasis(); - - if(dayCountBasis == 0) { - return 365; - } else if(dayCountBasis == 1) { - return 360; - } else { - revert("invalid day count basis value"); - } - } -} diff --git a/assets/eip-7093/social-recovery-flow.svg b/assets/eip-7093/social-recovery-flow.svg deleted file mode 100644 index 0ccb6cbf77ffe9..00000000000000 --- a/assets/eip-7093/social-recovery-flow.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - Generate 712-MsgHash+Identity guardSigners + signatures(sequential Verify)R1 keyIGuardSignerVerifyOpenID keyIGuardSignerVerifyEmail keyIGuardSignerVerifyCA key IERC1271 VerifyEOA key ECDSA VerifyIRecoveryAccountWalletrecoveryConfig1:1.List of Guardians with property2.Conditional threshold + lock period3.PolicyVerification Contract Address AupdateGuardians()TXstartRecovery()TX: Param: 1.PolicyVerification Contract Address A 2.newOwner 3.List:(guardSigner + signature)1. VerifyPermissions2.RecoveryPolicyVerifyAs Param + guardSigners Address AIRecoveryPolicyVerfiy3.Based on return (verification success, weight),Handle account recovery:replaceOwnerKey/temporaryStoragerecoveryConfig2:1.List of Guardians with property2.Conditional threshold + lock period3.PolicyVerification Contract Address B \ No newline at end of file diff --git a/assets/eip-7231/contracts/ERC7231.sol b/assets/eip-7231/contracts/ERC7231.sol deleted file mode 100644 index 85be7e549fac0f..00000000000000 --- a/assets/eip-7231/contracts/ERC7231.sol +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import { StrSlice, toSlice } from "@dk1a/solidity-stringutils/src/StrSlice.sol"; - - -import "./interfaces/IERC7231.sol"; - - -contract ERC7231 is IERC7231,ERC721 { - - using { toSlice } for string; - mapping (uint256 => bytes32) _idMultiIdentitiesRootBinding; - mapping(uint256 => mapping(bytes32 => bool)) internal _idSignatureSetting; - - constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {} - - /** - * @dev Checks if the sender owns NFT with ID tokenId - * @param id NFT ID of the signing NFT - */ - modifier onlyTokenOwner(uint256 id) { - //nft owner check - require(ownerOf(id) == msg.sender,"nft owner is not correct"); - _; - } - - - /** - * @notice - * @dev set the user ID binding information of NFT with multiIdentitiesRoot - * @param id nft id - * @param multiIdentitiesRoot multi UserID Root data hash - */ - function setIdentitiesRoot( - uint256 id, - bytes32 multiIdentitiesRoot - ) external { - - _idMultiIdentitiesRootBinding[id] = multiIdentitiesRoot; - emit SetIdentitiesRoot(id,multiIdentitiesRoot); - } - - /** - * @notice - * @dev Update the user ID binding information of NFT - * @param id nft id - */ - function getIdentitiesRoot( - uint256 id - ) external view returns(bytes32){ - - return _idMultiIdentitiesRootBinding[id]; - } - - /** - * @notice - * @dev verify the userIDs binding - * @param id nft id - * @param multiIdentitiesRoot msg hash to veriry - * @param userIDs userIDs for check - * @param signature ECDSA signature - */ - function verifyIdentitiesBinding( - uint256 id,address nftOwnerAddress,string[] memory userIDs,bytes32 multiIdentitiesRoot, bytes calldata signature - ) external view returns (bool){ - - //nft ownership validation - require(ownerOf(id) == nftOwnerAddress,"nft owner is not correct"); - - //multi-identities root consistency validation - require(_idMultiIdentitiesRootBinding[id] == multiIdentitiesRoot,""); - - //user id format validtion - uint256 userIDLen = userIDs.length; - require(userIDLen > 0,"userID cannot be empty"); - - for(uint i = 0 ;i < userIDLen ;i ++){ - _verifyUserID(userIDs[i]); - } - - //signature validation from nft owner - bool isVerified = SignatureChecker.isValidSignatureNow(msg.sender,multiIdentitiesRoot,signature); - return isVerified; - - } - - /** - * @notice - * @dev verify the userIDs binding - * @param userID nft id - */ - function _verifyUserID(string memory userID) internal view{ - - require(bytes(userID).length > 0,"userID can not be empty"); - - //first part(encryption algorithm or did) check - string memory strSplit = ":"; - bool found; - StrSlice left; - StrSlice right = userID.toSlice(); - (found, left, right) = right.splitOnce(strSplit.toSlice()); - require(found,"the first part delimiter does not exist"); - require(bytes(left.toString()).length > 0,"the first part does not exist"); - - //second part(Organization Information) check - (found, left, right) = right.splitOnce(strSplit.toSlice()); - require(found,"the second part delimiter does not exist"); - require(bytes(left.toString()).length > 0,"the second part does not exist"); - - //id hash check - require(bytes(right.toString()).length == 64,"id hash length is not correct"); - - } - - /** - * @dev ERC-165 support - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual override returns (bool) { - return - interfaceId == type(IERC7231).interfaceId || - super.supportsInterface(interfaceId); - } - -} - diff --git a/assets/eip-7231/contracts/interfaces/IERC7231.sol b/assets/eip-7231/contracts/interfaces/IERC7231.sol deleted file mode 100644 index 63dee26bb9a583..00000000000000 --- a/assets/eip-7231/contracts/interfaces/IERC7231.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.15; - -interface IERC7231 { - - /** - * @notice emit the use binding informain - * @param id nft id - * @param identitiesRoot new identity root - */ - event SetIdentitiesRoot( - uint256 id, - bytes32 identitiesRoot - ); - - /** - * @notice - * @dev set the user ID binding information of NFT with identitiesRoot - * @param id nft id - * @param identitiesRoot multi UserID Root data hash - * MUST allow external calls - */ - function setIdentitiesRoot( - uint256 id, - bytes32 identitiesRoot - ) external; - - /** - * @notice - * @dev get the multi-userID root by NFTID - * @param id nft id - * MUST return the bytes32 multiUserIDsRoot - * MUST NOT modify the state - * MUST allow external calls - */ - function getIdentitiesRoot( - uint256 id - ) external returns(bytes32); - - /** - * @notice - * @dev verify the userIDs binding - * @param id nft id - * @param userIDs userIDs for check - * @param identitiesRoot msg hash to veriry - * @param signature ECDSA signature - * MUST If the verification is passed, return true, otherwise return false - * MUST NOT modify the state - * MUST allow external calls - */ - function verifyIdentitiesBinding( - uint256 id,address nftOwnerAddress,string[] memory userIDs,bytes32 identitiesRoot, bytes calldata signature - ) external returns (bool); - -} \ No newline at end of file diff --git a/assets/eip-7231/contracts/mocks/ERC7231Mock.sol b/assets/eip-7231/contracts/mocks/ERC7231Mock.sol deleted file mode 100644 index b17f155bb2b0a6..00000000000000 --- a/assets/eip-7231/contracts/mocks/ERC7231Mock.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 -pragma solidity ^0.8.17; - -import "../ERC7231.sol"; - -contract ERC7231Mock is ERC7231 { - - constructor( - string memory name, - string memory symbol - ) ERC7231(name, symbol) {} - - function mint(address to, uint256 tokenId) external { - _mint(to, tokenId); - } - - function transfer(address to, uint256 tokenId) external { - _transfer(msg.sender, to, tokenId); - } - - function burn(uint256 tokenId) external { - _burn(tokenId); - } -} diff --git a/assets/eip-7231/img/Identity-aggregated-NFT-flow.png b/assets/eip-7231/img/Identity-aggregated-NFT-flow.png deleted file mode 100644 index 28b05bf1a50b82..00000000000000 Binary files a/assets/eip-7231/img/Identity-aggregated-NFT-flow.png and /dev/null differ diff --git a/assets/eip-7231/test/erc7231.ts b/assets/eip-7231/test/erc7231.ts deleted file mode 100644 index 83bcbed7245df9..00000000000000 --- a/assets/eip-7231/test/erc7231.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { ethers } from "hardhat"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ERC7231Mock } from "../typechain-types"; - -import { expect } from "chai"; -import web3 from "web3"; - - -describe("ERC7231", async () => { - - let owner : SignerWithAddress; - let others: SignerWithAddress[]; - - let ERC7231Mock: ERC7231Mock; - - const name = "carvTest"; - const symbol = "CVTS"; - const tokenId = 1; - - const MultiUserIDs = [ - { - "userID":"openID2:steam:a000000000000000000000000000000000000000000000000000000000000001", - "verifierUri1":"https://carv.io/verify/steam/a000000000000000000000000000000000000000000000000000000000000001", - "memo":"memo1" - }, - { - "userID":"did:polgyonId:b000000000000000000000000000000000000000000000000000000000000002", - "verifierUri1":"https://carv.io/verify/steam/b000000000000000000000000000000000000000000000000000000000000002", - "memo":"memo1" - } - ] - - beforeEach(async () => { - - [owner, ...others] = await ethers.getSigners(); - - const ERC7231Factory = await ethers.getContractFactory("ERC7231Mock"); - ERC7231Mock = await ERC7231Factory.deploy(name, symbol,); - await ERC7231Mock.deployed(); - - // await ERC7231Mock. - await ERC7231Mock.connect(owner).mint(owner.address,tokenId); - - }); - - describe("Init of Erc721 ", async function () { - - it("Name", async function () { - expect(await ERC7231Mock.name()).to.equal(name); - }); - - it("Symbol", async function () { - expect(await ERC7231Mock.symbol()).to.equal(symbol); - }); - - }); - - describe("set MultiUserIDs Root", async function () { - - it("Normal case", async function () { - - let multiUserIDsHash = "0xa5b9d60f32436310afebcfda832817a68921beb782fabf7915cc0460b443116a" - await expect( - ERC7231Mock.connect(owner).setIdentitiesRoot( - tokenId, - multiUserIDsHash - ) - ).to.emit(ERC7231Mock,"SetIdentitiesRoot").withArgs( - tokenId, - multiUserIDsHash - ); - - let multiUserIDsRoot = await ERC7231Mock.getIdentitiesRoot( - tokenId - ); - - expect(multiUserIDsHash).to.eql(multiUserIDsRoot); - - }); - - }); - - - describe("verify UserIDs Binding", async function () { - - it("Normal case", async function () { - - const dataHash = ethers.utils.keccak256( - ethers.utils.toUtf8Bytes(JSON.stringify(MultiUserIDs)) - ); - const dataHashBin = ethers.utils.arrayify(dataHash); - const ethHash = ethers.utils.hashMessage(dataHashBin); - - // const wallet = new ethers.Wallet(process.env.PK); - const signature = await owner.signMessage(dataHashBin); - - await ERC7231Mock.connect(owner).setIdentitiesRoot( - tokenId,ethHash - ) - - let userIDS = new Array(); - MultiUserIDs.forEach( - (MultiUserIDObj) => { - userIDS.push(MultiUserIDObj.userID) - } - ) - - let result = await ERC7231Mock.verifyIdentitiesBinding( - tokenId, - owner.address, - userIDS, - ethHash, - signature - ) - expect(result).to.eql(true); - - }); - - - }); - - - - - - - - - -}); diff --git a/assets/eip-725/ERC725.sol b/assets/eip-725/ERC725.sol deleted file mode 100644 index 1d760cfe632734..00000000000000 --- a/assets/eip-725/ERC725.sol +++ /dev/null @@ -1,1283 +0,0 @@ -pragma solidity ^0.8.8; - -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding [ERC165] standard. - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} - -contract ERC165 is IERC165 { - /** - * @inheritdoc IERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override - returns (bool) - { - return interfaceId == type(IERC165).interfaceId; - } -} - -contract ERC173 { - address private _owner; - - event OwnershipTransferred( - address indexed previousOwner, - address indexed newOwner - ); - - /** - * @dev Initializes the contract setting the deployer as the initial owner. - */ - constructor() { - _transferOwnership(msg.sender); - } - - /** - * @dev Throws if called by any account other than the owner. - */ - modifier onlyOwner() { - _checkOwner(); - _; - } - - /** - * @dev Returns the address of the current owner. - */ - function owner() public view virtual returns (address) { - return _owner; - } - - /** - * @dev Throws if the sender is not the owner. - */ - function _checkOwner() internal view virtual { - require(owner() == msg.sender, "Ownable: caller is not the owner"); - } - - /** - * @dev Leaves the contract without owner. It will not be possible to call - * `onlyOwner` functions anymore. Can only be called by the current owner. - * - * NOTE: Renouncing ownership will leave the contract without an owner, - * thereby removing any functionality that is only available to the owner. - */ - function renounceOwnership() public virtual onlyOwner { - _transferOwnership(address(0)); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Can only be called by the current owner. - */ - function transferOwnership(address newOwner) public virtual onlyOwner { - require( - newOwner != address(0), - "Ownable: new owner is the zero address" - ); - _transferOwnership(newOwner); - } - - /** - * @dev Transfers ownership of the contract to a new account (`newOwner`). - * Internal function without access restriction. - */ - function _transferOwnership(address newOwner) internal virtual { - address oldOwner = _owner; - _owner = newOwner; - emit OwnershipTransferred(oldOwner, newOwner); - } -} - -interface IERC725X is IERC165 { - /** - * @notice Emitted when deploying a contract - * @param operationType The opcode used to deploy the contract (CREATE or CREATE2) - * @param contractAddress The created contract address - * @param value The amount of native tokens (in Wei) sent to fund the created contract address - * @param salt The salt used in case of CREATE2. Will be bytes32(0) in case of CREATE operation - */ - event ContractCreated( - uint256 indexed operationType, - address indexed contractAddress, - uint256 indexed value, - bytes32 salt - ); - - /** - * @notice Emitted when calling an address (EOA or contract) - * @param operationType The low-level call opcode used to call the `to` address (CALL, STATICALL or DELEGATECALL) - * @param target The address to call. `target` will be unused if a contract is created (operation types 1 and 2). - * @param value The amount of native tokens transferred with the call (in Wei) - * @param selector The first 4 bytes (= function selector) of the data sent with the call - */ - event Executed( - uint256 indexed operationType, - address indexed target, - uint256 indexed value, - bytes4 selector - ); - - /** - * @param operationType The operation type used: CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4 - * @param target The address of the EOA or smart contract. (unused if a contract is created via operation type 1 or 2) - * @param value The amount of native tokens to transfer (in Wei) - * @param data The call data, or the creation bytecode of the contract to deploy - * - * @dev Generic executor function to: - * - * - send native tokens to any address. - * - interact with any contract by passing an abi-encoded function call in the `data` parameter. - * - deploy a contract by providing its creation bytecode in the `data` parameter. - * - * Requirements: - * - * - SHOULD only be callable by the owner of the contract set via ERC173. - * - if a `value` is provided, the contract MUST have at least this amount in its balance to execute successfully. - * - if the operation type is STATICCALL or DELEGATECALL, `value` SHOULD be 0. - * - `target` SHOULD be address(0) when deploying a contract. - * - * Emits an {Executed} event, when a call is made with `operationType` 0 (CALL), 3 (STATICCALL) or 4 (DELEGATECALL) - * Emits a {ContractCreated} event, when deploying a contract with `operationType` 1 (CREATE) or 2 (CREATE2) - */ - function execute( - uint256 operationType, - address target, - uint256 value, - bytes memory data - ) external payable returns (bytes memory); - - /** - * @param operationsType The list of operations type used: CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4 - * @param targets The list of addresses to call. `targets` will be unused if a contract is created (operation types 1 and 2). - * @param values The list of native token amounts to transfer (in Wei) - * @param datas The list of call data, or the creation bytecode of the contract to deploy - * - * @dev Generic batch executor function to: - * - * - send native tokens to any address. - * - interact with any contract by passing an abi-encoded function call in the `datas` parameter. - * - deploy a contract by providing its creation bytecode in the `datas` parameter. - * - * Requirements: - * - * - The length of the parameters provided MUST be equal - * - SHOULD only be callable by the owner of the contract set via ERC173. - * - if a `values` is provided, the contract MUST have at least this amount in its balance to execute successfully. - * - if the operation type is STATICCALL or DELEGATECALL, `values` SHOULD be 0. - * - `targets` SHOULD be address(0) when deploying a contract. - * - * Emits an {Executed} event, when a call is made with `operationType` 0 (CALL), 3 (STATICCALL) or 4 (DELEGATECALL) - * Emits a {ContractCreated} event, when deploying a contract with `operationType` 1 (CREATE) or 2 (CREATE2) - */ - function executeBatch( - uint256[] memory operationsType, - address[] memory targets, - uint256[] memory values, - bytes[] memory datas - ) external payable returns (bytes[] memory); -} - -// ERC165 INTERFACE IDs -bytes4 constant _INTERFACEID_ERC725X = 0x7545acac; - -// ERC725X OPERATION TYPES -uint256 constant OPERATION_0_CALL = 0; -uint256 constant OPERATION_1_CREATE = 1; -uint256 constant OPERATION_2_CREATE2 = 2; -uint256 constant OPERATION_3_STATICCALL = 3; -uint256 constant OPERATION_4_DELEGATECALL = 4; - -/** - * @dev reverts when trying to send more native tokens `value` than available in current `balance`. - * @param balance the balance of the ERC725X contract. - * @param value the amount of native tokens sent via `ERC725X.execute(...)`. - */ -error ERC725X_InsufficientBalance(uint256 balance, uint256 value); - -/** - * @dev reverts when the `operationTypeProvided` is none of the default operation types available. - * (CALL = 0; CREATE = 1; CREATE2 = 2; STATICCALL = 3; DELEGATECALL = 4) - */ -error ERC725X_UnknownOperationType(uint256 operationTypeProvided); - -/** - * @dev the `value` parameter (= sending native tokens) is not allowed when making a staticcall - * via `ERC725X.execute(...)` because sending native tokens is a state changing operation. - */ -error ERC725X_MsgValueDisallowedInStaticCall(); - -/** - * @dev the `value` parameter (= sending native tokens) is not allowed when making a delegatecall - * via `ERC725X.execute(...)` because msg.value is persisting. - */ -error ERC725X_MsgValueDisallowedInDelegateCall(); - -/** - * @dev reverts when passing a `to` address while deploying a contract va `ERC725X.execute(...)` - * whether using operation type 1 (CREATE) or 2 (CREATE2). - */ -error ERC725X_CreateOperationsRequireEmptyRecipientAddress(); - -/** - * @dev reverts when contract deployment via `ERC725X.execute(...)` failed. - * whether using operation type 1 (CREATE) or 2 (CREATE2). - */ -error ERC725X_ContractDeploymentFailed(); - -/** - * @dev reverts when no contract bytecode was provided as parameter when trying to deploy a contract - * via `ERC725X.execute(...)`, whether using operation type 1 (CREATE) or 2 (CREATE2). - */ -error ERC725X_NoContractBytecodeProvided(); - -/** - * @dev reverts when there is not the same number of operation, to addresses, value, and data. - */ -error ERC725X_ExecuteParametersLengthMismatch(); - -contract ERC725X is ERC173, ERC165, IERC725X { - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(IERC165, ERC165) - returns (bool) - { - return - interfaceId == _INTERFACEID_ERC725X || - super.supportsInterface(interfaceId); - } - - /** - * @inheritdoc IERC725X - */ - function execute( - uint256 operationType, - address target, - uint256 value, - bytes memory data - ) public payable virtual onlyOwner returns (bytes memory) { - if (address(this).balance < value) { - revert ERC725X_InsufficientBalance(address(this).balance, value); - } - return _execute(operationType, target, value, data); - } - - /** - * @inheritdoc IERC725X - */ - function executeBatch( - uint256[] memory operationsType, - address[] memory targets, - uint256[] memory values, - bytes[] memory datas - ) public payable virtual onlyOwner returns (bytes[] memory result) { - if ( - operationsType.length != targets.length || - (targets.length != values.length || values.length != datas.length) - ) revert ERC725X_ExecuteParametersLengthMismatch(); - - result = new bytes[](operationsType.length); - for (uint256 i = 0; i < operationsType.length; i++) { - if (address(this).balance < values[i]) - revert ERC725X_InsufficientBalance( - address(this).balance, - values[i] - ); - - result[i] = _execute( - operationsType[i], - targets[i], - values[i], - datas[i] - ); - } - } - - function _execute( - uint256 operationType, - address target, - uint256 value, - bytes memory data - ) internal virtual returns (bytes memory) { - // CALL - if (operationType == OPERATION_0_CALL) { - return _executeCall(target, value, data); - } - - // Deploy with CREATE - if (operationType == uint256(OPERATION_1_CREATE)) { - if (target != address(0)) - revert ERC725X_CreateOperationsRequireEmptyRecipientAddress(); - return _deployCreate(value, data); - } - - // Deploy with CREATE2 - if (operationType == uint256(OPERATION_2_CREATE2)) { - if (target != address(0)) - revert ERC725X_CreateOperationsRequireEmptyRecipientAddress(); - return _deployCreate2(value, data); - } - - // STATICCALL - if (operationType == uint256(OPERATION_3_STATICCALL)) { - if (value != 0) revert ERC725X_MsgValueDisallowedInStaticCall(); - return _executeStaticCall(target, data); - } - - // DELEGATECALL - // - // WARNING! delegatecall is a dangerous operation type! use with EXTRA CAUTION - // - // delegate allows to call another deployed contract and use its functions - // to update the state of the current calling contract. - // - // this can lead to unexpected behaviour on the contract storage, such as: - // - updating any state variables (even if these are protected) - // - update the contract owner - // - run selfdestruct in the context of this contract - // - if (operationType == uint256(OPERATION_4_DELEGATECALL)) { - if (value != 0) revert ERC725X_MsgValueDisallowedInDelegateCall(); - return _executeDelegateCall(target, data); - } - - revert ERC725X_UnknownOperationType(operationType); - } - - /** - * @dev perform low-level call (operation type = 0) - * @param target The address on which call is executed - * @param value The value to be sent with the call - * @param data The data to be sent with the call - * @return result The data from the call - */ - function _executeCall( - address target, - uint256 value, - bytes memory data - ) internal virtual returns (bytes memory result) { - emit Executed(OPERATION_0_CALL, target, value, bytes4(data)); - - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = target.call{value: value}( - data - ); - result = Address.verifyCallResult( - success, - returnData, - "ERC725X: Unknown Error" - ); - } - - /** - * @dev perform low-level staticcall (operation type = 3) - * @param target The address on which staticcall is executed - * @param data The data to be sent with the staticcall - * @return result The data returned from the staticcall - */ - function _executeStaticCall(address target, bytes memory data) - internal - virtual - returns (bytes memory result) - { - emit Executed(OPERATION_3_STATICCALL, target, 0, bytes4(data)); - - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = target.staticcall(data); - result = Address.verifyCallResult( - success, - returnData, - "ERC725X: Unknown Error" - ); - } - - /** - * @dev perform low-level delegatecall (operation type = 4) - * @param target The address on which delegatecall is executed - * @param data The data to be sent with the delegatecall - * @return result The data returned from the delegatecall - */ - function _executeDelegateCall(address target, bytes memory data) - internal - virtual - returns (bytes memory result) - { - emit Executed(OPERATION_4_DELEGATECALL, target, 0, bytes4(data)); - - // solhint-disable avoid-low-level-calls - (bool success, bytes memory returnData) = target.delegatecall(data); - result = Address.verifyCallResult( - success, - returnData, - "ERC725X: Unknown Error" - ); - } - - /** - * @dev deploy a contract using the CREATE opcode (operation type = 1) - * @param value The value to be sent to the contract created - * @param creationCode The contract creation bytecode to deploy appended with the constructor argument(s) - * @return newContract The address of the contract created as bytes - */ - function _deployCreate(uint256 value, bytes memory creationCode) - internal - virtual - returns (bytes memory newContract) - { - if (creationCode.length == 0) { - revert ERC725X_NoContractBytecodeProvided(); - } - - address contractAddress; - // solhint-disable no-inline-assembly - assembly { - contractAddress := create( - value, - add(creationCode, 0x20), - mload(creationCode) - ) - } - - if (contractAddress == address(0)) { - revert ERC725X_ContractDeploymentFailed(); - } - - newContract = abi.encodePacked(contractAddress); - emit ContractCreated( - OPERATION_1_CREATE, - contractAddress, - value, - bytes32(0) - ); - } - - /** - * @dev deploy a contract using the CREATE2 opcode (operation type = 2) - * @param value The value to be sent to the contract created - * @param creationCode The contract creation bytecode to deploy appended with the constructor argument(s) and a bytes32 salt - * @return newContract The address of the contract created as bytes - */ - function _deployCreate2(uint256 value, bytes memory creationCode) - internal - virtual - returns (bytes memory newContract) - { - bytes32 salt = BytesLib.toBytes32( - creationCode, - creationCode.length - 32 - ); - bytes memory bytecode = BytesLib.slice( - creationCode, - 0, - creationCode.length - 32 - ); - - address contractAddress; - require( - address(this).balance >= value, - "Create2: insufficient balance" - ); - require(creationCode.length != 0, "Create2: bytecode length is zero"); - /// @solidity memory-safe-assembly - assembly { - contractAddress := create2( - value, - add(bytecode, 0x20), - mload(bytecode), - salt - ) - } - require(contractAddress != address(0), "Create2: Failed on deploy"); - - newContract = abi.encodePacked(contractAddress); - emit ContractCreated(OPERATION_2_CREATE2, contractAddress, value, salt); - } -} - -/** - * @title The interface for ERC725Y General data key/value store - * @dev ERC725Y provides the ability to set arbitrary data key/value pairs that can be changed over time - * It is intended to standardise certain data key/value pairs to allow automated read and writes - * from/to the contract storage - */ -interface IERC725Y is IERC165 { - /** - * @notice Emitted when data at a key is changed - * @param dataKey The data key which data value is set - * @param dataValue The data value to set - */ - event DataChanged(bytes32 indexed dataKey, bytes dataValue); - - /** - * @notice Gets singular data at a given `dataKey` - * @param dataKey The key which value to retrieve - * @return dataValue The data stored at the key - */ - function getData(bytes32 dataKey) - external - view - returns (bytes memory dataValue); - - /** - * @notice Gets array of data for multiple given keys - * @param dataKeys The array of keys which values to retrieve - * @return dataValues The array of data stored at multiple keys - */ - function getDataBatch(bytes32[] memory dataKeys) - external - view - returns (bytes[] memory dataValues); - - /** - * @notice Sets singular data for a given `dataKey` - * @param dataKey The key to retrieve stored value - * @param dataValue The value to set - * SHOULD only be callable by the owner of the contract set via ERC173 - * - * Emits a {DataChanged} event. - */ - function setData(bytes32 dataKey, bytes memory dataValue) external; - - /** - * @param dataKeys The array of data keys for values to set - * @param dataValues The array of values to set - * @dev Sets array of data for multiple given `dataKeys` - * SHOULD only be callable by the owner of the contract set via ERC173 - * - * Emits a {DataChanged} event. - */ - function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) - external; -} - -// ERC165 INTERFACE IDs -bytes4 constant _INTERFACEID_ERC725Y = 0x629aa694; - -/** - * @dev reverts when there is not the same number of elements in the lists of data keys and data values - * when calling setData(bytes32[],bytes[]). - * @param dataKeysLength the number of data keys in the bytes32[] dataKeys - * @param dataValuesLength the number of data value in the bytes[] dataValue - */ -error ERC725Y_DataKeysValuesLengthMismatch( - uint256 dataKeysLength, - uint256 dataValuesLength -); - -contract ERC725Y is ERC173, ERC165, IERC725Y { - // overrides - - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(IERC165, ERC165) - returns (bool) - { - return - interfaceId == _INTERFACEID_ERC725Y || - super.supportsInterface(interfaceId); - } - - /** - * @dev Map the dataKeys to their dataValues - */ - mapping(bytes32 => bytes) internal _store; - - /** - * @inheritdoc IERC725Y - */ - function getData(bytes32 dataKey) - public - view - virtual - returns (bytes memory dataValue) - { - dataValue = _getData(dataKey); - } - - /** - * @inheritdoc IERC725Y - */ - function getDataBatch(bytes32[] memory dataKeys) - public - view - virtual - returns (bytes[] memory dataValues) - { - dataValues = new bytes[](dataKeys.length); - - for (uint256 i = 0; i < dataKeys.length; i++) { - dataValues[i] = _getData(dataKeys[i]); - } - - return dataValues; - } - - /** - * @inheritdoc IERC725Y - */ - function setData(bytes32 dataKey, bytes memory dataValue) - public - virtual - onlyOwner - { - _setData(dataKey, dataValue); - } - - /** - * @inheritdoc IERC725Y - */ - function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) - public - virtual - onlyOwner - { - if (dataKeys.length != dataValues.length) { - revert ERC725Y_DataKeysValuesLengthMismatch( - dataKeys.length, - dataValues.length - ); - } - - for (uint256 i = 0; i < dataKeys.length; i++) { - _setData(dataKeys[i], dataValues[i]); - } - } - - function _getData(bytes32 dataKey) - internal - view - virtual - returns (bytes memory dataValue) - { - return _store[dataKey]; - } - - function _setData(bytes32 dataKey, bytes memory dataValue) - internal - virtual - { - _store[dataKey] = dataValue; - emit DataChanged(dataKey, dataValue); - } -} - -contract ERC725 is ERC725X, ERC725Y { - /** - * @inheritdoc ERC165 - */ - function supportsInterface(bytes4 interfaceId) - public - view - virtual - override(ERC725X, ERC725Y) - returns (bool) - { - return - interfaceId == _INTERFACEID_ERC725X || - interfaceId == _INTERFACEID_ERC725Y || - super.supportsInterface(interfaceId); - } -} - -// external needed libraries - -library BytesLib { - function concat(bytes memory _preBytes, bytes memory _postBytes) - internal - pure - returns (bytes memory) - { - bytes memory tempBytes; - - assembly { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. - tempBytes := mload(0x40) - - // Store the length of the first bytes array at the beginning of - // the memory for tempBytes. - let length := mload(_preBytes) - mstore(tempBytes, length) - - // Maintain a memory counter for the current write location in the - // temp bytes array by adding the 32 bytes for the array length to - // the starting location. - let mc := add(tempBytes, 0x20) - // Stop copying when the memory counter reaches the length of the - // first bytes array. - let end := add(mc, length) - - for { - // Initialize a copy counter to the start of the _preBytes data, - // 32 bytes into its memory. - let cc := add(_preBytes, 0x20) - } lt(mc, end) { - // Increase both counters by 32 bytes each iteration. - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - // Write the _preBytes data into the tempBytes memory 32 bytes - // at a time. - mstore(mc, mload(cc)) - } - - // Add the length of _postBytes to the current length of tempBytes - // and store it as the new length in the first 32 bytes of the - // tempBytes memory. - length := mload(_postBytes) - mstore(tempBytes, add(length, mload(tempBytes))) - - // Move the memory counter back from a multiple of 0x20 to the - // actual end of the _preBytes data. - mc := end - // Stop copying when the memory counter reaches the new combined - // length of the arrays. - end := add(mc, length) - - for { - let cc := add(_postBytes, 0x20) - } lt(mc, end) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } - - // Update the free-memory pointer by padding our last write location - // to 32 bytes: add 31 bytes to the end of tempBytes to move to the - // next 32 byte block, then round down to the nearest multiple of - // 32. If the sum of the length of the two arrays is zero then add - // one before rounding down to leave a blank 32 bytes (the length block with 0). - mstore( - 0x40, - and( - add(add(end, iszero(add(length, mload(_preBytes)))), 31), - not(31) // Round down to the nearest 32 bytes. - ) - ) - } - - return tempBytes; - } - - function concatStorage(bytes storage _preBytes, bytes memory _postBytes) - internal - { - assembly { - // Read the first 32 bytes of _preBytes storage, which is the length - // of the array. (We don't need to use the offset into the slot - // because arrays use the entire slot.) - let fslot := sload(_preBytes.slot) - // Arrays of 31 bytes or less have an even value in their slot, - // while longer arrays have an odd value. The actual length is - // the slot divided by two for odd values, and the lowest order - // byte divided by two for even values. - // If the slot is even, bitwise and the slot with 255 and divide by - // two to get the length. If the slot is odd, bitwise and the slot - // with -1 and divide by two. - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) - let mlength := mload(_postBytes) - let newlength := add(slength, mlength) - // slength can contain both the length and contents of the array - // if length < 32 bytes so let's prepare for that - // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage - switch add(lt(slength, 32), lt(newlength, 32)) - case 2 { - // Since the new array still fits in the slot, we just need to - // update the contents of the slot. - // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length - sstore( - _preBytes.slot, - // all the modifications to the slot are inside this - // next block - add( - // we can just add to the slot contents because the - // bytes we want to change are the LSBs - fslot, - add( - mul( - div( - // load the bytes from memory - mload(add(_postBytes, 0x20)), - // zero all bytes to the right - exp(0x100, sub(32, mlength)) - ), - // and now shift left the number of bytes to - // leave space for the length in the slot - exp(0x100, sub(32, newlength)) - ), - // increase length by the double of the memory - // bytes length - mul(mlength, 2) - ) - ) - ) - } - case 1 { - // The stored value fits in the slot, but the combined value - // will exceed it. - // get the keccak hash to get the contents of the array - mstore(0x0, _preBytes.slot) - let sc := add(keccak256(0x0, 0x20), div(slength, 32)) - - // save new length - sstore(_preBytes.slot, add(mul(newlength, 2), 1)) - - // The contents of the _postBytes array start 32 bytes into - // the structure. Our first read should obtain the `submod` - // bytes that can fit into the unused space in the last word - // of the stored array. To get this, we read 32 bytes starting - // from `submod`, so the data we read overlaps with the array - // contents by `submod` bytes. Masking the lowest-order - // `submod` bytes allows us to add that value directly to the - // stored value. - - let submod := sub(32, slength) - let mc := add(_postBytes, submod) - let end := add(_postBytes, mlength) - let mask := sub(exp(0x100, submod), 1) - - sstore( - sc, - add( - and( - fslot, - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00 - ), - and(mload(mc), mask) - ) - ) - - for { - mc := add(mc, 0x20) - sc := add(sc, 1) - } lt(mc, end) { - sc := add(sc, 1) - mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } - - mask := exp(0x100, sub(mc, end)) - - sstore(sc, mul(div(mload(mc), mask), mask)) - } - default { - // get the keccak hash to get the contents of the array - mstore(0x0, _preBytes.slot) - // Start copying to the last used word of the stored array. - let sc := add(keccak256(0x0, 0x20), div(slength, 32)) - - // save new length - sstore(_preBytes.slot, add(mul(newlength, 2), 1)) - - // Copy over the first `submod` bytes of the new data as in - // case 1 above. - let slengthmod := mod(slength, 32) - let mlengthmod := mod(mlength, 32) - let submod := sub(32, slengthmod) - let mc := add(_postBytes, submod) - let end := add(_postBytes, mlength) - let mask := sub(exp(0x100, submod), 1) - - sstore(sc, add(sload(sc), and(mload(mc), mask))) - - for { - sc := add(sc, 1) - mc := add(mc, 0x20) - } lt(mc, end) { - sc := add(sc, 1) - mc := add(mc, 0x20) - } { - sstore(sc, mload(mc)) - } - - mask := exp(0x100, sub(mc, end)) - - sstore(sc, mul(div(mload(mc), mask), mask)) - } - } - } - - function slice( - bytes memory _bytes, - uint256 _start, - uint256 _length - ) internal pure returns (bytes memory) { - require(_length + 31 >= _length, "slice_overflow"); - require(_bytes.length >= _start + _length, "slice_outOfBounds"); - - bytes memory tempBytes; - - assembly { - switch iszero(_length) - case 0 { - // Get a location of some free memory and store it in tempBytes as - // Solidity does for memory variables. - tempBytes := mload(0x40) - - // The first word of the slice result is potentially a partial - // word read from the original array. To read it, we calculate - // the length of that partial word and start copying that many - // bytes into the array. The first word we copy will start with - // data we don't care about, but the last `lengthmod` bytes will - // land at the beginning of the contents of the new array. When - // we're done copying, we overwrite the full first word with - // the actual length of the slice. - let lengthmod := and(_length, 31) - - // The multiplication in the next line is necessary - // because when slicing multiples of 32 bytes (lengthmod == 0) - // the following copy loop was copying the origin's length - // and then ending prematurely not copying everything it should. - let mc := add( - add(tempBytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ) - let end := add(mc, _length) - - for { - // The multiplication in the next line has the same exact purpose - // as the one above. - let cc := add( - add( - add(_bytes, lengthmod), - mul(0x20, iszero(lengthmod)) - ), - _start - ) - } lt(mc, end) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - mstore(mc, mload(cc)) - } - - mstore(tempBytes, _length) - - //update free-memory pointer - //allocating the array padded to 32 bytes like the compiler does now - mstore(0x40, and(add(mc, 31), not(31))) - } - //if we want a zero-length slice let's just return a zero-length array - default { - tempBytes := mload(0x40) - //zero out the 32 bytes slice we are about to return - //we need to do it because Solidity does not garbage collect - mstore(tempBytes, 0) - - mstore(0x40, add(tempBytes, 0x20)) - } - } - - return tempBytes; - } - - function toAddress(bytes memory _bytes, uint256 _start) - internal - pure - returns (address) - { - require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); - address tempAddress; - - assembly { - tempAddress := div( - mload(add(add(_bytes, 0x20), _start)), - 0x1000000000000000000000000 - ) - } - - return tempAddress; - } - - function toUint8(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint8) - { - require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); - uint8 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x1), _start)) - } - - return tempUint; - } - - function toUint16(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint16) - { - require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); - uint16 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x2), _start)) - } - - return tempUint; - } - - function toUint32(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint32) - { - require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); - uint32 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x4), _start)) - } - - return tempUint; - } - - function toUint64(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint64) - { - require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); - uint64 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x8), _start)) - } - - return tempUint; - } - - function toUint96(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint96) - { - require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); - uint96 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0xc), _start)) - } - - return tempUint; - } - - function toUint128(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint128) - { - require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); - uint128 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x10), _start)) - } - - return tempUint; - } - - function toUint256(bytes memory _bytes, uint256 _start) - internal - pure - returns (uint256) - { - require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); - uint256 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x20), _start)) - } - - return tempUint; - } - - function toBytes32(bytes memory _bytes, uint256 _start) - internal - pure - returns (bytes32) - { - require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); - bytes32 tempBytes32; - - assembly { - tempBytes32 := mload(add(add(_bytes, 0x20), _start)) - } - - return tempBytes32; - } - - function equal(bytes memory _preBytes, bytes memory _postBytes) - internal - pure - returns (bool) - { - bool success = true; - - assembly { - let length := mload(_preBytes) - - // if lengths don't match the arrays are not equal - switch eq(length, mload(_postBytes)) - case 1 { - // cb is a circuit breaker in the for loop since there's - // no said feature for inline assembly loops - // cb = 1 - don't breaker - // cb = 0 - break - let cb := 1 - - let mc := add(_preBytes, 0x20) - let end := add(mc, length) - - for { - let cc := add(_postBytes, 0x20) - // the next line is the loop condition: - // while(uint256(mc < end) + cb == 2) - } eq(add(lt(mc, end), cb), 2) { - mc := add(mc, 0x20) - cc := add(cc, 0x20) - } { - // if any of these checks fails then arrays are not equal - if iszero(eq(mload(mc), mload(cc))) { - // unsuccess: - success := 0 - cb := 0 - } - } - } - default { - // unsuccess: - success := 0 - } - } - - return success; - } - - function equalStorage(bytes storage _preBytes, bytes memory _postBytes) - internal - view - returns (bool) - { - bool success = true; - - assembly { - // we know _preBytes_offset is 0 - let fslot := sload(_preBytes.slot) - // Decode the length of the stored array like in concatStorage(). - let slength := div( - and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), - 2 - ) - let mlength := mload(_postBytes) - - // if lengths don't match the arrays are not equal - switch eq(slength, mlength) - case 1 { - // slength can contain both the length and contents of the array - // if length < 32 bytes so let's prepare for that - // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage - if iszero(iszero(slength)) { - switch lt(slength, 32) - case 1 { - // blank the last byte which is the length - fslot := mul(div(fslot, 0x100), 0x100) - - if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { - // unsuccess: - success := 0 - } - } - default { - // cb is a circuit breaker in the for loop since there's - // no said feature for inline assembly loops - // cb = 1 - don't breaker - // cb = 0 - break - let cb := 1 - - // get the keccak hash to get the contents of the array - mstore(0x0, _preBytes.slot) - let sc := keccak256(0x0, 0x20) - - let mc := add(_postBytes, 0x20) - let end := add(mc, mlength) - - // the next line is the loop condition: - // while(uint256(mc < end) + cb == 2) - for { - - } eq(add(lt(mc, end), cb), 2) { - sc := add(sc, 1) - mc := add(mc, 0x20) - } { - if iszero(eq(sload(sc), mload(mc))) { - // unsuccess: - success := 0 - cb := 0 - } - } - } - } - } - default { - // unsuccess: - success := 0 - } - } - - return success; - } -} - -library Address { - /** - * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the - * revert reason using the provided one. - */ - function verifyCallResult( - bool success, - bytes memory returndata, - string memory errorMessage - ) internal pure returns (bytes memory) { - if (success) { - return returndata; - } else { - // Look for revert reason and bubble it up if present - if (returndata.length > 0) { - // The easiest way to bubble the revert reason is using memory via assembly - /// @solidity memory-safe-assembly - assembly { - let returndata_size := mload(returndata) - revert(add(32, returndata), returndata_size) - } - } else { - revert(errorMessage); - } - } - } -} diff --git a/assets/eip-7401/contracts/IERC7401.sol b/assets/eip-7401/contracts/IERC7401.sol deleted file mode 100644 index 6de317930d0ed4..00000000000000 --- a/assets/eip-7401/contracts/IERC7401.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - - -interface IERC7401 { - struct DirectOwner { - uint256 tokenId; - address ownerAddress; - } - - event NestTransfer( - address indexed from, - address indexed to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 indexed tokenId - ); - - event ChildProposed( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event ChildAccepted( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId - ); - - event AllChildrenRejected(uint256 indexed tokenId); - - event ChildTransferred( - uint256 indexed tokenId, - uint256 childIndex, - address indexed childAddress, - uint256 indexed childId, - bool fromPending, - bool toZero - ); - - struct Child { - uint256 tokenId; - address contractAddress; - } - - function ownerOf(uint256 tokenId) external view returns (address owner); - - function directOwnerOf( - uint256 tokenId - ) external view returns (address, uint256, bool); - - function burn( - uint256 tokenId, - uint256 maxRecursiveBurns - ) external returns (uint256); - - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) external; - - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) external; - - function rejectAllChildren(uint256 parentId, uint256 maxRejections) - external; - - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) external; - - function childrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function pendingChildrenOf( - uint256 parentId - ) external view returns (Child[] memory); - - function childOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function pendingChildOf( - uint256 parentId, - uint256 index - ) external view returns (Child memory); - - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) external; -} diff --git a/assets/eip-7401/contracts/NestableToken.sol b/assets/eip-7401/contracts/NestableToken.sol deleted file mode 100644 index 89c0e569f44af5..00000000000000 --- a/assets/eip-7401/contracts/NestableToken.sol +++ /dev/null @@ -1,1492 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//Generally all interactions should propagate downstream - -pragma solidity ^0.8.16; - -import "./IERC7401.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import "@openzeppelin/contracts/utils/Address.sol"; -import "@openzeppelin/contracts/utils/Context.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -error ChildAlreadyExists(); -error ChildIndexOutOfRange(); -error ERC721AddressZeroIsNotaValidOwner(); -error ERC721ApprovalToCurrentOwner(); -error ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); -error ERC721ApproveToCaller(); -error ERC721InvalidTokenId(); -error ERC721MintToTheZeroAddress(); -error ERC721NotApprovedOrOwner(); -error ERC721TokenAlreadyMinted(); -error ERC721TransferFromIncorrectOwner(); -error ERC721TransferToNonReceiverImplementer(); -error ERC721TransferToTheZeroAddress(); -error IdZeroForbidden(); -error IsNotContract(); -error MaxPendingChildrenReached(); -error MaxRecursiveBurnsReached(address childContract, uint256 childId); -error MintToNonNestableImplementer(); -error NestableTooDeep(); -error NestableTransferToDescendant(); -error NestableTransferToNonNestableImplementer(); -error NestableTransferToSelf(); -error NotApprovedOrDirectOwner(); -error PendingChildIndexOutOfRange(); -error UnexpectedChildId(); -error UnexpectedNumberOfChildren(); - -/** - * @title NestableToken - * @author RMRK team - * @notice Smart contract of the Nestable module. - * @dev This contract is hierarchy agnostic and can support an arbitrary number of nested levels up and down, as long as - * gas limits allow it. - */ -contract NestableToken is Context, IERC165, IERC721, IERC7401 { - using Address for address; - - uint256 private constant _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP = 100; - - // Mapping owner address to token count - mapping(address => uint256) private _balances; - - // Mapping from token ID to approver address to approved address - // The approver is necessary so approvals are invalidated for nested children on transfer - // WARNING: If a child NFT returns to a previous root owner, old permissions would be active again - mapping(uint256 => mapping(address => address)) private _tokenApprovals; - - // Mapping from owner to operator approvals - mapping(address => mapping(address => bool)) private _operatorApprovals; - - // ------------------- NESTABLE -------------- - - // Mapping from token ID to DirectOwner struct - mapping(uint256 => DirectOwner) private _directOwners; - - // Mapping of tokenId to array of active children structs - mapping(uint256 => Child[]) internal _activeChildren; - - // Mapping of tokenId to array of pending children structs - mapping(uint256 => Child[]) internal _pendingChildren; - - // Mapping of child token address to child token ID to whether they are pending or active on any token - // We might have a first extra mapping from token ID, but since the same child cannot be nested into multiple tokens - // we can strip it for size/gas savings. - mapping(address => mapping(uint256 => uint256)) internal _childIsInActive; - - // -------------------------- MODIFIERS ---------------------------- - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @dev If the caller is not the owner of the token or approved to manage it by its owner, the execution will be - * reverted. - * @param tokenId ID of the token to check - */ - function _onlyApprovedOrOwner(uint256 tokenId) private view { - if (!_isApprovedOrOwner(_msgSender(), tokenId)) - revert ERC721NotApprovedOrOwner(); - } - - /** - * @notice Used to verify that the caller is either the owner of the token or approved to manage it by its owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrOwner(uint256 tokenId) { - _onlyApprovedOrOwner(tokenId); - _; - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or it its direct owner. - * @dev This does not delegate to ownerOf, which returns the root owner, but rater uses an owner from DirectOwner - * struct. - * @dev The execution is reverted if the caller is not immediate owner or approved to manage the given token. - * @dev Used for parent-scoped transfers. - * @param tokenId ID of the token to check. - */ - function _onlyApprovedOrDirectOwner(uint256 tokenId) private view { - if (!_isApprovedOrDirectOwner(_msgSender(), tokenId)) - revert NotApprovedOrDirectOwner(); - } - - /** - * @notice Used to verify that the caller is approved to manage the given token or is its direct owner. - * @param tokenId ID of the token to check - */ - modifier onlyApprovedOrDirectOwner(uint256 tokenId) { - _onlyApprovedOrDirectOwner(tokenId); - _; - } - - // ------------------------------- ERC721 --------------------------------- - /** - * @inheritdoc IERC165 - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC165).interfaceId || - interfaceId == type(IERC721).interfaceId || - interfaceId == type(IERC7401).interfaceId; - } - - /** - * @inheritdoc IERC721 - */ - function balanceOf(address owner) public view virtual returns (uint256) { - if (owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner(); - return _balances[owner]; - } - - //////////////////////////////////////// - // TRANSFERS - //////////////////////////////////////// - - /** - * @inheritdoc IERC721 - */ - function transferFrom( - address from, - address to, - uint256 tokenId - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _transfer(from, to, tokenId, ""); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId - ) public virtual { - safeTransferFrom(from, to, tokenId, ""); - } - - /** - * @inheritdoc IERC721 - */ - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _safeTransfer(from, to, tokenId, data); - } - - /** - * @notice Used to transfer the token into another token. - * @dev The destination token MUST NOT be a child token of the token being transferred or one of its downstream - * child tokens. - * @param from Address of the direct owner of the token to be transferred - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token being transferred - * @param destinationId ID of the token to receive the token being transferred - */ - function nestTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) public virtual onlyApprovedOrDirectOwner(tokenId) { - _nestTransfer(from, to, tokenId, destinationId, data); - } - - /** - * @notice Used to safely transfer the token form `from` to `to`. - * @dev The function checks that contract recipients are aware of the ERC721 protocol to prevent tokens from being - * forever locked. - * @dev This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. implement alternative - * mechanisms to perform token transfer, such as signature-based. - * @dev Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `tokenId` token must exist and be owned by `from`. - * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - * @param data Additional data with no specified format, sent in call to `to` - */ - function _safeTransfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _transfer(from, to, tokenId, data); - if (!_checkOnERC721Received(from, to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to transfer the token from `from` to `to`. - * @dev As opposed to {transferFrom}, this imposes no restrictions on msg.sender. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` token must be owned by `from`. - * @dev Emits a {Transfer} event. - * @param from Address of the account currently owning the given token - * @param to Address to transfer the token to - * @param tokenId ID of the token to transfer - * @param data Additional data with no specified format, sent in call to `to` - */ - function _transfer( - address from, - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer(from, to, parentId, 0, tokenId, data); - - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, 0, to); - _balances[to] += 1; - - emit Transfer(from, to, tokenId); - emit NestTransfer(from, to, parentId, 0, tokenId); - - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer(from, to, parentId, 0, tokenId, data); - } - - /** - * @notice Used to transfer a token into another token. - * @dev Attempting to nest a token into `0x0` address will result in reverted transaction. - * @dev Attempting to nest a token into itself will result in reverted transaction. - * @param from Address of the account currently owning the given token - * @param to Address of the receiving token's collection smart contract - * @param tokenId ID of the token to transfer - * @param destinationId ID of the token receiving the given token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestTransfer( - address from, - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - if (immediateOwner != from) revert ERC721TransferFromIncorrectOwner(); - if (to == address(0)) revert ERC721TransferToTheZeroAddress(); - if (to == address(this) && tokenId == destinationId) - revert NestableTransferToSelf(); - - // Destination contract checks: - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC7401).interfaceId)) - revert NestableTransferToNonNestableImplementer(); - _checkForInheritanceLoop(tokenId, to, destinationId); - - _beforeTokenTransfer(from, to, tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - to, - parentId, - destinationId, - tokenId, - data - ); - _balances[from] -= 1; - _updateOwnerAndClearApprovals(tokenId, destinationId, to); - _balances[to] += 1; - - // Sending to NFT: - _sendToNFT(immediateOwner, to, parentId, destinationId, tokenId, data); - } - - /** - * @notice Used to send a token to another token. - * @dev If the token being sent is currently owned by an externally owned account, the `parentId` should equal `0`. - * @dev Emits {Transfer} event. - * @dev Emits {NestTransfer} event. - * @param from Address from which the token is being sent - * @param to Address of the collection smart contract of the token to receive the given token - * @param parentId ID of the current parent token of the token being sent - * @param destinationId ID of the tokento receive the token being sent - * @param tokenId ID of the token being sent - * @param data Additional data with no specified format, sent in the addChild call - */ - function _sendToNFT( - address from, - address to, - uint256 parentId, - uint256 destinationId, - uint256 tokenId, - bytes memory data - ) private { - IERC7401 destContract = IERC7401(to); - destContract.addChild(destinationId, tokenId, data); - - emit Transfer(from, to, tokenId); - emit NestTransfer(from, to, parentId, destinationId, tokenId); - - _afterTokenTransfer(from, to, tokenId); - _afterNestedTokenTransfer( - from, - to, - parentId, - destinationId, - tokenId, - data - ); - } - - /** - * @notice Used to check if nesting a given token into a specified token would create an inheritance loop. - * @dev If a loop would occur, the tokens would be unmanageable, so the execution is reverted if one is detected. - * @dev The check for inheritance loop is bounded to guard against too much gas being consumed. - * @param currentId ID of the token that would be nested - * @param targetContract Address of the collection smart contract of the token into which the given token would be - * nested - * @param targetId ID of the token into which the given token would be nested - */ - function _checkForInheritanceLoop( - uint256 currentId, - address targetContract, - uint256 targetId - ) private view { - for (uint256 i; i < _MAX_LEVELS_TO_CHECK_FOR_INHERITANCE_LOOP; ) { - ( - address nextOwner, - uint256 nextOwnerTokenId, - bool isNft - ) = IERC7401(targetContract).directOwnerOf(targetId); - // If there's a final address, we're good. There's no loop. - if (!isNft) { - return; - } - // Ff the current nft is an ancestor at some point, there is an inheritance loop - if (nextOwner == address(this) && nextOwnerTokenId == currentId) { - revert NestableTransferToDescendant(); - } - // We reuse the parameters to save some contract size - targetContract = nextOwner; - targetId = nextOwnerTokenId; - unchecked { - ++i; - } - } - revert NestableTooDeep(); - } - - //////////////////////////////////////// - // MINTING - //////////////////////////////////////// - - /** - * @notice Used to safely mint the token to the specified address while passing the additional data to contract - * recipients. - * @param to Address to which to mint the token - * @param tokenId ID of the token to mint - * @param data Additional data to send with the tokens - */ - function _safeMint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _mint(to, tokenId, data); - if (!_checkOnERC721Received(address(0), to, tokenId, data)) - revert ERC721TransferToNonReceiverImplementer(); - } - - /** - * @notice Used to mint a specified token to a given address. - * @dev WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible. - * @dev Requirements: - * - * - `tokenId` must not exist. - * - `to` cannot be the zero address. - * @dev Emits a {Transfer} event. - * @dev Emits a {NestTransfer} event. - * @param to Address to mint the token to - * @param tokenId ID of the token to mint - * @param data Additional data with no specified format, sent in call to `to` - */ - function _mint( - address to, - uint256 tokenId, - bytes memory data - ) internal virtual { - _innerMint(to, tokenId, 0, data); - - emit Transfer(address(0), to, tokenId); - emit NestTransfer(address(0), to, 0, 0, tokenId); - - _afterTokenTransfer(address(0), to, tokenId); - _afterNestedTokenTransfer(address(0), to, 0, 0, tokenId, data); - } - - /** - * @notice Used to mint a child token to a given parent token. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new child token - * @param data Additional data with no specified format, sent in the addChild call - */ - function _nestMint( - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) internal virtual { - // It seems redundant, but otherwise it would revert with no error - if (!to.isContract()) revert IsNotContract(); - if (!IERC165(to).supportsInterface(type(IERC7401).interfaceId)) - revert MintToNonNestableImplementer(); - - _innerMint(to, tokenId, destinationId, data); - _sendToNFT(address(0), to, 0, destinationId, tokenId, data); - } - - /** - * @notice Used to mint a child token into a given parent token. - * @dev Requirements: - * - * - `to` cannot be the zero address. - * - `tokenId` must not exist. - * - `tokenId` must not be `0`. - * @param to Address of the collection smart contract of the token into which to mint the child token - * @param tokenId ID of the token to mint - * @param destinationId ID of the token into which to mint the new token - * @param data Additional data with no specified format, sent in call to `to` - */ - function _innerMint( - address to, - uint256 tokenId, - uint256 destinationId, - bytes memory data - ) private { - if (to == address(0)) revert ERC721MintToTheZeroAddress(); - if (_exists(tokenId)) revert ERC721TokenAlreadyMinted(); - if (tokenId == uint256(0)) revert IdZeroForbidden(); - - _beforeTokenTransfer(address(0), to, tokenId); - _beforeNestedTokenTransfer( - address(0), - to, - 0, - destinationId, - tokenId, - data - ); - - _balances[to] += 1; - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId - }); - } - - //////////////////////////////////////// - // Ownership - //////////////////////////////////////// - - /** - * @notice Used to retrieve the root owner of the given token. - * @dev Root owner is always the externally owned account. - * @dev If the given token is owned by another token, it will recursively query the parent tokens until reaching the - * root owner. - * @param tokenId ID of the token for which the root owner is being retrieved - * @return address Address of the root owner of the given token - */ - function ownerOf( - uint256 tokenId - ) public view virtual override(IERC7401, IERC721) returns (address) { - (address owner, uint256 ownerTokenId, bool isNft) = directOwnerOf( - tokenId - ); - if (isNft) { - owner = IERC7401(owner).ownerOf(ownerTokenId); - } - return owner; - } - - /** - * @notice Used to retrieve the immediate owner of the given token. - * @dev In the event the NFT is owned by an externally owned account, `tokenId` will be `0`. - * @param tokenId ID of the token for which the immediate owner is being retrieved - * @return address Address of the immediate owner. If the token is owned by an externally owned account, its address - * will be returned. If the token is owned by another token, the parent token's collection smart contract address - * is returned - * @return uint256 Token ID of the immediate owner. If the immediate owner is an externally owned account, the value - * should be `0` - * @return bool A boolean value signifying whether the immediate owner is a token (`true`) or not (`false`) - */ - function directOwnerOf( - uint256 tokenId - ) public view virtual returns (address, uint256, bool) { - DirectOwner memory owner = _directOwners[tokenId]; - if (owner.ownerAddress == address(0)) revert ERC721InvalidTokenId(); - - return (owner.ownerAddress, owner.tokenId, owner.tokenId != 0); - } - - //////////////////////////////////////// - // BURNING - //////////////////////////////////////// - - /** - * @notice Used to burn a given token. - * @dev In case the token has any child tokens, the execution will be reverted. - * @param tokenId ID of the token to burn - */ - function burn(uint256 tokenId) public virtual { - burn(tokenId, 0); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return uint256 The number of recursive burns it took to burn all of the children - */ - function burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) public virtual onlyApprovedOrDirectOwner(tokenId) returns (uint256) { - return _burn(tokenId, maxChildrenBurns); - } - - /** - * @notice Used to burn a token. - * @dev When a token is burned, its children are recursively burned as well. - * @dev The approvals are cleared when the token is burned. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits a {Transfer} event. - * @dev Emits a {NestTransfer} event. - * @param tokenId ID of the token to burn - * @param maxChildrenBurns Maximum children to recursively burn - * @return The number of recursive burns it took to burn all of the children - */ - function _burn( - uint256 tokenId, - uint256 maxChildrenBurns - ) internal virtual returns (uint256) { - (address immediateOwner, uint256 parentId, ) = directOwnerOf(tokenId); - address rootOwner = ownerOf(tokenId); - - _beforeTokenTransfer(immediateOwner, address(0), tokenId); - _beforeNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId, - "" - ); - - _balances[immediateOwner] -= 1; - _approve(address(0), tokenId); - _cleanApprovals(tokenId); - - Child[] memory children = childrenOf(tokenId); - - delete _activeChildren[tokenId]; - delete _pendingChildren[tokenId]; - delete _tokenApprovals[tokenId][rootOwner]; - - uint256 pendingRecursiveBurns; - uint256 totalChildBurns; - - uint256 length = children.length; //gas savings - for (uint256 i; i < length; ) { - if (totalChildBurns >= maxChildrenBurns) - revert MaxRecursiveBurnsReached( - children[i].contractAddress, - children[i].tokenId - ); - delete _childIsInActive[children[i].contractAddress][ - children[i].tokenId - ]; - unchecked { - // At this point we know pendingRecursiveBurns must be at least 1 - pendingRecursiveBurns = maxChildrenBurns - totalChildBurns; - } - // We substract one to the next level to count for the token being burned, then add it again on returns - // This is to allow the behavior of 0 recursive burns meaning only the current token is deleted. - totalChildBurns += - IERC7401(children[i].contractAddress).burn( - children[i].tokenId, - pendingRecursiveBurns - 1 - ) + - 1; - unchecked { - ++i; - } - } - // Can't remove before burning child since child will call back to get root owner - delete _directOwners[tokenId]; - - emit Transfer(immediateOwner, address(0), tokenId); - emit NestTransfer(immediateOwner, address(0), parentId, 0, tokenId); - - _afterTokenTransfer(immediateOwner, address(0), tokenId); - _afterNestedTokenTransfer( - immediateOwner, - address(0), - parentId, - 0, - tokenId, - "" - ); - - return totalChildBurns; - } - - //////////////////////////////////////// - // APPROVALS - //////////////////////////////////////// - - /** - * @inheritdoc IERC721 - */ - function approve(address to, uint256 tokenId) public virtual { - address owner = ownerOf(tokenId); - if (to == owner) revert ERC721ApprovalToCurrentOwner(); - - if (_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) - revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); - - _approve(to, tokenId); - } - - /** - * @inheritdoc IERC721 - */ - function getApproved( - uint256 tokenId - ) public view virtual returns (address) { - _requireMinted(tokenId); - - return _tokenApprovals[tokenId][ownerOf(tokenId)]; - } - - /** - * @inheritdoc IERC721 - */ - function setApprovalForAll(address operator, bool approved) public virtual { - if (_msgSender() == operator) revert ERC721ApproveToCaller(); - _operatorApprovals[_msgSender()][operator] = approved; - emit ApprovalForAll(_msgSender(), operator, approved); - } - - /** - * @inheritdoc IERC721 - */ - function isApprovedForAll( - address owner, - address operator - ) public view virtual returns (bool) { - return _operatorApprovals[owner][operator]; - } - - /** - * @notice Used to grant an approval to manage a given token. - * @dev Emits an {Approval} event. - * @param to Address to which the approval is being granted - * @param tokenId ID of the token for which the approval is being granted - */ - function _approve(address to, uint256 tokenId) internal virtual { - address owner = ownerOf(tokenId); - _tokenApprovals[tokenId][owner] = to; - emit Approval(owner, to, tokenId); - } - - /** - * @notice Used to update the owner of the token and clear the approvals associated with the previous owner. - * @dev The `destinationId` should equal `0` if the new owner is an externally owned account. - * @param tokenId ID of the token being updated - * @param destinationId ID of the token to receive the given token - * @param to Address of account to receive the token - */ - function _updateOwnerAndClearApprovals( - uint256 tokenId, - uint256 destinationId, - address to - ) internal { - _directOwners[tokenId] = DirectOwner({ - ownerAddress: to, - tokenId: destinationId - }); - - // Clear approvals from the previous owner - _approve(address(0), tokenId); - _cleanApprovals(tokenId); - } - - /** - * @notice Used to remove approvals for the current owner of the given token. - * @param tokenId ID of the token to clear the approvals for - */ - function _cleanApprovals(uint256 tokenId) internal virtual {} - - //////////////////////////////////////// - // UTILS - //////////////////////////////////////// - - /** - * @notice Used to check whether the given account is allowed to manage the given token. - * @dev Requirements: - * - * - `tokenId` must exist. - * @param spender Address that is being checked for approval - * @param tokenId ID of the token being checked - * @return A boolean value indicating whether the `spender` is approved to manage the given token - */ - function _isApprovedOrOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - address owner = ownerOf(tokenId); - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to check whether the account is approved to manage the token or its direct owner. - * @param spender Address that is being checked for approval or direct ownership - * @param tokenId ID of the token being checked - * @return A boolean value indicating whether the `spender` is approved to manage the given token or its - * direct owner - */ - function _isApprovedOrDirectOwner( - address spender, - uint256 tokenId - ) internal view virtual returns (bool) { - (address owner, uint256 parentId, ) = directOwnerOf(tokenId); - // When the parent is an NFT, only it can do operations - if (parentId != 0) { - return (spender == owner); - } - // Otherwise, the owner or approved address can - return (spender == owner || - isApprovedForAll(owner, spender) || - getApproved(tokenId) == spender); - } - - /** - * @notice Used to enforce that the given token has been minted. - * @dev Reverts if the `tokenId` has not been minted yet. - * @dev The validation checks whether the owner of a given token is a `0x0` address and considers it not minted if - * it is. This means that both tokens that haven't been minted yet as well as the ones that have already been - * burned will cause the transaction to be reverted. - * @param tokenId ID of the token to check - */ - function _requireMinted(uint256 tokenId) internal view virtual { - if (!_exists(tokenId)) revert ERC721InvalidTokenId(); - } - - /** - * @notice Used to check whether the given token exists. - * @dev Tokens start existing when they are minted (`_mint`) and stop existing when they are burned (`_burn`). - * @param tokenId ID of the token being checked - * @return A boolean value signifying whether the token exists - */ - function _exists(uint256 tokenId) internal view virtual returns (bool) { - return _directOwners[tokenId].ownerAddress != address(0); - } - - /** - * @notice Used to invoke {IERC721Receiver-onERC721Received} on a target address. - * @dev The call is not executed if the target address is not a contract. - * @param from Address representing the previous owner of the given token - * @param to Yarget address that will receive the tokens - * @param tokenId ID of the token to be transferred - * @param data Optional data to send along with the call - * @return Boolean value signifying whether the call correctly returned the expected magic value - */ - function _checkOnERC721Received( - address from, - address to, - uint256 tokenId, - bytes memory data - ) private returns (bool) { - if (to.isContract()) { - try - IERC721Receiver(to).onERC721Received( - _msgSender(), - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == uint256(0)) { - revert ERC721TransferToNonReceiverImplementer(); - } else { - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } - } - } - } else { - return true; - } - } - - //////////////////////////////////////// - // CHILD MANAGEMENT PUBLIC - //////////////////////////////////////// - - /** - * @notice Used to add a child token to a given parent token. - * @dev This adds the iichild token into the given parent token's pending child tokens array. - * @dev You MUST NOT call this method directly. To add a a child to an NFT you must use either - * `nestTransfer`, `nestMint` or `transferChild` to the NFT. - * @dev Requirements: - * - * - `ownerOf` on the child contract must resolve to the called contract. - * - The pending array of the parent contract must not be full. - * @param parentId ID of the parent token to receive the new child token - * @param childId ID of the new proposed child token - * @param data Additional data with no specified format - */ - function addChild( - uint256 parentId, - uint256 childId, - bytes memory data - ) public virtual { - _requireMinted(parentId); - - address childAddress = _msgSender(); - if (!childAddress.isContract()) revert IsNotContract(); - - Child memory child = Child({ - contractAddress: childAddress, - tokenId: childId - }); - - _beforeAddChild(parentId, childAddress, childId, data); - - uint256 length = pendingChildrenOf(parentId).length; - - if (length < 128) { - _pendingChildren[parentId].push(child); - } else { - revert MaxPendingChildrenReached(); - } - - // Previous length matches the index for the new child - emit ChildProposed(parentId, length, childAddress, childId); - - _afterAddChild(parentId, childAddress, childId, data); - } - - /** - * @notice @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) public virtual onlyApprovedOrOwner(parentId) { - _acceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to accept a pending child token for a given parent token. - * @dev This moves the child token from parent token's pending child tokens array into the active child tokens - * array. - * @dev Requirements: - * - * - `tokenId` must exist - * - `index` must be in range of the pending children array - * @dev Emits ***ChildAccepted*** event. - * @param parentId ID of the parent token for which the child token is being accepted - * @param childIndex Index of a child tokem in the given parent's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _acceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual { - Child memory child = pendingChildOf(parentId, childIndex); - _checkExpectedChild(child, childAddress, childId); - if (_childIsInActive[childAddress][childId] != 0) - revert ChildAlreadyExists(); - - _beforeAcceptChild(parentId, childIndex, childAddress, childId); - - // Remove from pending: - _removeChildByIndex(_pendingChildren[parentId], childIndex); - - // Add to active: - _activeChildren[parentId].push(child); - _childIsInActive[childAddress][childId] = 1; // We use 1 as true - - emit ChildAccepted(parentId, childIndex, childAddress, childId); - - _afterAcceptChild(parentId, childIndex, childAddress, childId); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @param tokenId ID of the parent token for which to reject all of the pending tokens - */ - function rejectAllChildren( - uint256 tokenId, - uint256 maxRejections - ) public virtual onlyApprovedOrOwner(tokenId) { - _rejectAllChildren(tokenId, maxRejections); - } - - /** - * @notice Used to reject all pending children of a given parent token. - * @dev Removes the children from the pending array mapping. - * @dev This does not update the ownership storage data on children. If necessary, ownership can be reclaimed by the - * rootOwner of the previous parent. - * @dev Requirements: - * - * - `tokenId` must exist - * @dev Emits ***AllChildrenRejected*** event. - * @param tokenId ID of the parent token for which to reject all of the pending tokens. - * @param maxRejections Maximum number of expected children to reject, used to prevent from rejecting children which - * arrive just before this operation. - */ - function _rejectAllChildren( - uint256 tokenId, - uint256 maxRejections - ) internal virtual { - if (_pendingChildren[tokenId].length > maxRejections) - revert UnexpectedNumberOfChildren(); - - _beforeRejectAllChildren(tokenId); - delete _pendingChildren[tokenId]; - emit AllChildrenRejected(tokenId); - _afterRejectAllChildren(tokenId); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of the - * parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function transferChild( - uint256 tokenId, - address to, - uint256 destinationId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) public virtual onlyApprovedOrOwner(tokenId) { - _transferChild( - tokenId, - to, - destinationId, - childIndex, - childAddress, - childId, - isPending, - data - ); - } - - /** - * @notice Used to transfer a child token from a given parent token. - * @dev When transferring a child token, the owner of the token is set to `to`, or is not updated in the event of - * `to` being the `0x0` address. - * @dev Requirements: - * - * - `tokenId` must exist. - * @dev Emits {ChildTransferred} event. - * @param tokenId ID of the parent token from which the child token is being transferred - * @param to Address to which to transfer the token to - * @param destinationId ID of the token to receive this child token (MUST be 0 if the destination is not a token) - * @param childIndex Index of a token we are transferring, in the array it belongs to (can be either active array or - * pending array) - * @param childAddress Address of the child token's collection smart contract. - * @param childId ID of the child token in its own collection smart contract. - * @param isPending A boolean value indicating whether the child token being transferred is in the pending array of - * the parent token (`true`) or in the active array (`false`) - * @param data Additional data with no specified format, sent in call to `_to` - */ - function _transferChild( - uint256 tokenId, - address to, - uint256 destinationId, // newParentId - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) internal virtual { - Child memory child; - if (isPending) { - child = pendingChildOf(tokenId, childIndex); - } else { - child = childOf(tokenId, childIndex); - } - _checkExpectedChild(child, childAddress, childId); - - _beforeTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending, - data - ); - - if (isPending) { - _removeChildByIndex(_pendingChildren[tokenId], childIndex); - } else { - delete _childIsInActive[childAddress][childId]; - _removeChildByIndex(_activeChildren[tokenId], childIndex); - } - - if (to != address(0)) { - if (destinationId == uint256(0)) { - IERC721(childAddress).safeTransferFrom( - address(this), - to, - childId, - data - ); - } else { - // Destination is an NFT - IERC7401(child.contractAddress).nestTransferFrom( - address(this), - to, - child.tokenId, - destinationId, - data - ); - } - } - - emit ChildTransferred( - tokenId, - childIndex, - childAddress, - childId, - isPending, - to == address(0) - ); - _afterTransferChild( - tokenId, - childIndex, - childAddress, - childId, - isPending, - data - ); - } - - /** - * @notice Used to verify that the child being accessed is the intended child. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param child A Child struct of a child being accessed - * @param expectedAddress The address expected to be the one of the child - * @param expectedId The token ID expected to be the one of the child - */ - function _checkExpectedChild( - Child memory child, - address expectedAddress, - uint256 expectedId - ) private pure { - if ( - expectedAddress != child.contractAddress || - expectedId != child.tokenId - ) revert UnexpectedChildId(); - } - - //////////////////////////////////////// - // CHILD MANAGEMENT GETTERS - //////////////////////////////////////// - - /** - * @notice Used to retrieve the active child tokens of a given parent token. - * @dev Returns array of Child structs existing for parent token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the active child tokens - * @return struct[] An array of Child structs containing the parent token's active child tokens - */ - - function childrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory children = _activeChildren[parentId]; - return children; - } - - /** - * @notice Used to retrieve the pending child tokens of a given parent token. - * @dev Returns array of pending Child structs existing for given parent. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which to retrieve the pending child tokens - * @return struct[] An array of Child structs containing the parent token's pending child tokens - */ - - function pendingChildrenOf( - uint256 parentId - ) public view virtual returns (Child[] memory) { - Child[] memory pendingChildren = _pendingChildren[parentId]; - return pendingChildren; - } - - /** - * @notice Used to retrieve a specific active child token for a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the child is being retrieved - * @param index Index of the child token in the parent token's active child tokens array - * @return struct A Child struct containing data about the specified child - */ - function childOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (childrenOf(parentId).length <= index) - revert ChildIndexOutOfRange(); - Child memory child = _activeChildren[parentId][index]; - return child; - } - - /** - * @notice Used to retrieve a specific pending child token from a given parent token. - * @dev Returns a single Child struct locating at `index` of parent token's active child tokens array. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param parentId ID of the parent token for which the pending child token is being retrieved - * @param index Index of the child token in the parent token's pending child tokens array - * @return struct A Child struct containting data about the specified child - */ - function pendingChildOf( - uint256 parentId, - uint256 index - ) public view virtual returns (Child memory) { - if (pendingChildrenOf(parentId).length <= index) - revert PendingChildIndexOutOfRange(); - Child memory child = _pendingChildren[parentId][index]; - return child; - } - - // HOOKS - - /** - * @notice Hook that is called before any token transfer. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be transferred to `to`. - * - When `from` is zero, `tokenId` will be minted to `to`. - * - When `to` is zero, ``from``'s `tokenId` will be burned. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param tokenId ID of the token being transferred - */ - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called after any transfer of tokens. This includes minting and burning. - * @dev Calling conditions: - * - * - When `from` and `to` are both non-zero. - * - `from` and `to` are never zero at the same time. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token has been transferred - * @param to Address to which the token has been transferred - * @param tokenId ID of the token that has been transferred - */ - function _afterTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal virtual {} - - /** - * @notice Hook that is called before nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token is being transferred - * @param to Address to which the token is being transferred - * @param fromTokenId ID of the token from which the given token is being transferred - * @param toTokenId ID of the token to which the given token is being transferred - * @param tokenId ID of the token being transferred - * @param data Additional data with no specified format, sent in the addChild call - */ - function _beforeNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId, - bytes memory data - ) internal virtual {} - - /** - * @notice Hook that is called after nested token transfer. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param from Address from which the token was transferred - * @param to Address to which the token was transferred - * @param fromTokenId ID of the token from which the given token was transferred - * @param toTokenId ID of the token to which the given token was transferred - * @param tokenId ID of the token that was transferred - * @param data Additional data with no specified format, sent in the addChild call - */ - function _afterNestedTokenTransfer( - address from, - address to, - uint256 fromTokenId, - uint256 toTokenId, - uint256 tokenId, - bytes memory data - ) internal virtual {} - - /** - * @notice Hook that is called before a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will receive a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - * @param data Additional data with no specified format - */ - function _beforeAddChild( - uint256 tokenId, - address childAddress, - uint256 childId, - bytes memory data - ) internal virtual {} - - /** - * @notice Hook that is called after a child is added to the pending tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has received a new pending child token - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - * @param data Additional data with no specified format - */ - function _afterAddChild( - uint256 tokenId, - address childAddress, - uint256 childId, - bytes memory data - ) internal virtual {} - - /** - * @notice Hook that is called before a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that will accept a pending child token - * @param childIndex Index of the child token to accept in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token expected to be located at the - * specified index of the given parent token's pending children array - * @param childId ID of the child token expected to be located at the specified index of the given parent token's - * pending children array - */ - function _beforeAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called after a child is accepted to the active tokens array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param parentId ID of the token that has accepted a pending child token - * @param childIndex Index of the child token that was accpeted in the given parent token's pending children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's pending children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's pending children array - */ - function _afterAcceptChild( - uint256 parentId, - uint256 childIndex, - address childAddress, - uint256 childId - ) internal virtual {} - - /** - * @notice Hook that is called before a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will transfer a child token - * @param childIndex Index of the child token that will be transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that is expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that is expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token is being transferred from the pending child - * tokens array (`true`) or from the active child tokens array (`false`) - * @param data Additional data with no specified format, sent in the addChild call - */ - function _beforeTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) internal virtual {} - - /** - * @notice Hook that is called after a child is transferred from a given child token array of a given token. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has transferred a child token - * @param childIndex Index of the child token that was transferred from the given parent token's children array - * @param childAddress Address of the collection smart contract of the child token that was expected to be located - * at the specified index of the given parent token's children array - * @param childId ID of the child token that was expected to be located at the specified index of the given parent - * token's children array - * @param isPending A boolean value signifying whether the child token was transferred from the pending child tokens - * array (`true`) or from the active child tokens array (`false`) - * @param data Additional data with no specified format, sent in the addChild call - */ - function _afterTransferChild( - uint256 tokenId, - uint256 childIndex, - address childAddress, - uint256 childId, - bool isPending, - bytes memory data - ) internal virtual {} - - /** - * @notice Hook that is called before a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that will reject all of the pending child tokens - */ - function _beforeRejectAllChildren(uint256 tokenId) internal virtual {} - - /** - * @notice Hook that is called after a pending child tokens array of a given token is cleared. - * @dev To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - * @param tokenId ID of the token that has rejected all of the pending child tokens - */ - function _afterRejectAllChildren(uint256 tokenId) internal virtual {} - - // HELPERS - - /** - * @notice Used to remove a specified child token form an array using its index within said array. - * @dev The caller must ensure that the length of the array is valid compared to the index passed. - * @dev The Child struct consists of the following values: - * [ - * tokenId, - * contractAddress - * ] - * @param array An array od Child struct containing info about the child tokens in a given child tokens array - * @param index An index of the child token to remove in the accompanying array - */ - function _removeChildByIndex(Child[] storage array, uint256 index) private { - array[index] = array[array.length - 1]; - array.pop(); - } -} diff --git a/assets/eip-7401/contracts/mocks/ERC721Mock.sol b/assets/eip-7401/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index b31caeb545ac9e..00000000000000 --- a/assets/eip-7401/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests with non RMRK implementer - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} -} diff --git a/assets/eip-7401/contracts/mocks/NestableTokenMock.sol b/assets/eip-7401/contracts/mocks/NestableTokenMock.sol deleted file mode 100644 index 30872e3e48d904..00000000000000 --- a/assets/eip-7401/contracts/mocks/NestableTokenMock.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "../NestableToken.sol"; - -//Minimal public implementation of IRMRKNestable for testing. -contract NestableTokenMock is NestableToken { - constructor() NestableToken() {} - - function mint(address to, uint256 tokenId) external { - _mint(to, tokenId, ""); - } - - function nestMint( - address to, - uint256 tokenId, - uint256 destinationId - ) external { - _nestMint(to, tokenId, destinationId, ""); - } - - // Utility transfers: - - function transfer(address to, uint256 tokenId) public virtual { - transferFrom(_msgSender(), to, tokenId); - } - - function nestTransfer( - address to, - uint256 tokenId, - uint256 destinationId - ) public virtual { - nestTransferFrom(_msgSender(), to, tokenId, destinationId, ""); - } -} diff --git a/assets/eip-7401/hardhat.config.ts b/assets/eip-7401/hardhat.config.ts deleted file mode 100644 index 4289f15e8be78d..00000000000000 --- a/assets/eip-7401/hardhat.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HardhatUserConfig } from 'hardhat/config'; -import '@nomicfoundation/hardhat-chai-matchers'; -import '@nomiclabs/hardhat-etherscan'; -import '@typechain/hardhat'; -import 'hardhat-contract-sizer'; -import 'hardhat-gas-reporter'; -import 'solidity-coverage'; - -const config: HardhatUserConfig = { - solidity: { - version: '0.8.16', - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-7401/img/eip-7401-abandon-child.png b/assets/eip-7401/img/eip-7401-abandon-child.png deleted file mode 100644 index e97465dbca45f1..00000000000000 Binary files a/assets/eip-7401/img/eip-7401-abandon-child.png and /dev/null differ diff --git a/assets/eip-7401/img/eip-7401-nestable-tokens.png b/assets/eip-7401/img/eip-7401-nestable-tokens.png deleted file mode 100644 index cfc15eca08d4bc..00000000000000 Binary files a/assets/eip-7401/img/eip-7401-nestable-tokens.png and /dev/null differ diff --git a/assets/eip-7401/img/eip-7401-reject-child.png b/assets/eip-7401/img/eip-7401-reject-child.png deleted file mode 100644 index 2dc8979906f210..00000000000000 Binary files a/assets/eip-7401/img/eip-7401-reject-child.png and /dev/null differ diff --git a/assets/eip-7401/img/eip-7401-transfer-child-to-eoa.png b/assets/eip-7401/img/eip-7401-transfer-child-to-eoa.png deleted file mode 100644 index e623cbc0346492..00000000000000 Binary files a/assets/eip-7401/img/eip-7401-transfer-child-to-eoa.png and /dev/null differ diff --git a/assets/eip-7401/img/eip-7401-transfer-child-to-token.png b/assets/eip-7401/img/eip-7401-transfer-child-to-token.png deleted file mode 100644 index ee94447fcaef98..00000000000000 Binary files a/assets/eip-7401/img/eip-7401-transfer-child-to-token.png and /dev/null differ diff --git a/assets/eip-7401/img/eip-7401-unnest-child.png b/assets/eip-7401/img/eip-7401-unnest-child.png deleted file mode 100644 index f675c9c28d59ab..00000000000000 Binary files a/assets/eip-7401/img/eip-7401-unnest-child.png and /dev/null differ diff --git a/assets/eip-7401/package.json b/assets/eip-7401/package.json deleted file mode 100644 index 10e0e65ba6429f..00000000000000 --- a/assets/eip-7401/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "erc-7401", - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-etherscan": "^3.1.0", - "@openzeppelin/test-helpers": "^0.5.15", - "@primitivefi/hardhat-dodoc": "^0.2.3", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "@types/mocha": "^9.1.0", - "@types/node": "^18.0.3", - "@typescript-eslint/eslint-plugin": "^5.30.6", - "@typescript-eslint/parser": "^5.30.6", - "chai": "^4.3.6", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-promise": "^6.0.0", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "hardhat-contract-sizer": "^2.6.1", - "hardhat-gas-reporter": "^1.0.8", - "prettier": "2.7.1", - "prettier-plugin-solidity": "^1.0.0-beta.20", - "solc": "^0.8.9", - "solhint": "^3.3.7", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "walk-sync": "^3.0.0" - } -} diff --git a/assets/eip-7401/test/nestable.ts b/assets/eip-7401/test/nestable.ts deleted file mode 100644 index 7009e60891aadf..00000000000000 --- a/assets/eip-7401/test/nestable.ts +++ /dev/null @@ -1,1158 +0,0 @@ -import { expect } from 'chai'; -import { ethers } from 'hardhat'; -import { BigNumber, constants } from 'ethers'; -import { NestableTokenMock } from '../typechain-types'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -const ADDRESS_ZERO = constants.AddressZero; - -async function parentChildFixture(): Promise<{ - parent: NestableTokenMock; - child: NestableTokenMock; -}> { - const factory = await ethers.getContractFactory('NestableTokenMock'); - - const parent = await factory.deploy(); - await parent.deployed(); - const child = await factory.deploy(); - await child.deployed(); - return { parent, child }; -} - -describe('NestableToken', function () { - let parent: NestableTokenMock; - let child: NestableTokenMock; - let owner: SignerWithAddress; - let tokenOwner: SignerWithAddress; - let addrs: SignerWithAddress[]; - - beforeEach(async function () { - [owner, tokenOwner, ...addrs] = await ethers.getSigners(); - ({ parent, child } = await loadFixture(parentChildFixture)); - }); - - describe('Minting', async function () { - it('cannot mint id 0', async function () { - const tokenId1 = 0; - await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError( - child, - 'IdZeroForbidden', - ); - }); - - it('cannot nest mint id 0', async function () { - const parentId = 1; - await child.mint(owner.address, parentId); - const childId1 = 0; - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'IdZeroForbidden'); - }); - - it('cannot mint already minted token', async function () { - const tokenId1 = 1; - await child.mint(owner.address, tokenId1); - await expect(child.mint(owner.address, tokenId1)).to.be.revertedWithCustomError( - child, - 'ERC721TokenAlreadyMinted', - ); - }); - - it('cannot nest mint already minted token', async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted'); - }); - - it('cannot nest mint already minted token', async function () { - const parentId = 1; - const childId1 = 99; - await parent.mint(owner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.nestMint(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TokenAlreadyMinted'); - }); - - it('can mint with no destination', async function () { - const tokenId1 = 1; - await child.mint(tokenOwner.address, tokenId1); - expect(await child.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await child.directOwnerOf(tokenId1)).to.eql([tokenOwner.address, bn(0), false]); - }); - - it('has right owners', async function () { - const otherOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(tokenOwner.address, tokenId1); - const tokenId2 = 2; - await parent.mint(otherOwner.address, tokenId2); - const tokenId3 = 3; - await parent.mint(otherOwner.address, tokenId3); - - expect(await parent.ownerOf(tokenId1)).to.equal(tokenOwner.address); - expect(await parent.ownerOf(tokenId2)).to.equal(otherOwner.address); - expect(await parent.ownerOf(tokenId3)).to.equal(otherOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(2); - - await expect(parent.ownerOf(9999)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - }); - - it('cannot mint to zero address', async function () { - await expect(child.mint(ADDRESS_ZERO, 1)).to.be.revertedWithCustomError( - child, - 'ERC721MintToTheZeroAddress', - ); - }); - - it('cannot nest mint to a non-contract destination', async function () { - await expect(child.nestMint(tokenOwner.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'IsNotContract', - ); - }); - - it('cannot nest mint to non nestable receiver', async function () { - const ERC721 = await ethers.getContractFactory('ERC721Mock'); - const nonReceiver = await ERC721.deploy('Non receiver', 'NR'); - await nonReceiver.deployed(); - - await expect(child.nestMint(nonReceiver.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'MintToNonNestableImplementer', - ); - }); - - it('cannot nest mint to a non-existent token', async function () { - await expect(child.nestMint(parent.address, 1, 1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - }); - - it('cannot nest mint to zero address', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(child.nestMint(ADDRESS_ZERO, parentId, 1)).to.be.revertedWithCustomError( - child, - 'IsNotContract', - ); - }); - - it('can mint to contract and owners are ok', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // owner is the same adress - expect(await parent.ownerOf(parentId)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - }); - - it('can mint to contract and direct owners are ok', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Direct owner is an address for the parent - expect(await parent.directOwnerOf(parentId)).to.eql([tokenOwner.address, bn(0), false]); - // Direct owner is a contract for the child - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - }); - - it("can mint to contract and parent's children are ok", async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - const children = await parent.childrenOf(parentId); - expect(children).to.eql([]); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([[bn(childId1), child.address]]); - expect(await parent.pendingChildOf(parentId, 0)).to.eql([bn(childId1), child.address]); - }); - - it('cannot get child out of index', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.childOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - 'ChildIndexOutOfRange', - ); - }); - - it('cannot get pending child out of index', async function () { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await expect(parent.pendingChildOf(parentId, 0)).to.be.revertedWithCustomError( - parent, - 'PendingChildIndexOutOfRange', - ); - }); - - it('can mint multiple children', async function () { - const parentId = 1; - const childId1 = 99; - const childId2 = 100; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - - expect(await child.ownerOf(childId1)).to.equal(tokenOwner.address); - expect(await child.ownerOf(childId2)).to.equal(tokenOwner.address); - - expect(await child.balanceOf(parent.address)).to.equal(2); - - const pendingChildren = await parent.pendingChildrenOf(parentId); - expect(pendingChildren).to.eql([ - [bn(childId1), child.address], - [bn(childId2), child.address], - ]); - }); - - it('can mint child into child', async function () { - const parentId = 1; - const childId1 = 99; - const granchildId = 999; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - - // Check balances -- yes, technically the counted balance indicates `child` owns an instance of itself - // and this is a little counterintuitive, but the root owner is the EOA. - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - const pendingChildrenOfChunky10 = await parent.pendingChildrenOf(parentId); - const pendingChildrenOfMonkey1 = await child.pendingChildrenOf(childId1); - - expect(pendingChildrenOfChunky10).to.eql([[bn(childId1), child.address]]); - expect(pendingChildrenOfMonkey1).to.eql([[bn(granchildId), child.address]]); - - expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]); - - expect(await child.ownerOf(granchildId)).to.eql(tokenOwner.address); - }); - - it('cannot have too many pending children', async () => { - const parentId = 1; - await parent.mint(tokenOwner.address, parentId); - - // First 128 should be fine. - for (let i = 1; i <= 128; i++) { - await child.nestMint(parent.address, i, parentId); - } - - await expect(child.nestMint(parent.address, 129, parentId)).to.be.revertedWithCustomError( - child, - 'MaxPendingChildrenReached', - ); - }); - }); - - describe('Interface support', async function () { - it('can support IERC165', async function () { - expect(await parent.supportsInterface('0x01ffc9a7')).to.equal(true); - }); - - it('can support IERC721', async function () { - expect(await parent.supportsInterface('0x80ac58cd')).to.equal(true); - }); - - it('can support IERC7401', async function () { - expect(await parent.supportsInterface('0x42b0e56f')).to.equal(true); - }); - - it('cannot support other interfaceId', async function () { - expect(await parent.supportsInterface('0xffffffff')).to.equal(false); - }); - }); - - describe('Adding child', async function () { - it('cannot add child from user address', async function () { - const tokenOwner1 = addrs[0]; - const tokenOwner2 = addrs[1]; - const parentId = 1; - await parent.mint(tokenOwner1.address, parentId); - const childId1 = 99; - await child.mint(tokenOwner2.address, childId1); - await expect(parent.addChild(parentId, childId1, '0x')).to.be.revertedWithCustomError( - parent, - 'IsNotContract', - ); - }); - }); - - describe('Accept child', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it('can accept child', async function () { - await expect(parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1)) - .to.emit(parent, 'ChildAccepted') - .withArgs(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('can accept child if approved', async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved).acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('can accept child if approved for all', async function () { - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator).acceptChild(parentId, 0, child.address, childId1); - await checkChildWasAccepted(); - }); - - it('cannot accept not owned child', async function () { - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).acceptChild(parentId, 0, child.address, childId1), - ).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot accept child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, otherChildId), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 0, otherAddress, childId1), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('cannot accept children for non existing index', async () => { - await expect( - parent.connect(tokenOwner).acceptChild(parentId, 1, child.address, childId1), - ).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange'); - }); - - async function checkChildWasAccepted() { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - } - }); - - describe('Rejecting children', async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, 99, parentId); - }); - - it('can reject all pending children', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect(parent.connect(tokenOwner).rejectAllChildren(parentId, 3)) - .to.emit(parent, 'AllChildrenRejected') - .withArgs(parentId); - await checkNoChildrenNorPending(parentId); - - // They are still on the child - expect(await child.balanceOf(parent.address)).to.equal(3); - }); - - it('cannot reject all pending children if there are more than expected', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - await expect( - parent.connect(tokenOwner).rejectAllChildren(parentId, 1), - ).to.be.revertedWithCustomError(parent, 'UnexpectedNumberOfChildren'); - }); - - it('can reject all pending children if approved', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const rejecter = addrs[1]; - await parent.connect(tokenOwner).approve(rejecter.address, parentId); - await parent.connect(rejecter).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it('can reject all pending children if approved for all', async function () { - // Mint a couple of more children - await child.nestMint(parent.address, 100, parentId); - await child.nestMint(parent.address, 101, parentId); - - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator).rejectAllChildren(parentId, 3); - await checkNoChildrenNorPending(parentId); - }); - - it('cannot reject all pending children for not owned pending child', async function () { - const notOwner = addrs[3]; - - await expect( - parent.connect(notOwner).rejectAllChildren(parentId, 2), - ).to.be.revertedWithCustomError(parent, 'ERC721NotApprovedOrOwner'); - }); - }); - - describe('Burning', async function () { - let parentId: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - }); - - it('can burn token', async function () { - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - await parent.connect(tokenOwner)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can burn token if approved', async function () { - const approved = addrs[1]; - await parent.connect(tokenOwner).approve(approved.address, parentId); - await parent.connect(approved)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can burn token if approved for all', async function () { - const operator = addrs[2]; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - await parent.connect(operator)['burn(uint256)'](parentId); - await checkBurntParent(); - }); - - it('can recursively burn nested token', async function () { - const childId1 = 99; - const granchildId = 999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(child.address, granchildId, childId1); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, granchildId); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await child.balanceOf(child.address)).to.equal(1); - - expect(await parent.childrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - expect(await child.childrenOf(childId1)).to.eql([[bn(granchildId), child.address]]); - expect(await child.directOwnerOf(granchildId)).to.eql([child.address, bn(childId1), true]); - - // Sets recursive burns to 2 - await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2); - - expect(await parent.balanceOf(tokenOwner.address)).to.equal(0); - expect(await child.balanceOf(parent.address)).to.equal(0); - expect(await child.balanceOf(child.address)).to.equal(0); - - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - await expect(parent.directOwnerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - - await expect(child.ownerOf(childId1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - await expect(child.directOwnerOf(childId1)).to.be.revertedWithCustomError( - child, - 'ERC721InvalidTokenId', - ); - - await expect(parent.ownerOf(granchildId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - await expect(parent.directOwnerOf(granchildId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - }); - - it('can recursively burn nested token with the right number of recursive burns', async function () { - // Parent - // -> Child1 - // -> GrandChild1 - // -> GrandChild2 - // -> GreatGrandChild1 - // -> Child2 - // Total tree 5 (4 recursive burns) - const childId1 = 99; - const childId2 = 100; - const grandChild1 = 999; - const grandChild2 = 1000; - const greatGrandChild1 = 9999; - await child.nestMint(parent.address, childId1, parentId); - await child.nestMint(parent.address, childId2, parentId); - await child.nestMint(child.address, grandChild1, childId1); - await child.nestMint(child.address, grandChild2, childId1); - await child.nestMint(child.address, greatGrandChild1, grandChild2); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId2); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild1); - await child.connect(tokenOwner).acceptChild(childId1, 0, child.address, grandChild2); - await child.connect(tokenOwner).acceptChild(grandChild2, 0, child.address, greatGrandChild1); - - // 0 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 0)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, childId1); - // 1 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 1)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, grandChild1); - // 2 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 2)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, grandChild2); - // 3 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 3)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, greatGrandChild1); - // 4 is not enough - await expect(parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 4)) - .to.be.revertedWithCustomError(parent, 'MaxRecursiveBurnsReached') - .withArgs(child.address, childId2); - // 5 is just enough - await parent.connect(tokenOwner)['burn(uint256,uint256)'](parentId, 5); - }); - - async function checkBurntParent() { - expect(await parent.balanceOf(addrs[1].address)).to.equal(0); - await expect(parent.ownerOf(parentId)).to.be.revertedWithCustomError( - parent, - 'ERC721InvalidTokenId', - ); - } - }); - - describe('Transferring Active Children', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - childId1 = 99; - await parent.mint(tokenOwner.address, parentId); - await child.nestMint(parent.address, childId1, parentId); - await parent.connect(tokenOwner).acceptChild(parentId, 0, child.address, childId1); - }); - - it('can transfer child with to as root owner', async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, false, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false, false); - - await checkChildMovedToRootOwner(); - }); - - it('can transfer child to another address', async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, false, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false, false); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it('can transfer child to another NFT', async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - false, - '0x', - ), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, false, false); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot transfer child out of index', async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'ChildIndexOutOfRange'); - }); - - it('cannot transfer child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, false, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('can transfer child if approved', async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child if approved for all', async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child with grandchild and children are ok', async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]); - }); - - it('cannot transfer child if not child root owner', async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot transfer child from not existing parent', async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild(badChildId, toOwner, 0, 0, child.address, childId1, false, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId'); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe('Transferring Pending Children', async function () { - let parentId: number; - let childId1: number; - - beforeEach(async function () { - parentId = 1; - await parent.mint(tokenOwner.address, parentId); - childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - }); - - it('can transfer child with to as root owner', async function () { - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, tokenOwner.address, 0, 0, child.address, childId1, true, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true, false); - - await checkChildMovedToRootOwner(); - }); - - it('can transfer child to another address', async function () { - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, childId1, true, '0x'), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true, false); - - await checkChildMovedToRootOwner(toOwnerAddress); - }); - - it('can transfer child to another NFT', async function () { - const newOwnerAddress = addrs[2].address; - const newParentId = 2; - await parent.mint(newOwnerAddress, newParentId); - await expect( - parent - .connect(tokenOwner) - .transferChild( - parentId, - parent.address, - newParentId, - 0, - child.address, - childId1, - true, - '0x', - ), - ) - .to.emit(parent, 'ChildTransferred') - .withArgs(parentId, 0, child.address, childId1, true, false); - - expect(await child.ownerOf(childId1)).to.eql(newOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(newParentId), true]); - expect(await parent.pendingChildrenOf(newParentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot transfer child out of index', async function () { - const toOwnerAddress = addrs[2].address; - const badIndex = 2; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, badIndex, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'PendingChildIndexOutOfRange'); - }); - - it('cannot transfer child if address or id do not match', async function () { - const otherAddress = addrs[1].address; - const otherChildId = 9999; - const toOwnerAddress = addrs[2].address; - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, otherAddress, childId1, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - await expect( - parent - .connect(tokenOwner) - .transferChild(parentId, toOwnerAddress, 0, 0, child.address, otherChildId, true, '0x'), - ).to.be.revertedWithCustomError(parent, 'UnexpectedChildId'); - }); - - it('can transfer child if approved', async function () { - const transferer = addrs[1]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).approve(transferer.address, parentId); - - await parent - .connect(transferer) - .transferChild(parentId, toOwner, 0, 0,child.address, childId1, true, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child if approved for all', async function () { - const operator = addrs[2]; - const toOwner = tokenOwner.address; - await parent.connect(tokenOwner).setApprovalForAll(operator.address, true); - - await parent - .connect(operator) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'); - await checkChildMovedToRootOwner(); - }); - - it('can transfer child with grandchild and children are ok', async function () { - const toOwner = tokenOwner.address; - const grandchildId = 999; - await child.nestMint(child.address, grandchildId, childId1); - - // Transfer child from parent. - await parent - .connect(tokenOwner) - .transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([tokenOwner.address, bn(0), false]); - - // Grandchild is still owned by child - expect(await child.ownerOf(grandchildId)).to.eql(tokenOwner.address); - expect(await child.directOwnerOf(grandchildId)).to.eql([child.address, bn(childId1), true]); - }); - - it('cannot transfer child if not child root owner', async function () { - const toOwner = tokenOwner.address; - const notOwner = addrs[3]; - await expect( - parent.connect(notOwner).transferChild(parentId, toOwner, 0, 0, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721NotApprovedOrOwner'); - }); - - it('cannot transfer child from not existing parent', async function () { - const badChildId = 99; - const toOwner = tokenOwner.address; - await expect( - parent - .connect(tokenOwner) - .transferChild(badChildId, toOwner, 0, 0, child.address, childId1, true, '0x'), - ).to.be.revertedWithCustomError(child, 'ERC721InvalidTokenId'); - }); - - async function checkChildMovedToRootOwner(rootOwnerAddress?: string) { - if (rootOwnerAddress === undefined) { - rootOwnerAddress = tokenOwner.address; - } - expect(await child.ownerOf(childId1)).to.eql(rootOwnerAddress); - expect(await child.directOwnerOf(childId1)).to.eql([rootOwnerAddress, bn(0), false]); - - // Transferring updates balances downstream - expect(await child.balanceOf(rootOwnerAddress)).to.equal(1); - expect(await parent.balanceOf(tokenOwner.address)).to.equal(1); - } - }); - - describe('Transfer', async function () { - it('can transfer token', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - // Balances and ownership are updated - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - }); - - it('cannot transfer not owned token', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(newOwner).transfer(newOwner.address, tokenId1), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot transfer to address zero', async function () { - const firstOwner = addrs[1]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - await expect( - parent.connect(firstOwner).transfer(ADDRESS_ZERO, tokenId1), - ).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress'); - }); - - it('can transfer token from approved address (not owner)', async function () { - const firstOwner = addrs[1]; - const approved = addrs[2]; - const newOwner = addrs[3]; - const tokenId1 = 1; - await parent.mint(firstOwner.address, tokenId1); - - await parent.connect(firstOwner).approve(approved.address, tokenId1); - await parent.connect(firstOwner).transfer(newOwner.address, tokenId1); - - expect(await parent.ownerOf(tokenId1)).to.eql(newOwner.address); - }); - - it('can transfer not nested token with child to address and owners/children are ok', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await parent.connect(firstOwner).transfer(newOwner.address, parentId); - - // Balances and ownership are updated - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(newOwner.address)).to.equal(1); - - expect(await parent.ownerOf(parentId)).to.eql(newOwner.address); - expect(await parent.directOwnerOf(parentId)).to.eql([newOwner.address, bn(0), false]); - - // New owner of child - expect(await child.ownerOf(childId1)).to.eql(newOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - - // Parent still has its children - expect(await parent.pendingChildrenOf(parentId)).to.eql([[bn(childId1), child.address]]); - }); - - it('cannot directly transfer nested child', async function () { - const firstOwner = addrs[1]; - const newOwner = addrs[2]; - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - await expect( - child.connect(firstOwner).transfer(newOwner.address, childId1), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('can transfer parent token to token with same owner, family tree is ok', async function () { - const firstOwner = addrs[1]; - const grandParentId = 999; - await parent.mint(firstOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(firstOwner.address)).to.equal(2); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Transfers token parentId to (parent.address, token grandParentId) - await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId); - - // Balances unchanged since root owner is the same - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - - it('can transfer parent token to token with different owner, family tree is ok', async function () { - const firstOwner = addrs[1]; - const otherOwner = addrs[2]; - const grandParentId = 999; - await parent.mint(otherOwner.address, grandParentId); - const parentId = 1; - await parent.mint(firstOwner.address, parentId); - const childId1 = 99; - await child.nestMint(parent.address, childId1, parentId); - - // Check balances - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await parent.balanceOf(firstOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // firstOwner calls parent to transfer parent token parent - await parent.connect(firstOwner).nestTransfer(parent.address, parentId, grandParentId); - - // Balances update - expect(await parent.balanceOf(firstOwner.address)).to.equal(0); - expect(await parent.balanceOf(parent.address)).to.equal(1); - expect(await parent.balanceOf(otherOwner.address)).to.equal(1); - expect(await child.balanceOf(parent.address)).to.equal(1); - - // Parent is still owner of child - let expected = [bn(childId1), child.address]; - checkAcceptedAndPendingChildren(parent, parentId, [expected], []); - // Ownership: firstOwner > newGrandparent > parent > child - expected = [bn(parentId), parent.address]; - checkAcceptedAndPendingChildren(parent, grandParentId, [], [expected]); - }); - }); - - describe('Nest Transfer', async function () { - let firstOwner: SignerWithAddress; - let parentId: number; - let childId1: number; - - beforeEach(async function () { - firstOwner = addrs[1]; - parentId = 1; - childId1 = 99; - await parent.mint(firstOwner.address, parentId); - await child.mint(firstOwner.address, childId1); - }); - - it('cannot nest tranfer from non immediate owner (owner of parent)', async function () { - const otherParentId = 2; - await parent.mint(firstOwner.address, otherParentId); - // We send it to the parent first - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child.connect(firstOwner).nestTransfer(parent.address, childId1, otherParentId), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot nest tranfer to same NFT', async function () { - // We can no longer nest transfer it, even if we are the root owner: - await expect( - child.connect(firstOwner).nestTransfer(child.address, childId1, childId1), - ).to.be.revertedWithCustomError(child, 'NestableTransferToSelf'); - }); - - it('cannot nest tranfer a descendant same NFT', async function () { - // We can no longer nest transfer it, even if we are the root owner: - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - const grandChildId = 999; - await child.nestMint(child.address, grandChildId, childId1); - // Ownership is now parent->child->granChild - // Cannot send parent to grandChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, grandChildId), - ).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant'); - // Cannot send parent to child - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, childId1), - ).to.be.revertedWithCustomError(child, 'NestableTransferToDescendant'); - }); - - it('cannot nest tranfer if ancestors tree is too deep', async function () { - let lastId = childId1; - for (let i = 101; i <= 200; i++) { - await child.nestMint(child.address, i, lastId); - lastId = i; - } - // Ownership is now parent->child->child->child->child...->lastChild - // Cannot send parent to lastChild - await expect( - parent.connect(firstOwner).nestTransfer(child.address, parentId, lastId), - ).to.be.revertedWithCustomError(child, 'NestableTooDeep'); - }); - - it('cannot nest tranfer if not owner', async function () { - const notOwner = addrs[3]; - await expect( - child.connect(notOwner).nestTransfer(parent.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'NotApprovedOrDirectOwner'); - }); - - it('cannot nest tranfer to address 0', async function () { - await expect( - child.connect(firstOwner).nestTransfer(ADDRESS_ZERO, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'ERC721TransferToTheZeroAddress'); - }); - - it('cannot nest tranfer to a non contract', async function () { - const newOwner = addrs[2]; - await expect( - child.connect(firstOwner).nestTransfer(newOwner.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'IsNotContract'); - }); - - it('cannot nest tranfer to contract if it does implement IERC7401', async function () { - const ERC721 = await ethers.getContractFactory('ERC721Mock'); - const nonNestable = await ERC721.deploy('Non receiver', 'NR'); - await nonNestable.deployed(); - await expect( - child.connect(firstOwner).nestTransfer(nonNestable.address, childId1, parentId), - ).to.be.revertedWithCustomError(child, 'NestableTransferToNonNestableImplementer'); - }); - - it('can nest tranfer to IERC7401 contract', async function () { - await child.connect(firstOwner).nestTransfer(parent.address, childId1, parentId); - expect(await child.ownerOf(childId1)).to.eql(firstOwner.address); - expect(await child.directOwnerOf(childId1)).to.eql([parent.address, bn(parentId), true]); - }); - - it('cannot nest tranfer to non existing parent token', async function () { - const notExistingParentId = 9999; - await expect( - child.connect(firstOwner).nestTransfer(parent.address, childId1, notExistingParentId), - ).to.be.revertedWithCustomError(parent, 'ERC721InvalidTokenId'); - }); - }); - - async function checkNoChildrenNorPending(parentId: number): Promise { - expect(await parent.pendingChildrenOf(parentId)).to.eql([]); - expect(await parent.childrenOf(parentId)).to.eql([]); - } - - async function checkAcceptedAndPendingChildren( - contract: NestableTokenMock, - tokenId1: number, - expectedAccepted: any[], - expectedPending: any[], - ) { - const accepted = await contract.childrenOf(tokenId1); - expect(accepted).to.eql(expectedAccepted); - - const pending = await contract.pendingChildrenOf(tokenId1); - expect(pending).to.eql(expectedPending); - } -}); diff --git a/assets/eip-7405/overview-diagram.jpg b/assets/eip-7405/overview-diagram.jpg deleted file mode 100644 index 63ca67a41c5fb4..00000000000000 Binary files a/assets/eip-7405/overview-diagram.jpg and /dev/null differ diff --git a/assets/eip-7409/contracts/EmotableRepository.sol b/assets/eip-7409/contracts/EmotableRepository.sol deleted file mode 100644 index 0ed43ee39b18bf..00000000000000 --- a/assets/eip-7409/contracts/EmotableRepository.sol +++ /dev/null @@ -1,362 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.21; - -import "./IERC7409.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -error BulkParametersOfUnequalLength(); -error ExpiredPresignedEmote(); -error InvalidSignature(); - -contract EmoteRepository is IERC7409 { - bytes32 public immutable DOMAIN_SEPARATOR = - keccak256( - abi.encode( - "ERC-7409: Public Non-Fungible Token Emote Repository", - "1", - block.chainid, - address(this) - ) - ); - - // Used to avoid double emoting and control undoing - mapping(address emoter => mapping(address collection => mapping(uint256 tokenId => mapping(string emoji => uint256 state)))) - private _emotesUsedByEmoter; // Cheaper than using a bool - mapping(address collection => mapping(uint256 tokenId => mapping(string emoji => uint256 count))) - private _emotesPerToken; - - function emoteCountOf( - address collection, - uint256 tokenId, - string memory emoji - ) public view returns (uint256) { - return _emotesPerToken[collection][tokenId][emoji]; - } - - function bulkEmoteCountOf( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis - ) public view returns (uint256[] memory) { - if ( - collections.length != tokenIds.length || - collections.length != emojis.length - ) { - revert BulkParametersOfUnequalLength(); - } - - uint256[] memory counts = new uint256[](collections.length); - for (uint256 i; i < collections.length; ) { - counts[i] = _emotesPerToken[collections[i]][tokenIds[i]][emojis[i]]; - unchecked { - ++i; - } - } - return counts; - } - - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - string memory emoji - ) public view returns (bool) { - return _emotesUsedByEmoter[emoter][collection][tokenId][emoji] == 1; - } - - function haveEmotersUsedEmotes( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis - ) public view returns (bool[] memory) { - if ( - emoters.length != collections.length || - emoters.length != tokenIds.length || - emoters.length != emojis.length - ) { - revert BulkParametersOfUnequalLength(); - } - - bool[] memory states = new bool[](collections.length); - for (uint256 i; i < collections.length; ) { - states[i] = - _emotesUsedByEmoter[emoters[i]][collections[i]][tokenIds[i]][ - emojis[i] - ] == - 1; - unchecked { - ++i; - } - } - return states; - } - - function emote( - address collection, - uint256 tokenId, - string memory emoji, - bool state - ) public { - bool currentVal = _emotesUsedByEmoter[msg.sender][collection][tokenId][ - emoji - ] == 1; - if (currentVal != state) { - if (state) { - _emotesPerToken[collection][tokenId][emoji] += 1; - } else { - _emotesPerToken[collection][tokenId][emoji] -= 1; - } - _emotesUsedByEmoter[msg.sender][collection][tokenId][emoji] = state - ? 1 - : 0; - emit Emoted(msg.sender, collection, tokenId, emoji, state); - } - } - - function bulkEmote( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states - ) public { - if ( - collections.length != tokenIds.length || - collections.length != emojis.length || - collections.length != states.length - ) { - revert BulkParametersOfUnequalLength(); - } - - bool currentVal; - for (uint256 i; i < collections.length; ) { - currentVal = - _emotesUsedByEmoter[msg.sender][collections[i]][tokenIds[i]][ - emojis[i] - ] == - 1; - if (currentVal != states[i]) { - if (states[i]) { - _emotesPerToken[collections[i]][tokenIds[i]][ - emojis[i] - ] += 1; - } else { - _emotesPerToken[collections[i]][tokenIds[i]][ - emojis[i] - ] -= 1; - } - _emotesUsedByEmoter[msg.sender][collections[i]][tokenIds[i]][ - emojis[i] - ] = states[i] ? 1 : 0; - emit Emoted( - msg.sender, - collections[i], - tokenIds[i], - emojis[i], - states[i] - ); - } - unchecked { - ++i; - } - } - } - - function prepareMessageToPresignEmote( - address collection, - uint256 tokenId, - string memory emoji, - bool state, - uint256 deadline - ) public view returns (bytes32) { - return - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collection, - tokenId, - emoji, - state, - deadline - ) - ); - } - - function bulkPrepareMessagesToPresignEmote( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states, - uint256[] memory deadlines - ) public view returns (bytes32[] memory) { - if ( - collections.length != tokenIds.length || - collections.length != emojis.length || - collections.length != states.length || - collections.length != deadlines.length - ) { - revert BulkParametersOfUnequalLength(); - } - - bytes32[] memory messages = new bytes32[](collections.length); - for (uint256 i; i < collections.length; ) { - messages[i] = keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collections[i], - tokenIds[i], - emojis[i], - states[i], - deadlines[i] - ) - ); - unchecked { - ++i; - } - } - - return messages; - } - - function presignedEmote( - address emoter, - address collection, - uint256 tokenId, - string memory emoji, - bool state, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public { - if (block.timestamp > deadline) { - revert ExpiredPresignedEmote(); - } - bytes32 digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collection, - tokenId, - emoji, - state, - deadline - ) - ) - ) - ); - address signer = ecrecover(digest, v, r, s); - if (signer != emoter) { - revert InvalidSignature(); - } - - bool currentVal = _emotesUsedByEmoter[signer][collection][tokenId][ - emoji - ] == 1; - if (currentVal != state) { - if (state) { - _emotesPerToken[collection][tokenId][emoji] += 1; - } else { - _emotesPerToken[collection][tokenId][emoji] -= 1; - } - _emotesUsedByEmoter[signer][collection][tokenId][emoji] = state - ? 1 - : 0; - emit Emoted(signer, collection, tokenId, emoji, state); - } - } - - function bulkPresignedEmote( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states, - uint256[] memory deadlines, - uint8[] memory v, - bytes32[] memory r, - bytes32[] memory s - ) public { - if ( - emoters.length != collections.length || - emoters.length != tokenIds.length || - emoters.length != emojis.length || - emoters.length != states.length || - emoters.length != deadlines.length || - emoters.length != v.length || - emoters.length != r.length || - emoters.length != s.length - ) { - revert BulkParametersOfUnequalLength(); - } - - bytes32 digest; - address signer; - bool currentVal; - for (uint256 i; i < collections.length; ) { - if (block.timestamp > deadlines[i]) { - revert ExpiredPresignedEmote(); - } - digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - collections[i], - tokenIds[i], - emojis[i], - states[i], - deadlines[i] - ) - ) - ) - ); - signer = ecrecover(digest, v[i], r[i], s[i]); - if (signer != emoters[i]) { - revert InvalidSignature(); - } - - currentVal = - _emotesUsedByEmoter[signer][collections[i]][tokenIds[i]][ - emojis[i] - ] == - 1; - if (currentVal != states[i]) { - if (states[i]) { - _emotesPerToken[collections[i]][tokenIds[i]][ - emojis[i] - ] += 1; - } else { - _emotesPerToken[collections[i]][tokenIds[i]][ - emojis[i] - ] -= 1; - } - _emotesUsedByEmoter[signer][collections[i]][tokenIds[i]][ - emojis[i] - ] = states[i] ? 1 : 0; - emit Emoted( - signer, - collections[i], - tokenIds[i], - emojis[i], - states[i] - ); - } - unchecked { - ++i; - } - } - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC7409).interfaceId || - interfaceId == type(IERC165).interfaceId; - } -} \ No newline at end of file diff --git a/assets/eip-7409/contracts/IERC7409.sol b/assets/eip-7409/contracts/IERC7409.sol deleted file mode 100644 index 8341bc21991409..00000000000000 --- a/assets/eip-7409/contracts/IERC7409.sol +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -interface IERC7409 { - event Emoted( - address indexed emoter, - address indexed collection, - uint256 indexed tokenId, - string emoji, - bool on - ); - - function emoteCountOf( - address collection, - uint256 tokenId, - string memory emoji - ) external view returns (uint256); - - function bulkEmoteCountOf( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis - ) external view returns (uint256[] memory); - - function hasEmoterUsedEmote( - address emoter, - address collection, - uint256 tokenId, - string memory emoji - ) external view returns (bool); - - function haveEmotersUsedEmotes( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis - ) external view returns (bool[] memory); - - function prepareMessageToPresignEmote( - address collection, - uint256 tokenId, - string memory emoji, - bool state, - uint256 deadline - ) external view returns (bytes32); - - function bulkPrepareMessagesToPresignEmote( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states, - uint256[] memory deadlines - ) external view returns (bytes32[] memory); - - function emote( - address collection, - uint256 tokenId, - string memory emoji, - bool state - ) external; - - function bulkEmote( - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states - ) external; - - function presignedEmote( - address emoter, - address collection, - uint256 tokenId, - string memory emoji, - bool state, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - function bulkPresignedEmote( - address[] memory emoters, - address[] memory collections, - uint256[] memory tokenIds, - string[] memory emojis, - bool[] memory states, - uint256[] memory deadlines, - uint8[] memory v, - bytes32[] memory r, - bytes32[] memory s - ) external; -} \ No newline at end of file diff --git a/assets/eip-7409/contracts/mocks/ERC721Mock.sol b/assets/eip-7409/contracts/mocks/ERC721Mock.sol deleted file mode 100644 index 1d450dddd0f330..00000000000000 --- a/assets/eip-7409/contracts/mocks/ERC721Mock.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.16; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -/** - * @title ERC721Mock - * Used for tests - */ -contract ERC721Mock is ERC721 { - constructor( - string memory name, - string memory symbol - ) ERC721(name, symbol) {} - - function mint(address to, uint256 amount) public { - _mint(to, amount); - } -} diff --git a/assets/eip-7409/hardhat.config.ts b/assets/eip-7409/hardhat.config.ts deleted file mode 100644 index da1c607c0f27b4..00000000000000 --- a/assets/eip-7409/hardhat.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.21", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-7409/package.json b/assets/eip-7409/package.json deleted file mode 100644 index b29b1e45945c1f..00000000000000 --- a/assets/eip-7409/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "eip-7409", - "scripts": { - "test": "yarn typechain && hardhat test", - "typechain": "hardhat typechain", - "prettier": "prettier --write ." - }, - "engines": { - "node": ">=16.0.0" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4" - } -} diff --git a/assets/eip-7409/test/emotableRepository.ts b/assets/eip-7409/test/emotableRepository.ts deleted file mode 100644 index d8773a95f38698..00000000000000 --- a/assets/eip-7409/test/emotableRepository.ts +++ /dev/null @@ -1,492 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { BigNumber, Contract } from "ethers"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { ERC721Mock, EmotableRepository } from "../typechain-types"; - -function bn(x: number): BigNumber { - return BigNumber.from(x); -} - -async function tokenFixture() { - const factory = await ethers.getContractFactory("ERC721Mock"); - const token = await factory.deploy("Chunky", "CHNK"); - await token.deployed(); - - return token; -} - -async function emotableRepositoryFixture() { - const factory = await ethers.getContractFactory("EmoteRepository"); - const repository = await factory.deploy(); - await repository.deployed(); - - return repository; -} - -describe("EmoteRepository", async function () { - let token: ERC721Mock; - let repository: EmotableRepository; - let owner: SignerWithAddress; - let addrs: SignerWithAddress[]; - const tokenId = bn(1); - const emoji1 = "👨‍👩‍👧"; - const emoji2 = "😁"; - - beforeEach(async function () { - [owner, ...addrs] = await ethers.getSigners(); - token = await loadFixture(tokenFixture); - repository = await loadFixture(emotableRepositoryFixture); - }); - - it("can support IERC7409", async function () { - expect(await repository.supportsInterface("0x1b3327ab")).to.equal(true); - }); - - it("can support IERC165", async function () { - expect(await repository.supportsInterface("0x01ffc9a7")).to.equal(true); - }); - - it("does not support other interfaces", async function () { - expect(await repository.supportsInterface("0xffffffff")).to.equal(false); - }); - - describe("With minted tokens", async function () { - beforeEach(async function () { - await token.mint(owner.address, tokenId); - }); - - it("can emote", async function () { - await expect( - repository.connect(addrs[0]).emote(token.address, tokenId, emoji1, true) - ) - .to.emit(repository, "Emoted") - .withArgs( - addrs[0].address, - token.address, - tokenId.toNumber(), - emoji1, - true - ); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - }); - - it("can undo emote", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - - await expect(repository.emote(token.address, tokenId, emoji1, false)) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - false - ); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(0)); - }); - - it("can be emoted from different accounts", async function () { - await repository - .connect(addrs[0]) - .emote(token.address, tokenId, emoji1, true); - await repository - .connect(addrs[1]) - .emote(token.address, tokenId, emoji1, true); - await repository - .connect(addrs[2]) - .emote(token.address, tokenId, emoji2, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(2)); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(1)); - }); - - it("can add multiple emojis to same NFT", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - await repository.emote(token.address, tokenId, emoji2, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(1)); - }); - - it("does nothing if new state is the same as old state", async function () { - await repository.emote(token.address, tokenId, emoji1, true); - await repository.emote(token.address, tokenId, emoji1, true); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji1) - ).to.equal(bn(1)); - - await repository.emote(token.address, tokenId, emoji2, false); - expect( - await repository.emoteCountOf(token.address, tokenId, emoji2) - ).to.equal(bn(0)); - }); - - it("can bulk emote", async function () { - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(0), bn(0)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([false, false]); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - true - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(1), bn(1)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([true, true]); - }); - - it("can bulk undo emote", async function () { - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - true - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(1), bn(1)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([true, true]); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [false, false] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - false - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - false - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(0), bn(0)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([false, false]); - }); - - it("can bulk emote and unemote at the same time", async function () { - await repository.emote(token.address, tokenId, emoji2, true); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(0), bn(1)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([false, true]); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, false] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - false - ); - - expect( - await repository.bulkEmoteCountOf( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([bn(1), bn(0)]); - - expect( - await repository.haveEmotersUsedEmotes( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2] - ) - ).to.eql([true, false]); - }); - - it("can not bulk emote if passing arrays of different length", async function () { - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - - await expect( - repository.bulkEmote( - [token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId], - [emoji1, emoji2], - [true, true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - - await expect( - repository.bulkEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1], - [true, true] - ) - ).to.be.revertedWithCustomError( - repository, - "BulkParametersOfUnequalLength" - ); - }); - - it("can use presigned emote to react to token", async function () { - const message = await repository.prepareMessageToPresignEmote( - token.address, - tokenId, - emoji1, - true, - bn(9999999999) - ); - - const signature = await owner.signMessage(ethers.utils.arrayify(message)); - - const r: string = signature.substring(0, 66); - const s: string = "0x" + signature.substring(66, 130); - const v: number = parseInt(signature.substring(130, 132), 16); - - await expect( - repository - .connect(addrs[0]) - .presignedEmote( - owner.address, - token.address, - tokenId, - emoji1, - true, - bn(9999999999), - v, - r, - s - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ); - }); - - it("can use presigned emotes to bulk react to token", async function () { - const messages = await repository.bulkPrepareMessagesToPresignEmote( - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true], - [bn(9999999999), bn(9999999999)] - ); - - const signature1 = await owner.signMessage( - ethers.utils.arrayify(messages[0]) - ); - const signature2 = await owner.signMessage( - ethers.utils.arrayify(messages[1]) - ); - - const r1: string = signature1.substring(0, 66); - const s1: string = "0x" + signature1.substring(66, 130); - const v1: number = parseInt(signature1.substring(130, 132), 16); - const r2: string = signature2.substring(0, 66); - const s2: string = "0x" + signature2.substring(66, 130); - const v2: number = parseInt(signature2.substring(130, 132), 16); - - await expect( - repository - .connect(addrs[0]) - .bulkPresignedEmote( - [owner.address, owner.address], - [token.address, token.address], - [tokenId, tokenId], - [emoji1, emoji2], - [true, true], - [bn(9999999999), bn(9999999999)], - [v1, v2], - [r1, r2], - [s1, s2] - ) - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji1, - true - ) - .to.emit(repository, "Emoted") - .withArgs( - owner.address, - token.address, - tokenId.toNumber(), - emoji2, - true - ); - }); - }); -}); diff --git a/assets/eip-7432/ERC7432.sol b/assets/eip-7432/ERC7432.sol deleted file mode 100644 index fccf29a58b1247..00000000000000 --- a/assets/eip-7432/ERC7432.sol +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.9; - -import { IERC7432 } from "./interfaces/IERC7432.sol"; - -contract ERC7432 is IERC7432 { - // grantor => grantee => tokenAddress => tokenId => role => struct(expirationDate, data) - mapping(address => mapping(address => mapping(address => mapping(uint256 => mapping(bytes32 => RoleData))))) - public roleAssignments; - - // grantor => tokenAddress => tokenId => role => grantee - mapping(address => mapping(address => mapping(uint256 => mapping(bytes32 => address)))) public latestGrantees; - - // grantor => operator => tokenAddress => isApproved - mapping(address => mapping(address => mapping(address => bool))) public tokenApprovals; - - modifier validExpirationDate(uint64 _expirationDate) { - require(_expirationDate > block.timestamp, "ERC7432: expiration date must be in the future"); - _; - } - - modifier onlyAccountOrApproved(address _tokenAddress, address _account) { - require( - msg.sender == _account || isRoleApprovedForAll(_tokenAddress, _account, msg.sender), - "ERC7432: sender must be approved" - ); - _; - } - - function grantRevocableRoleFrom(RoleAssignment calldata _roleAssignment) override onlyAccountOrApproved(_roleAssignment.tokenAddress, _roleAssignment.grantor) external { - _grantRole(_roleAssignment, true); - } - - function grantRoleFrom(RoleAssignment calldata _roleAssignment) external override onlyAccountOrApproved(_roleAssignment.tokenAddress, _roleAssignment.grantor) { - _grantRole(_roleAssignment, false); - } - - - function _grantRole( - RoleAssignment memory _roleAssignment, - bool _revocable - ) internal validExpirationDate(_roleAssignment.expirationDate) { - roleAssignments[_roleAssignment.grantor][_roleAssignment.grantee][_roleAssignment.tokenAddress][ - _roleAssignment.tokenId - ][_roleAssignment.role] = RoleData(_roleAssignment.expirationDate, _revocable, _roleAssignment.data); - latestGrantees[_roleAssignment.grantor][_roleAssignment.tokenAddress][_roleAssignment.tokenId][ - _roleAssignment.role - ] = _roleAssignment.grantee; - emit RoleGranted(_roleAssignment.role, _roleAssignment.tokenAddress, _roleAssignment.tokenId, _roleAssignment.grantor, _roleAssignment.grantee, _roleAssignment.expirationDate, _revocable, _roleAssignment.data); - } - - function revokeRoleFrom( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _revoker, - address _grantee - ) external override { - address _caller = msg.sender == _revoker || msg.sender == _grantee ? msg.sender : _getApprovedCaller(_tokenAddress, _revoker, _grantee); - _revokeRole(_role, _tokenAddress, _tokenId, _revoker, _grantee, _caller); - } - - function _getApprovedCaller(address _tokenAddress, address _revoker, address _grantee) internal view returns (address) { - if (isRoleApprovedForAll(_tokenAddress, _grantee, msg.sender)) { - return _grantee; - } else if (isRoleApprovedForAll(_tokenAddress, _revoker, msg.sender)) { - return _revoker; - } else { - revert("ERC7432: sender must be approved"); - } - } - - function _revokeRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _revoker, - address _grantee, - address _caller - ) internal { - bool _isRevocable = roleAssignments[_revoker][_grantee][_tokenAddress][_tokenId][_role].revocable; - require(_isRevocable || _caller == _grantee, "ERC7432: Role is not revocable or caller is not the grantee"); - delete roleAssignments[_revoker][_grantee][_tokenAddress][_tokenId][_role]; - delete latestGrantees[_revoker][_tokenAddress][_tokenId][_role]; - emit RoleRevoked(_role, _tokenAddress, _tokenId, _revoker, _grantee); - } - - function hasRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (bool) { - return roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role].expirationDate > block.timestamp; - } - - function hasUniqueRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (bool) { - return latestGrantees[_grantor][_tokenAddress][_tokenId][_role] == _grantee && roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role].expirationDate > block.timestamp; - } - - function roleData( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (RoleData memory) { - return roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role]; - } - - - function roleExpirationDate( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (uint64 expirationDate_) { - return roleAssignments[_grantor][_grantee][_tokenAddress][_tokenId][_role].expirationDate; - } - - function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) { - return interfaceId == type(IERC7432).interfaceId; - } - - function setRoleApprovalForAll( - address _tokenAddress, - address _operator, - bool _isApproved - ) external override { - tokenApprovals[msg.sender][_tokenAddress][_operator] = _isApproved; - emit RoleApprovalForAll(_tokenAddress, _operator, _isApproved); - } - - function isRoleApprovedForAll( - address _tokenAddress, - address _grantor, - address _operator - ) public view override returns (bool) { - return tokenApprovals[_grantor][_tokenAddress][_operator]; - } - - function lastGrantee( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor - ) public view override returns (address) { - return latestGrantees[_grantor][_tokenAddress][_tokenId][_role]; - } -} diff --git a/assets/eip-7432/interfaces/IERC7432.sol b/assets/eip-7432/interfaces/IERC7432.sol deleted file mode 100644 index 4bd30e0842db8e..00000000000000 --- a/assets/eip-7432/interfaces/IERC7432.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity 0.8.9; - -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/// @title ERC-7432 Non-Fungible Token Roles -/// @dev See https://eips.ethereum.org/EIPS/eip-7432 -/// Note: the ERC-165 identifier for this interface is 0x04984ac8. -interface IERC7432 is IERC165 { - struct RoleData { - uint64 expirationDate; - bool revocable; - bytes data; - } - - struct RoleAssignment { - bytes32 role; - address tokenAddress; - uint256 tokenId; - address grantor; - address grantee; - uint64 expirationDate; - bytes data; - } - - /** Events **/ - - /// @notice Emitted when a role is granted. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user assigning the role. - /// @param _grantee The user receiving the role. - /// @param _expirationDate The expiration date of the role. - /// @param _revocable Whether the role is revocable or not. - /// @param _data Any additional data about the role. - event RoleGranted( - bytes32 indexed _role, - address indexed _tokenAddress, - uint256 indexed _tokenId, - address _grantor, - address _grantee, - uint64 _expirationDate, - bool _revocable, - bytes _data - ); - - /// @notice Emitted when a role is revoked. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _revoker The user revoking the role. - /// @param _grantee The user that receives the role revocation. - event RoleRevoked( - bytes32 indexed _role, - address indexed _tokenAddress, - uint256 indexed _tokenId, - address _revoker, - address _grantee - ); - - /// @notice Emitted when a user is approved to manage any role on behalf of another user. - /// @param _tokenAddress The token address. - /// @param _operator The user approved to grant and revoke roles. - /// @param _isApproved The approval status. - event RoleApprovalForAll( - address indexed _tokenAddress, - address indexed _operator, - bool _isApproved - ); - - /** External Functions **/ - - /// @notice Grants a role on behalf of a user. - /// @param _roleAssignment The role assignment data. - function grantRoleFrom(RoleAssignment calldata _roleAssignment) external; - - /// @notice Grants a role on behalf of a user. - /// @param _roleAssignment The role assignment data. - function grantRevocableRoleFrom(RoleAssignment calldata _roleAssignment) external; - - /// @notice Revokes a role on behalf of a user. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _revoker The user revoking the role. - /// @param _grantee The user that receives the role revocation. - function revokeRoleFrom( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _revoker, - address _grantee - ) external; - - /// @notice Approves operator to grant and revoke any roles on behalf of another user. - /// @param _tokenAddress The token address. - /// @param _operator The user approved to grant and revoke roles. - /// @param _approved The approval status. - function setRoleApprovalForAll( - address _tokenAddress, - address _operator, - bool _approved - ) external; - - /** View Functions **/ - - /// @notice Checks if a user has a role. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function hasNonUniqueRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (bool); - - /// @notice Checks if a user has a unique role. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function hasRole( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (bool); - - /// @notice Returns the custom data of a role assignment. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function roleData( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (RoleData memory data_); - - /// @notice Returns the expiration date of a role assignment. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that assigned the role. - /// @param _grantee The user that received the role. - function roleExpirationDate( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor, - address _grantee - ) external view returns (uint64 expirationDate_); - - /// @notice Checks if the grantor approved the operator for all NFTs. - /// @param _tokenAddress The token address. - /// @param _grantor The user that approved the operator. - /// @param _operator The user that can grant and revoke roles. - function isRoleApprovedForAll( - address _tokenAddress, - address _grantor, - address _operator - ) external view returns (bool); - - /// @notice Returns the last user to receive a role. - /// @param _role The role identifier. - /// @param _tokenAddress The token address. - /// @param _tokenId The token identifier. - /// @param _grantor The user that granted the role. - function lastGrantee( - bytes32 _role, - address _tokenAddress, - uint256 _tokenId, - address _grantor - ) external view returns (address); -} diff --git a/assets/eip-7444/equation.png b/assets/eip-7444/equation.png deleted file mode 100644 index 6f296333dd56a4..00000000000000 Binary files a/assets/eip-7444/equation.png and /dev/null differ diff --git a/assets/eip-7484/check-sequence.jpg b/assets/eip-7484/check-sequence.jpg deleted file mode 100644 index 061257992d8f6f..00000000000000 Binary files a/assets/eip-7484/check-sequence.jpg and /dev/null differ diff --git a/assets/eip-7507/contracts/ERC7507.sol b/assets/eip-7507/contracts/ERC7507.sol deleted file mode 100644 index eb1abfac78367b..00000000000000 --- a/assets/eip-7507/contracts/ERC7507.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -import "./IERC7507.sol"; - -contract ERC7507 is ERC721, IERC7507 { - - mapping(uint256 => mapping(address => uint64)) private _expires; - - constructor( - string memory name, string memory symbol - ) ERC721(name, symbol) {} - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override returns (bool) { - return interfaceId == type(IERC7507).interfaceId || super.supportsInterface(interfaceId); - } - - function userExpires( - uint256 tokenId, address user - ) public view virtual override returns(uint256) { - require(_exists(tokenId), "ERC7507: query for nonexistent token"); - return _expires[tokenId][user]; - } - - function setUser( - uint256 tokenId, address user, uint64 expires - ) public virtual override { - require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC7507: caller is not owner or approved"); - _expires[tokenId][user] = expires; - emit UpdateUser(tokenId, user, expires); - } - - // For test only - function mint( - address to, uint256 tokenId - ) public virtual { - _mint(to, tokenId); - } - -} diff --git a/assets/eip-7507/contracts/IERC7507.sol b/assets/eip-7507/contracts/IERC7507.sol deleted file mode 100644 index 99e06f7f737761..00000000000000 --- a/assets/eip-7507/contracts/IERC7507.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -pragma solidity ^0.8.0; - -interface IERC7507 { - - /// @notice Emitted when the expires of a user for an NFT is changed - event UpdateUser(uint256 indexed tokenId, address indexed user, uint64 expires); - - /// @notice Get the user expires of an NFT - /// @param tokenId The NFT to get the user expires for - /// @param user The user to get the expires for - /// @return The user expires for this NFT - function userExpires(uint256 tokenId, address user) external view returns(uint256); - - /// @notice Set the user expires of an NFT - /// @param tokenId The NFT to set the user expires for - /// @param user The user to set the expires for - /// @param expires The user could use the NFT before expires in UNIX timestamp - function setUser(uint256 tokenId, address user, uint64 expires) external; - -} diff --git a/assets/eip-7507/hardhat.config.ts b/assets/eip-7507/hardhat.config.ts deleted file mode 100644 index e47e09fab862ac..00000000000000 --- a/assets/eip-7507/hardhat.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.21", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-7507/package.json b/assets/eip-7507/package.json deleted file mode 100644 index 8b8b91f1aea545..00000000000000 --- a/assets/eip-7507/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "eip-7507", - "version": "1.0.0", - "description": "Reference implementation and test cases for EIP-7507", - "author": "Comoco Labs", - "license": "CC0-1.0", - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^2.0.2", - "@nomicfoundation/hardhat-ethers": "^3.0.4", - "@nomicfoundation/hardhat-network-helpers": "^1.0.9", - "@nomicfoundation/hardhat-toolbox": "^3.0.0", - "@nomicfoundation/hardhat-verify": "^1.1.1", - "@typechain/ethers-v6": "^0.4.3", - "@typechain/hardhat": "^8.0.3", - "@types/chai": "^4.3.5", - "@types/mocha": "^10.0.1", - "@types/node": "^20.5.9", - "chai": "^4.3.8", - "ethers": "^6.7.1", - "hardhat": "^2.17.2", - "hardhat-gas-reporter": "^1.0.9", - "solidity-coverage": "^0.8.4", - "ts-node": "^10.9.1", - "typechain": "^8.3.1", - "typescript": "^5.2.2" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.9.3" - } -} diff --git a/assets/eip-7507/test/ERC7507.test.ts b/assets/eip-7507/test/ERC7507.test.ts deleted file mode 100644 index f86a69129ca9a7..00000000000000 --- a/assets/eip-7507/test/ERC7507.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"; -import { expect } from "chai"; -import { ethers } from "hardhat"; - -const NAME = "NAME"; -const SYMBOL = "SYMBOL"; -const TOKEN_ID = 1234; -const EXPIRATION = 2000000000; -const YEAR = 31536000; - -describe("ERC7507", function () { - - async function deployContractFixture() { - const [deployer, owner, user1, user2] = await ethers.getSigners(); - - const contract = await ethers.deployContract("ERC7507", [NAME, SYMBOL], deployer); - await contract.mint(owner, TOKEN_ID); - - return { contract, owner, user1, user2 }; - } - - describe("Functions", function () { - it("Should not set user if not owner or approved", async function () { - const { contract, user1 } = await loadFixture(deployContractFixture); - - await expect(contract.setUser(TOKEN_ID, user1, EXPIRATION)) - .to.be.revertedWith("ERC7507: caller is not owner or approved"); - }); - - it("Should return zero expiration for nonexistent user", async function () { - const { contract, user1 } = await loadFixture(deployContractFixture); - - expect(await contract.userExpires(TOKEN_ID, user1)).to.equal(0); - }); - - it("Should set users and then update", async function () { - const { contract, owner, user1, user2 } = await loadFixture(deployContractFixture); - - await contract.connect(owner).setUser(TOKEN_ID, user1, EXPIRATION); - await contract.connect(owner).setUser(TOKEN_ID, user2, EXPIRATION); - - expect(await contract.userExpires(TOKEN_ID, user1)).to.equal(EXPIRATION); - expect(await contract.userExpires(TOKEN_ID, user2)).to.equal(EXPIRATION); - - await contract.connect(owner).setUser(TOKEN_ID, user1, EXPIRATION + YEAR); - await contract.connect(owner).setUser(TOKEN_ID, user2, 0); - - expect(await contract.userExpires(TOKEN_ID, user1)).to.equal(EXPIRATION + YEAR); - expect(await contract.userExpires(TOKEN_ID, user2)).to.equal(0); - }); - }); - - describe("Events", function () { - it("Should emit event when set user", async function () { - const { contract, owner, user1 } = await loadFixture(deployContractFixture); - - await expect(contract.connect(owner).setUser(TOKEN_ID, user1, EXPIRATION)) - .to.emit(contract, "UpdateUser").withArgs(TOKEN_ID, user1.address, EXPIRATION); - }); - }); - -}); diff --git a/assets/eip-7507/tsconfig.json b/assets/eip-7507/tsconfig.json deleted file mode 100644 index 574e785c71ecaf..00000000000000 --- a/assets/eip-7507/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "commonjs", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "resolveJsonModule": true - } -} diff --git a/assets/eip-7508/contracts/AttributesRepository.sol b/assets/eip-7508/contracts/AttributesRepository.sol deleted file mode 100644 index dcefe8e2819bb8..00000000000000 --- a/assets/eip-7508/contracts/AttributesRepository.sol +++ /dev/null @@ -1,1223 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.21; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -import "./IERC7508.sol"; - -/** - * @title ERC-7508 Public On-Chain NFT Attributes Repository - * @author Steven Pineda, Jan Turk - * @notice Implementation smart contract of the ERC-7508 Public On-Chain NFT Attributes Repository - */ -contract AttributesRepository is IERC7508 { - bytes32 public immutable DOMAIN_SEPARATOR = - keccak256( - abi.encode( - "ERC-7508: Public On-Chain NFT Attributes Repository", - "1", - block.chainid, - address(this) - ) - ); - bytes32 public immutable SET_UINT_ATTRIBUTE_TYPEHASH = - keccak256( - "setUintAttribute(address collection,uint256 tokenId,string memory key,uint256 value)" - ); - bytes32 public immutable SET_STRING_ATTRIBUTE_TYPEHASH = - keccak256( - "setStringAttribute(address collection,uint256 tokenId,string memory key,string memory value)" - ); - bytes32 public immutable SET_BOOL_ATTRIBUTE_TYPEHASH = - keccak256( - "setBoolAttribute(address collection,uint256 tokenId,string memory key,bool value)" - ); - bytes32 public immutable SET_BYTES_ATTRIBUTE_TYPEHASH = - keccak256( - "setBytesAttribute(address collection,uint256 tokenId,string memory key,bytes memory value)" - ); - bytes32 public immutable SET_ADDRESS_ATTRIBUTE_TYPEHASH = - keccak256( - "setAddressAttribute(address collection,uint256 tokenId,string memory key,address value)" - ); - - mapping(address collection => mapping(uint256 keyId => AccessType accessType)) - private _parameterAccessType; - mapping(address collection => mapping(uint256 keyId => address specificAddress)) - private _parameterSpecificAddress; - mapping(address collection => IssuerSetting issuerSetting) private _issuerSettings; - mapping(address collection => mapping(address collaborator => bool isCollaborator)) private _collaborators; - - // For keys, we use a mapping from strings to IDs. - // The purpose is to store unique string keys only once, since they are more expensive. - mapping(string key => uint256 keyId) private _keysToIds; - uint256 private _totalAttributes; - - // For strings, we also use a mapping from strings to IDs, together with a reverse mapping - // The purpose is to store unique string values only once, since they are more expensive, - // and storing only IDs. - mapping(address collection => uint256 numberOfStringValues) private _totalStringValues; - mapping(address collection => mapping(string stringValue => uint256 stringId)) private _stringValueToId; - mapping(address collection => mapping(uint256 stringId => string stringValue)) private _stringIdToValue; - mapping(address collection => mapping(uint256 tokenId => mapping(uint256 stringKeyId => uint256 stringValueId))) - private _stringValueIds; - - mapping(address collection => mapping(uint256 tokenId => mapping(uint256 addressKeyId => address addressValue))) - private _addressValues; - mapping(address collection => mapping(uint256 tokenId => mapping(uint256 bytesKeyId => bytes bytesValue))) - private _bytesValues; - mapping(address collection => mapping(uint256 tokenId => mapping(uint256 uintKeyId => uint256 uintValue))) - private _uintValues; - mapping(address collection => mapping(uint256 tokenId => mapping(uint256 boolKeyId => bool boolValue))) - private _boolValues; - - struct IssuerSetting { - bool registered; - bool useOwnable; - address issuer; - } - - /// Used to signal that the smart contract interacting with the repository does not implement Ownable pattern. - error OwnableNotImplemented(); - /// Used to signal that the caller is not the issuer of the collection. - error NotCollectionIssuer(); - /// Used to signal that the collaborator and collaborator rights array are not of equal length. - error CollaboratorArraysNotEqualLength(); - /// Used to signal that the collection is not registered in the repository yet. - error CollectionNotRegistered(); - /// Used to signal that the collection is already registered in the repository. - error CollectionAlreadyRegistered(); - /// Used to signal that the caller is not aa collaborator of the collection. - error NotCollectionCollaborator(); - /// Used to signal that the caller is not the issuer or a collaborator of the collection. - error NotCollectionIssuerOrCollaborator(); - /// Used to signal that the caller is not the owner of the token. - error NotTokenOwner(); - /// Used to signal that the caller is not the specific address allowed to manage the attribute. - error NotSpecificAddress(); - /// Used to signal that the presigned message's signature is invalid. - error InvalidSignature(); - /// Used to signal that the presigned message's deadline has expired. - error ExpiredDeadline(); - - /** - * @inheritdoc IERC7508 - */ - function registerAccessControl( - address collection, - address issuer, - bool useOwnable - ) external onlyUnregisteredCollection(collection) { - (bool ownableSuccess, bytes memory ownableReturn) = collection.call( - abi.encodeWithSignature("owner()") - ); - - if (address(uint160(uint256(bytes32(ownableReturn)))) == address(0)) { - revert OwnableNotImplemented(); - } - if ( - ownableSuccess && - address(uint160(uint256(bytes32(ownableReturn)))) != msg.sender - ) { - revert NotCollectionIssuer(); - } - - IssuerSetting storage issuerSetting = _issuerSettings[collection]; - issuerSetting.registered = true; - issuerSetting.issuer = issuer; - issuerSetting.useOwnable = useOwnable; - - emit AccessControlRegistration( - collection, - issuer, - msg.sender, - useOwnable - ); - } - - /** - * @inheritdoc IERC7508 - */ - function manageAccessControl( - address collection, - string memory key, - AccessType accessType, - address specificAddress - ) external onlyRegisteredCollection(collection) onlyIssuer(collection) { - uint256 parameterId = _getIdForKey(key); - - _parameterAccessType[collection][parameterId] = accessType; - _parameterSpecificAddress[collection][parameterId] = specificAddress; - - emit AccessControlUpdate(collection, key, accessType, specificAddress); - } - - /** - * @inheritdoc IERC7508 - */ - function manageCollaborators( - address collection, - address[] memory collaboratorAddresses, - bool[] memory collaboratorAddressAccess - ) external onlyRegisteredCollection(collection) onlyIssuer(collection) { - if (collaboratorAddresses.length != collaboratorAddressAccess.length) { - revert CollaboratorArraysNotEqualLength(); - } - for (uint256 i; i < collaboratorAddresses.length; ) { - _collaborators[collection][ - collaboratorAddresses[i] - ] = collaboratorAddressAccess[i]; - emit CollaboratorUpdate( - collection, - collaboratorAddresses[i], - collaboratorAddressAccess[i] - ); - unchecked { - ++i; - } - } - } - - /** - * @inheritdoc IERC7508 - */ - function isCollaborator( - address collaborator, - address collection - ) external view returns (bool) { - return _collaborators[collection][collaborator]; - } - - /** - * @inheritdoc IERC7508 - */ - function isSpecificAddress( - address specificAddress, - address collection, - string memory key - ) external view returns (bool) { - return - _parameterSpecificAddress[collection][_keysToIds[key]] == - specificAddress; - } - - /** - * @notice Modifier to check if the caller is authorized to call the function. - * @dev If the authorization is set to TokenOwner and the tokenId provided is of the non-existent token, the - * execution will revert with `ERC721InvalidTokenId` rather than `NotTokenOwner`. - * @dev The tokenId parameter is only needed for the TokenOwner authorization type, other authorization types ignore - * it. - * @param collection The address of the collection. - * @param key Key of the attribute. - * @param tokenId The ID of the token. - */ - modifier onlyAuthorizedCaller( - address collection, - string memory key, - uint256 tokenId - ) { - _onlyAuthorizedCaller(msg.sender, collection, key, tokenId); - _; - } - - /** - * @notice Modifier to check if the collection is registered. - * @param collection Address of the collection. - */ - modifier onlyRegisteredCollection(address collection) { - if (!_issuerSettings[collection].registered) { - revert CollectionNotRegistered(); - } - _; - } - - /** - * @notice Modifier to check if the collection is not registered. - * @param collection Address of the collection. - */ - modifier onlyUnregisteredCollection(address collection) { - if (_issuerSettings[collection].registered) { - revert CollectionAlreadyRegistered(); - } - _; - } - - /** - * @notice Modifier to check if the caller is the issuer of the collection. - * @param collection Address of the collection. - */ - modifier onlyIssuer(address collection) { - if (_issuerSettings[collection].useOwnable) { - if (Ownable(collection).owner() != msg.sender) { - revert NotCollectionIssuer(); - } - } else if (_issuerSettings[collection].issuer != msg.sender) { - revert NotCollectionIssuer(); - } - _; - } - - /** - * @notice Function to check if the caller is authorized to mamage a given parameter. - * @param collection The address of the collection. - * @param key Key of the attribute. - * @param tokenId The ID of the token. - */ - function _onlyAuthorizedCaller( - address caller, - address collection, - string memory key, - uint256 tokenId - ) private view { - AccessType accessType = _parameterAccessType[collection][ - _keysToIds[key] - ]; - - if ( - accessType == AccessType.Issuer && - ((_issuerSettings[collection].useOwnable && - Ownable(collection).owner() != caller) || - (!_issuerSettings[collection].useOwnable && - _issuerSettings[collection].issuer != caller)) - ) { - revert NotCollectionIssuer(); - } else if ( - accessType == AccessType.Collaborator && - !_collaborators[collection][caller] - ) { - revert NotCollectionCollaborator(); - } else if ( - accessType == AccessType.IssuerOrCollaborator && - ((_issuerSettings[collection].useOwnable && - Ownable(collection).owner() != caller) || - (!_issuerSettings[collection].useOwnable && - _issuerSettings[collection].issuer != caller)) && - !_collaborators[collection][caller] - ) { - revert NotCollectionIssuerOrCollaborator(); - } else if ( - accessType == AccessType.TokenOwner && - IERC721(collection).ownerOf(tokenId) != caller - ) { - revert NotTokenOwner(); - } else if ( - accessType == AccessType.SpecificAddress && - !(_parameterSpecificAddress[collection][_keysToIds[key]] == caller) - ) { - revert NotSpecificAddress(); - } - } - - /** - * @inheritdoc IERC7508 - */ - function getStringTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (string memory) { - uint256 idForValue = _stringValueIds[collection][tokenId][ - _keysToIds[key] - ]; - return _stringIdToValue[collection][idForValue]; - } - - /** - * @inheritdoc IERC7508 - */ - function getUintTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (uint256) { - return _uintValues[collection][tokenId][_keysToIds[key]]; - } - - /** - * @inheritdoc IERC7508 - */ - function getBoolTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (bool) { - return _boolValues[collection][tokenId][_keysToIds[key]]; - } - - /** - * @inheritdoc IERC7508 - */ - function getAddressTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (address) { - return _addressValues[collection][tokenId][_keysToIds[key]]; - } - - /** - * @inheritdoc IERC7508 - */ - function getBytesTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (bytes memory) { - return _bytesValues[collection][tokenId][_keysToIds[key]]; - } - - /** - * @inheritdoc IERC7508 - */ - function getTokenAttributes( - address collection, - uint256 tokenId, - string[] memory stringKeys, - string[] memory uintKeys, - string[] memory boolKeys, - string[] memory addressKeys, - string[] memory bytesKeys - ) - external - view - returns ( - StringAttribute[] memory stringAttributes, - UintAttribute[] memory uintAttributes, - BoolAttribute[] memory boolAttributes, - AddressAttribute[] memory addressAttributes, - BytesAttribute[] memory bytesAttributes - ) - { - stringAttributes = getStringTokenAttributes( - collection, - tokenId, - stringKeys - ); - - uintAttributes = getUintTokenAttributes(collection, tokenId, uintKeys); - - boolAttributes = getBoolTokenAttributes(collection, tokenId, boolKeys); - - addressAttributes = getAddressTokenAttributes( - collection, - tokenId, - addressKeys - ); - - bytesAttributes = getBytesTokenAttributes( - collection, - tokenId, - bytesKeys - ); - } - - /** - * @inheritdoc IERC7508 - */ - function getStringTokenAttributes( - address collection, - uint256 tokenId, - string[] memory stringKeys - ) public view returns (StringAttribute[] memory) { - uint256 stringLen = stringKeys.length; - - StringAttribute[] memory stringAttributes = new StringAttribute[]( - stringLen - ); - - for (uint i; i < stringLen; ) { - stringAttributes[i] = StringAttribute({ - key: stringKeys[i], - value: _stringIdToValue[collection][ - _stringValueIds[collection][tokenId][ - _keysToIds[stringKeys[i]] - ] - ] - }); - unchecked { - ++i; - } - } - - return stringAttributes; - } - - /** - * @inheritdoc IERC7508 - */ - function getUintTokenAttributes( - address collection, - uint256 tokenId, - string[] memory uintKeys - ) public view returns (UintAttribute[] memory) { - uint256 uintLen = uintKeys.length; - - UintAttribute[] memory uintAttributes = new UintAttribute[](uintLen); - - for (uint i; i < uintLen; ) { - uintAttributes[i] = UintAttribute({ - key: uintKeys[i], - value: _uintValues[collection][tokenId][_keysToIds[uintKeys[i]]] - }); - unchecked { - ++i; - } - } - - return uintAttributes; - } - - /** - * @inheritdoc IERC7508 - */ - function getBoolTokenAttributes( - address collection, - uint256 tokenId, - string[] memory boolKeys - ) public view returns (BoolAttribute[] memory) { - uint256 boolLen = boolKeys.length; - - BoolAttribute[] memory boolAttributes = new BoolAttribute[](boolLen); - - for (uint i; i < boolLen; ) { - boolAttributes[i] = BoolAttribute({ - key: boolKeys[i], - value: _boolValues[collection][tokenId][_keysToIds[boolKeys[i]]] - }); - unchecked { - ++i; - } - } - - return boolAttributes; - } - - /** - * @inheritdoc IERC7508 - */ - function getAddressTokenAttributes( - address collection, - uint256 tokenId, - string[] memory addressKeys - ) public view returns (AddressAttribute[] memory) { - uint256 addressLen = addressKeys.length; - - AddressAttribute[] memory addressAttributes = new AddressAttribute[]( - addressLen - ); - - for (uint i; i < addressLen; ) { - addressAttributes[i] = AddressAttribute({ - key: addressKeys[i], - value: _addressValues[collection][tokenId][ - _keysToIds[addressKeys[i]] - ] - }); - unchecked { - ++i; - } - } - - return addressAttributes; - } - - /** - * @inheritdoc IERC7508 - */ - function getBytesTokenAttributes( - address collection, - uint256 tokenId, - string[] memory bytesKeys - ) public view returns (BytesAttribute[] memory) { - uint256 bytesLen = bytesKeys.length; - - BytesAttribute[] memory bytesAttributes = new BytesAttribute[](bytesLen); - - for (uint i; i < bytesLen; ) { - bytesAttributes[i] = BytesAttribute({ - key: bytesKeys[i], - value: _bytesValues[collection][tokenId][ - _keysToIds[bytesKeys[i]] - ] - }); - unchecked { - ++i; - } - } - - return bytesAttributes; - } - - /** - * @inheritdoc IERC7508 - */ - function prepareMessageToPresignUintAttribute( - address collection, - uint256 tokenId, - string memory key, - uint256 value, - uint256 deadline - ) public view returns (bytes32) { - return - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_UINT_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ); - } - - /** - * @inheritdoc IERC7508 - */ - function prepareMessageToPresignStringAttribute( - address collection, - uint256 tokenId, - string memory key, - string memory value, - uint256 deadline - ) public view returns (bytes32) { - return - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_STRING_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ); - } - - /** - * @inheritdoc IERC7508 - */ - function prepareMessageToPresignBoolAttribute( - address collection, - uint256 tokenId, - string memory key, - bool value, - uint256 deadline - ) public view returns (bytes32) { - return - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_BOOL_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ); - } - - /** - * @inheritdoc IERC7508 - */ - function prepareMessageToPresignBytesAttribute( - address collection, - uint256 tokenId, - string memory key, - bytes memory value, - uint256 deadline - ) public view returns (bytes32) { - return - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_BYTES_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ); - } - - /** - * @inheritdoc IERC7508 - */ - function prepareMessageToPresignAddressAttribute( - address collection, - uint256 tokenId, - string memory key, - address value, - uint256 deadline - ) public view returns (bytes32) { - return - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_ADDRESS_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ); - } - - /** - * @inheritdoc IERC7508 - */ - function setUintAttribute( - address collection, - uint256 tokenId, - string memory key, - uint256 value - ) external onlyAuthorizedCaller(collection, key, tokenId) { - _uintValues[collection][tokenId][_getIdForKey(key)] = value; - emit UintAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function setStringAttribute( - address collection, - uint256 tokenId, - string memory key, - string memory value - ) external onlyAuthorizedCaller(collection, key, tokenId) { - _stringValueIds[collection][tokenId][ - _getIdForKey(key) - ] = _getStringIdForValue(collection, value); - emit StringAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function setBoolAttribute( - address collection, - uint256 tokenId, - string memory key, - bool value - ) external onlyAuthorizedCaller(collection, key, tokenId) { - _boolValues[collection][tokenId][_getIdForKey(key)] = value; - emit BoolAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function setBytesAttribute( - address collection, - uint256 tokenId, - string memory key, - bytes memory value - ) external onlyAuthorizedCaller(collection, key, tokenId) { - _bytesValues[collection][tokenId][_getIdForKey(key)] = value; - emit BytesAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function setAddressAttribute( - address collection, - uint256 tokenId, - string memory key, - address value - ) external onlyAuthorizedCaller(collection, key, tokenId) { - _addressValues[collection][tokenId][_getIdForKey(key)] = value; - emit AddressAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function setStringAttributes( - address collection, - uint256 tokenId, - StringAttribute[] memory attributes - ) external onlyAuthorizedCaller(collection, "", tokenId) { - for (uint256 i = 0; i < attributes.length; ) { - _stringValueIds[collection][tokenId][ - _getIdForKey(attributes[i].key) - ] = _getStringIdForValue(collection, attributes[i].value); - emit StringAttributeUpdated( - collection, - tokenId, - attributes[i].key, - attributes[i].value - ); - unchecked { - ++i; - } - } - } - - /** - * @inheritdoc IERC7508 - */ - function setUintAttributes( - address collection, - uint256 tokenId, - UintAttribute[] memory attributes - ) external onlyAuthorizedCaller(collection, "", tokenId) { - for (uint256 i = 0; i < attributes.length; ) { - _uintValues[collection][tokenId][ - _getIdForKey(attributes[i].key) - ] = attributes[i].value; - emit UintAttributeUpdated( - collection, - tokenId, - attributes[i].key, - attributes[i].value - ); - unchecked { - ++i; - } - } - } - - /** - * @inheritdoc IERC7508 - */ - function setBoolAttributes( - address collection, - uint256 tokenId, - BoolAttribute[] memory attributes - ) external onlyAuthorizedCaller(collection, "", tokenId) { - for (uint256 i = 0; i < attributes.length; ) { - _boolValues[collection][tokenId][ - _getIdForKey(attributes[i].key) - ] = attributes[i].value; - emit BoolAttributeUpdated( - collection, - tokenId, - attributes[i].key, - attributes[i].value - ); - unchecked { - ++i; - } - } - } - - /** - * @inheritdoc IERC7508 - */ - function setAddressAttributes( - address collection, - uint256 tokenId, - AddressAttribute[] memory attributes - ) external onlyAuthorizedCaller(collection, "", tokenId) { - for (uint256 i = 0; i < attributes.length; ) { - _addressValues[collection][tokenId][ - _getIdForKey(attributes[i].key) - ] = attributes[i].value; - emit AddressAttributeUpdated( - collection, - tokenId, - attributes[i].key, - attributes[i].value - ); - unchecked { - ++i; - } - } - } - - /** - * @inheritdoc IERC7508 - */ - function setBytesAttributes( - address collection, - uint256 tokenId, - BytesAttribute[] memory attributes - ) external onlyAuthorizedCaller(collection, "", tokenId) { - for (uint256 i = 0; i < attributes.length; ) { - _bytesValues[collection][tokenId][ - _getIdForKey(attributes[i].key) - ] = attributes[i].value; - emit BytesAttributeUpdated( - collection, - tokenId, - attributes[i].key, - attributes[i].value - ); - unchecked { - ++i; - } - } - } - - /** - * @inheritdoc IERC7508 - */ - function setTokenAttributes( - address collection, - uint256 tokenId, - StringAttribute[] memory stringAttributes, - UintAttribute[] memory uintAttributes, - BoolAttribute[] memory boolAttributes, - AddressAttribute[] memory addressAttributes, - BytesAttribute[] memory bytesAttributes - ) external onlyAuthorizedCaller(collection, "", tokenId) { - for (uint256 i = 0; i < stringAttributes.length; ) { - _stringValueIds[collection][tokenId][ - _getIdForKey(stringAttributes[i].key) - ] = _getStringIdForValue(collection, stringAttributes[i].value); - emit StringAttributeUpdated( - collection, - tokenId, - stringAttributes[i].key, - stringAttributes[i].value - ); - unchecked { - ++i; - } - } - - for (uint256 i = 0; i < uintAttributes.length; ) { - _uintValues[collection][tokenId][ - _getIdForKey(uintAttributes[i].key) - ] = uintAttributes[i].value; - emit UintAttributeUpdated( - collection, - tokenId, - uintAttributes[i].key, - uintAttributes[i].value - ); - unchecked { - ++i; - } - } - - for (uint256 i = 0; i < boolAttributes.length; ) { - _boolValues[collection][tokenId][ - _getIdForKey(boolAttributes[i].key) - ] = boolAttributes[i].value; - emit BoolAttributeUpdated( - collection, - tokenId, - boolAttributes[i].key, - boolAttributes[i].value - ); - unchecked { - ++i; - } - } - - for (uint256 i = 0; i < addressAttributes.length; ) { - _addressValues[collection][tokenId][ - _getIdForKey(addressAttributes[i].key) - ] = addressAttributes[i].value; - emit AddressAttributeUpdated( - collection, - tokenId, - addressAttributes[i].key, - addressAttributes[i].value - ); - unchecked { - ++i; - } - } - - for (uint256 i = 0; i < bytesAttributes.length; ) { - _bytesValues[collection][tokenId][ - _getIdForKey(bytesAttributes[i].key) - ] = bytesAttributes[i].value; - emit BytesAttributeUpdated( - collection, - tokenId, - bytesAttributes[i].key, - bytesAttributes[i].value - ); - unchecked { - ++i; - } - } - } - - /** - * @inheritdoc IERC7508 - */ - function presignedSetUintAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - if (block.timestamp > deadline) { - revert ExpiredDeadline(); - } - - bytes32 digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_UINT_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ) - ) - ); - address signer = ecrecover(digest, v, r, s); - if (signer != setter) { - revert InvalidSignature(); - } - _onlyAuthorizedCaller(signer, collection, key, tokenId); - - _uintValues[collection][tokenId][_getIdForKey(key)] = value; - emit UintAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function presignedSetStringAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - string memory value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - if (block.timestamp > deadline) { - revert ExpiredDeadline(); - } - - bytes32 digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_STRING_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ) - ) - ); - address signer = ecrecover(digest, v, r, s); - if (signer != setter) { - revert InvalidSignature(); - } - _onlyAuthorizedCaller(signer, collection, key, tokenId); - - _stringValueIds[collection][tokenId][ - _getIdForKey(key) - ] = _getStringIdForValue(collection, value); - emit StringAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function presignedSetBoolAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - bool value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - if (block.timestamp > deadline) { - revert ExpiredDeadline(); - } - - bytes32 digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_BOOL_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ) - ) - ); - address signer = ecrecover(digest, v, r, s); - if (signer != setter) { - revert InvalidSignature(); - } - _onlyAuthorizedCaller(signer, collection, key, tokenId); - - _boolValues[collection][tokenId][_getIdForKey(key)] = value; - emit BoolAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function presignedSetBytesAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - bytes memory value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - if (block.timestamp > deadline) { - revert ExpiredDeadline(); - } - - bytes32 digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_BYTES_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ) - ) - ); - address signer = ecrecover(digest, v, r, s); - if (signer != setter) { - revert InvalidSignature(); - } - _onlyAuthorizedCaller(signer, collection, key, tokenId); - - _bytesValues[collection][tokenId][_getIdForKey(key)] = value; - emit BytesAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @inheritdoc IERC7508 - */ - function presignedSetAddressAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - address value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - if (block.timestamp > deadline) { - revert ExpiredDeadline(); - } - - bytes32 digest = keccak256( - abi.encodePacked( - "\x19Ethereum Signed Message:\n32", - keccak256( - abi.encode( - DOMAIN_SEPARATOR, - SET_ADDRESS_ATTRIBUTE_TYPEHASH, - collection, - tokenId, - key, - value, - deadline - ) - ) - ) - ); - address signer = ecrecover(digest, v, r, s); - if (signer != setter) { - revert InvalidSignature(); - } - _onlyAuthorizedCaller(signer, collection, key, tokenId); - - _addressValues[collection][tokenId][_getIdForKey(key)] = value; - emit AddressAttributeUpdated(collection, tokenId, key, value); - } - - /** - * @notice Used to get the Id for a key. If the key does not exist, a new ID is created. - * IDs are shared among all tokens and types - * @dev The ID of 0 is not used as it represents the default value. - * @param key The attribute key - * @return The ID of the key - */ - function _getIdForKey(string memory key) internal returns (uint256) { - if (_keysToIds[key] == 0) { - _totalAttributes++; - _keysToIds[key] = _totalAttributes; - return _totalAttributes; - } else { - return _keysToIds[key]; - } - } - - /** - * @notice Used to get the ID for a string value. If the value does not exist, a new ID is created. - * @dev IDs are shared among all tokens and used only for strings. - * @param collection Address of the collection being checked for string ID - * @param value The attribute value - * @return The id for the string value - */ - function _getStringIdForValue( - address collection, - string memory value - ) internal returns (uint256) { - if (_stringValueToId[collection][value] == 0) { - _totalStringValues[collection]++; - _stringValueToId[collection][value] = _totalStringValues[ - collection - ]; - _stringIdToValue[collection][ - _totalStringValues[collection] - ] = value; - return _totalStringValues[collection]; - } else { - return _stringValueToId[collection][value]; - } - } - - /** - * @inheritdoc IERC165 - */ - function supportsInterface( - bytes4 interfaceId - ) public view virtual returns (bool) { - return - interfaceId == type(IERC7508).interfaceId || - interfaceId == type(IERC165).interfaceId; - } -} diff --git a/assets/eip-7508/contracts/IERC7508.sol b/assets/eip-7508/contracts/IERC7508.sol deleted file mode 100644 index 469605991163c1..00000000000000 --- a/assets/eip-7508/contracts/IERC7508.sol +++ /dev/null @@ -1,865 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.21; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; - -/** - * @title ERC-7508 Public On-Chain NFT Attributes Repository - * @author Steven Pineda, Jan Turk - * @notice Interface smart contract of Dynamic On-Chain Token Attributes Repository - */ -interface IERC7508 is IERC165 { - /** - * @notice A list of supported access types. - * @return The `Issuer` type, where only the issuer can manage the parameter. - * @return The `Collaborator` type, where only the collaborators can manage the parameter. - * @return The `IssuerOrCollaborator` type, where only the issuer or collaborators can manage the parameter. - * @return The `TokenOwner` type, where only the token owner can manage the parameters of their tokens. - * @return The `SpecificAddress` type, where only specific addresses can manage the parameter. - */ - enum AccessType { - Issuer, - Collaborator, - IssuerOrCollaborator, - TokenOwner, - SpecificAddress - } - - /** - * @notice Structure used to represent a string attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct StringAttribute { - string key; - string value; - } - - /** - * @notice Structure used to represent an uint attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct UintAttribute { - string key; - uint256 value; - } - - /** - * @notice Structure used to represent a boolean attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct BoolAttribute { - string key; - bool value; - } - - /** - * @notice Structure used to represent an address attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct AddressAttribute { - string key; - address value; - } - - /** - * @notice Structure used to represent a bytes attribute. - * @return key The key of the attribute - * @return value The value of the attribute - */ - struct BytesAttribute { - string key; - bytes value; - } - - /** - * @notice Used to notify listeners that a new collection has been registered to use the repository. - * @param collection Address of the collection - * @param issuer Address of the issuer of the collection; the addess authorized to manage the access control - * @param registeringAddress Address that registered the collection - * @param useOwnable A boolean value indicating whether the collection uses the Ownable extension to verify the - * issuer (`true`) or not (`false`) - */ - event AccessControlRegistration( - address indexed collection, - address indexed issuer, - address indexed registeringAddress, - bool useOwnable - ); - - /** - * @notice Used to notify listeners that the access control settings for a specific parameter have been updated. - * @param collection Address of the collection - * @param key The name of the parameter for which the access control settings have been updated - * @param accessType The AccessType of the parameter for which the access control settings have been updated - * @param specificAddress The specific addresses that has been updated - */ - event AccessControlUpdate( - address indexed collection, - string key, - AccessType accessType, - address specificAddress - ); - - /** - * @notice Used to notify listeners that a new collaborator has been added or removed. - * @param collection Address of the collection - * @param collaborator Address of the collaborator - * @param isCollaborator A boolean value indicating whether the collaborator has been added (`true`) or removed - * (`false`) - */ - event CollaboratorUpdate( - address indexed collection, - address indexed collaborator, - bool isCollaborator - ); - - /** - * @notice Used to notify listeners that a string attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event StringAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - string value - ); - - /** - * @notice Used to notify listeners that an uint attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event UintAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - uint256 value - ); - - /** - * @notice Used to notify listeners that a boolean attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event BoolAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - bool value - ); - - /** - * @notice Used to notify listeners that an address attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event AddressAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - address value - ); - - /** - * @notice Used to notify listeners that a bytes attribute has been updated. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @param value The new value of the attribute - */ - event BytesAttributeUpdated( - address indexed collection, - uint256 indexed tokenId, - string key, - bytes value - ); - - /** - * @notice Used to register a collection to use the RMRK token attributes repository. - * @dev If the collection does not implement the Ownable interface, the `useOwnable` value must be set to `false`. - * @dev Emits an {AccessControlRegistration} event. - * @param collection The address of the collection that will use the RMRK token attributes repository. - * @param issuer The address of the issuer of the collection. - * @param useOwnable The boolean value to indicate if the collection implements the Ownable interface and whether it - * should be used to validate that the caller is the issuer (`true`) or to use the manually set issuer address - * (`false`). - */ - function registerAccessControl( - address collection, - address issuer, - bool useOwnable - ) external; - - /** - * @notice Used to manage the access control settings for a specific parameter. - * @dev Only the `issuer` of the collection can call this function. - * @dev The possible `accessType` values are: - * [ - * Issuer, - * Collaborator, - * IssuerOrCollaborator, - * TokenOwner, - * SpecificAddress, - * ] - * @dev Emits an {AccessControlUpdated} event. - * @param collection The address of the collection being managed. - * @param key The key of the attribute - * @param accessType The type of access control to be applied to the parameter. - * @param specificAddress The address to be added as a specific addresses allowed to manage the given - * parameter. - */ - function manageAccessControl( - address collection, - string memory key, - AccessType accessType, - address specificAddress - ) external; - - /** - * @notice Used to manage the collaborators of a collection. - * @dev The `collaboratorAddresses` and `collaboratorAddressAccess` arrays must be of the same length. - * @dev Emits a {CollaboratorUpdate} event. - * @param collection The address of the collection - * @param collaboratorAddresses The array of collaborator addresses being managed - * @param collaboratorAddressAccess The array of boolean values indicating if the collaborator address should - * receive the permission (`true`) or not (`false`). - */ - function manageCollaborators( - address collection, - address[] memory collaboratorAddresses, - bool[] memory collaboratorAddressAccess - ) external; - - /** - * @notice Used to set a number attribute. - * @dev Emits a {UintAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setUintAttribute( - address collection, - uint256 tokenId, - string memory key, - uint256 value - ) external; - - /** - * @notice Used to set a string attribute. - * @dev Emits a {StringAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setStringAttribute( - address collection, - uint256 tokenId, - string memory key, - string memory value - ) external; - - /** - * @notice Used to set a boolean attribute. - * @dev Emits a {BoolAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setBoolAttribute( - address collection, - uint256 tokenId, - string memory key, - bool value - ) external; - - /** - * @notice Used to set an bytes attribute. - * @dev Emits a {BytesAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setBytesAttribute( - address collection, - uint256 tokenId, - string memory key, - bytes memory value - ) external; - - /** - * @notice Used to set an address attribute. - * @dev Emits a {AddressAttributeUpdated} event. - * @param collection Address of the collection receiving the attribute - * @param tokenId The token ID - * @param key The attribute key - * @param value The attribute value - */ - function setAddressAttribute( - address collection, - uint256 tokenId, - string memory key, - address value - ) external; - - /** - * @notice Sets multiple string attributes for a token at once. - * @dev The `StringAttribute` struct contains the following fields: - * [ - * string key, - * string value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `StringAttribute` structs to be assigned to the given token - */ - function setStringAttributes( - address collection, - uint256 tokenId, - StringAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple uint attributes for a token at once. - * @dev The `UintAttribute` struct contains the following fields: - * [ - * string key, - * uint value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `UintAttribute` structs to be assigned to the given token - */ - function setUintAttributes( - address collection, - uint256 tokenId, - UintAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple bool attributes for a token at once. - * @dev The `BoolAttribute` struct contains the following fields: - * [ - * string key, - * bool value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `BoolAttribute` structs to be assigned to the given token - */ - function setBoolAttributes( - address collection, - uint256 tokenId, - BoolAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple address attributes for a token at once. - * @dev The `AddressAttribute` struct contains the following fields: - * [ - * string key, - * address value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `AddressAttribute` structs to be assigned to the given token - */ - function setAddressAttributes( - address collection, - uint256 tokenId, - AddressAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple bytes attributes for a token at once. - * @dev The `BytesAttribute` struct contains the following fields: - * [ - * string key, - * bytes value - * ] - * @param collection Address of the collection - * @param tokenId ID of the token - * @param attributes An array of `BytesAttribute` structs to be assigned to the given token - */ - function setBytesAttributes( - address collection, - uint256 tokenId, - BytesAttribute[] memory attributes - ) external; - - /** - * @notice Sets multiple attributes of multiple types for a token at the same time. - * @dev Emits a separate event for each attribute set. - * @dev The `StringAttribute`, `UintAttribute`, `BoolAttribute`, `AddressAttribute` and `BytesAttribute` structs consists - * to the following fields (where `value` is of the appropriate type): - * [ - * key, - * value, - * ] - * @param collection The address of the collection - * @param tokenId The token ID - * @param stringAttributes An array of `StringAttribute` structs containing string attributes to set - * @param uintAttributes An array of `UintAttribute` structs containing uint attributes to set - * @param boolAttributes An array of `BoolAttribute` structs containing bool attributes to set - * @param addressAttributes An array of `AddressAttribute` structs containing address attributes to set - * @param bytesAttributes An array of `BytesAttribute` structs containing bytes attributes to set - */ - function setTokenAttributes( - address collection, - uint256 tokenId, - StringAttribute[] memory stringAttributes, - UintAttribute[] memory uintAttributes, - BoolAttribute[] memory boolAttributes, - AddressAttribute[] memory addressAttributes, - BytesAttribute[] memory bytesAttributes - ) external; - - /** - * @notice Used to set the uint attribute on behalf of an authorized account. - * @dev Emits a {UintAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetUintAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the string attribute on behalf of an authorized account. - * @dev Emits a {StringAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetStringAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - string memory value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the bool attribute on behalf of an authorized account. - * @dev Emits a {BoolAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetBoolAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - bool value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the bytes attribute on behalf of an authorized account. - * @dev Emits a {BytesAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetBytesAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - bytes memory value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to set the address attribute on behalf of an authorized account. - * @dev Emits a {AddressAttributeUpdated} event. - * @param setter Address of the account that presigned the attribute change - * @param collection Address of the collection receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction - * @param v `v` value of an ECDSA signature of the presigned message - * @param r `r` value of an ECDSA signature of the presigned message - * @param s `s` value of an ECDSA signature of the presigned message - */ - function presignedSetAddressAttribute( - address setter, - address collection, - uint256 tokenId, - string memory key, - address value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external; - - /** - * @notice Used to check if the specified address is listed as a collaborator of the given collection's parameter. - * @param collaborator Address to be checked. - * @param collection Address of the collection. - * @return Boolean value indicating if the address is a collaborator of the given collection's (`true`) or not - * (`false`). - */ - function isCollaborator( - address collaborator, - address collection - ) external view returns (bool); - - /** - * @notice Used to check if the specified address is listed as a specific address of the given collection's - * parameter. - * @param specificAddress Address to be checked. - * @param collection Address of the collection. - * @param key The key of the attribute - * @return Boolean value indicating if the address is a specific address of the given collection's parameter - * (`true`) or not (`false`). - */ - function isSpecificAddress( - address specificAddress, - address collection, - string memory key - ) external view returns (bool); - - /** - * @notice Used to retrieve the string type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the string attribute - */ - function getStringTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (string memory); - - /** - * @notice Used to retrieve the uint type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the uint attribute - */ - function getUintTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (uint256); - - /** - * @notice Used to retrieve the bool type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the bool attribute - */ - function getBoolTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (bool); - - /** - * @notice Used to retrieve the address type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the address attribute - */ - function getAddressTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (address); - - /** - * @notice Used to retrieve the bytes type token attributes. - * @param collection The collection address - * @param tokenId The token ID - * @param key The key of the attribute - * @return The value of the bytes attribute - */ - function getBytesTokenAttribute( - address collection, - uint256 tokenId, - string memory key - ) external view returns (bytes memory); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned uint attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignUintAttribute( - address collection, - uint256 tokenId, - string memory key, - uint256 value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned string attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignStringAttribute( - address collection, - uint256 tokenId, - string memory key, - string memory value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned bool attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignBoolAttribute( - address collection, - uint256 tokenId, - string memory key, - bool value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned bytes attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignBytesAttribute( - address collection, - uint256 tokenId, - string memory key, - bytes memory value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve the message to be signed for submitting a presigned address attribute change. - * @param collection The address of the collection smart contract of the token receiving the attribute - * @param tokenId The ID of the token receiving the attribute - * @param key The attribute key - * @param value The attribute value - * @param deadline The deadline timestamp for the presigned transaction after which the message is invalid - * @return Raw message to be signed by the authorized account - */ - function prepareMessageToPresignAddressAttribute( - address collection, - uint256 tokenId, - string memory key, - address value, - uint256 deadline - ) external view returns (bytes32); - - /** - * @notice Used to retrieve multiple token attributes of any type at once. - * @dev The `StringAttribute`, `UintAttribute`, `BoolAttribute`, `AddressAttribute` and `BytesAttribute` structs consists - * to the following fields (where `value` is of the appropriate type): - * [ - * key, - * value, - * ] - * @param collection The collection address - * @param tokenId The token ID - * @param stringKeys An array of string type attribute keys to retrieve - * @param uintKeys An array of uint type attribute keys to retrieve - * @param boolKeys An array of bool type attribute keys to retrieve - * @param addressKeys An array of address type attribute keys to retrieve - * @param bytesKeys An array of bytes type attribute keys to retrieve - * @return stringAttributes An array of `StringAttribute` structs containing the string type attributes - * @return uintAttributes An array of `UintAttribute` structs containing the uint type attributes - * @return boolAttributes An array of `BoolAttribute` structs containing the bool type attributes - * @return addressAttributes An array of `AddressAttribute` structs containing the address type attributes - * @return bytesAttributes An array of `BytesAttribute` structs containing the bytes type attributes - */ - function getTokenAttributes( - address collection, - uint256 tokenId, - string[] memory stringKeys, - string[] memory uintKeys, - string[] memory boolKeys, - string[] memory addressKeys, - string[] memory bytesKeys - ) - external - view - returns ( - StringAttribute[] memory stringAttributes, - UintAttribute[] memory uintAttributes, - BoolAttribute[] memory boolAttributes, - AddressAttribute[] memory addressAttributes, - BytesAttribute[] memory bytesAttributes - ); - - /** - * @notice Used to get multiple sting parameter values for a token. - * @dev The `StringAttribute` struct contains the following fields: - * [ - * string key, - * string value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param stringKeys An array of string keys to retrieve - * @return An array of `StringAttribute` structs - */ - function getStringTokenAttributes( - address collection, - uint256 tokenId, - string[] memory stringKeys - ) external view returns (StringAttribute[] memory); - - /** - * @notice Used to get multiple uint parameter values for a token. - * @dev The `UintAttribute` struct contains the following fields: - * [ - * string key, - * uint value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param uintKeys An array of uint keys to retrieve - * @return An array of `UintAttribute` structs - */ - function getUintTokenAttributes( - address collection, - uint256 tokenId, - string[] memory uintKeys - ) external view returns (UintAttribute[] memory); - - /** - * @notice Used to get multiple bool parameter values for a token. - * @dev The `BoolAttribute` struct contains the following fields: - * [ - * string key, - * bool value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param boolKeys An array of bool keys to retrieve - * @return An array of `BoolAttribute` structs - */ - function getBoolTokenAttributes( - address collection, - uint256 tokenId, - string[] memory boolKeys - ) external view returns (BoolAttribute[] memory); - - /** - * @notice Used to get multiple address parameter values for a token. - * @dev The `AddressAttribute` struct contains the following fields: - * [ - * string key, - * address value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param addressKeys An array of address keys to retrieve - * @return An array of `AddressAttribute` structs - */ - function getAddressTokenAttributes( - address collection, - uint256 tokenId, - string[] memory addressKeys - ) external view returns (AddressAttribute[] memory); - - /** - * @notice Used to get multiple bytes parameter values for a token. - * @dev The `BytesAttribute` struct contains the following fields: - * [ - * string key, - * bytes value - * ] - * @param collection Address of the collection the token belongs to - * @param tokenId ID of the token for which the attributes are being retrieved - * @param bytesKeys An array of bytes keys to retrieve - * @return An array of `BytesAttribute` structs - */ - function getBytesTokenAttributes( - address collection, - uint256 tokenId, - string[] memory bytesKeys - ) external view returns (BytesAttribute[] memory); -} diff --git a/assets/eip-7508/contracts/mocks/OwnableMintableERC721Mock.sol b/assets/eip-7508/contracts/mocks/OwnableMintableERC721Mock.sol deleted file mode 100644 index 8bc53c33b333b9..00000000000000 --- a/assets/eip-7508/contracts/mocks/OwnableMintableERC721Mock.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.21; - -/// @dev This mock smart contract is intended to be used with `@defi-wonderland/smock` and doesn't need any business -/// logic. -contract OwnableMintableERC721Mock { - function owner() public returns (address) {} - - function ownerOf(uint256 tokenId) public returns (address) {} -} diff --git a/assets/eip-7508/hardhat.config.ts b/assets/eip-7508/hardhat.config.ts deleted file mode 100644 index da1c607c0f27b4..00000000000000 --- a/assets/eip-7508/hardhat.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-chai-matchers"; -import "@typechain/hardhat"; - -const config: HardhatUserConfig = { - solidity: { - version: "0.8.21", - settings: { - optimizer: { - enabled: true, - runs: 200, - }, - }, - }, -}; - -export default config; diff --git a/assets/eip-7508/package.json b/assets/eip-7508/package.json deleted file mode 100644 index 1a6eaafe63a1ca..00000000000000 --- a/assets/eip-7508/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "eip-7508", - "scripts": { - "test": "yarn typechain && hardhat test", - "typechain": "hardhat typechain", - "prettier": "prettier --write ." - }, - "engines": { - "node": ">=16.0.0" - }, - "dependencies": { - "@openzeppelin/contracts": "^4.6.0" - }, - "devDependencies": { - "@defi-wonderland/smock": "^2.3.5", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.1", - "@nomicfoundation/hardhat-network-helpers": "^1.0.3", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@typechain/ethers-v5": "^10.1.0", - "@typechain/hardhat": "^6.1.2", - "@types/chai": "^4.3.1", - "chai": "^4.3.6", - "ethers": "^5.6.9", - "hardhat": "^2.12.2", - "ts-node": "^10.8.2", - "typechain": "^8.1.0", - "typescript": "^4.7.4", - "web3-utils": "^4.0.5" - } -} diff --git a/assets/eip-7508/test/attributesRepository.ts b/assets/eip-7508/test/attributesRepository.ts deleted file mode 100644 index 06a28ee141216d..00000000000000 --- a/assets/eip-7508/test/attributesRepository.ts +++ /dev/null @@ -1,2015 +0,0 @@ -import { ethers } from "hardhat"; -import { expect } from "chai"; -import { loadFixture, mine } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { - OwnableMintableERC721Mock, - AttributesRepository, -} from "../../typechain-types"; -import { BigNumber } from "ethers"; -import { smock, FakeContract } from "@defi-wonderland/smock"; - -const IERC165 = "0x01ffc9a7"; -const IAttributesRepository = "0x07cd44c7"; - -// --------------- FIXTURES ----------------------- - -async function tokenAttributesFixture() { - const factory = await ethers.getContractFactory("AttributesRepository"); - const tokenAttributes = await factory.deploy(); - await tokenAttributes.deployed(); - - return { tokenAttributes }; -} - -async function ownedCollectionFixture() { - const ownedCollection = await smock.fake( - "OwnableMintableERC721Mock" - ); - - return { ownedCollection }; -} - -// --------------- TESTS ----------------------- - -describe("AttributesRepository", async function () { - let tokenAttributes: AttributesRepository; - let ownedCollection: FakeContract; - - beforeEach(async function () { - ({ tokenAttributes } = await loadFixture(tokenAttributesFixture)); - ({ ownedCollection } = await loadFixture(ownedCollectionFixture)); - - this.tokenAttributes = tokenAttributes; - this.ownedCollection = ownedCollection; - }); - - shouldBehaveLikeTokenAttributesRepositoryInterface(); - - describe("AttributesRepository", async function () { - let issuer: SignerWithAddress; - let owner: SignerWithAddress; - const tokenId = 1; - const tokenId2 = 2; - - beforeEach(async function () { - ({ tokenAttributes } = await loadFixture(tokenAttributesFixture)); - ({ ownedCollection } = await loadFixture(ownedCollectionFixture)); - - const signers = await ethers.getSigners(); - issuer = signers[0]; - owner = signers[1]; - - ownedCollection.owner.returns(issuer.address); - - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - }); - - it("can set and get token attributes", async function () { - expect( - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId, - "description", - "test description" - ) - ) - .to.emit(tokenAttributes, "StringAttributeSet") - .withArgs( - ownedCollection.address, - tokenId, - "description", - "test description" - ); - expect( - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId, - "description1", - "test description" - ) - ) - .to.emit(tokenAttributes, "StringAttributeSet") - .withArgs( - ownedCollection.address, - tokenId, - "description1", - "test description" - ); - expect( - await tokenAttributes.setBoolAttribute( - ownedCollection.address, - tokenId, - "rare", - true - ) - ) - .to.emit(tokenAttributes, "BoolAttributeSet") - .withArgs(ownedCollection.address, tokenId, "rare", true); - expect( - await tokenAttributes.setAddressAttribute( - ownedCollection.address, - tokenId, - "owner", - owner.address - ) - ) - .to.emit(tokenAttributes, "AddressAttributeSet") - .withArgs(ownedCollection.address, tokenId, "owner", owner.address); - expect( - await tokenAttributes.setUintAttribute( - ownedCollection.address, - tokenId, - "atk", - BigNumber.from(100) - ) - ) - .to.emit(tokenAttributes, "UintAttributeSet") - .withArgs(ownedCollection.address, tokenId, "atk", BigNumber.from(100)); - expect( - await tokenAttributes.setUintAttribute( - ownedCollection.address, - tokenId, - "health", - BigNumber.from(100) - ) - ) - .to.emit(tokenAttributes, "UintAttributeSet") - .withArgs( - ownedCollection.address, - tokenId, - "health", - BigNumber.from(100) - ); - expect( - await tokenAttributes.setUintAttribute( - ownedCollection.address, - tokenId, - "health", - BigNumber.from(95) - ) - ) - .to.emit(tokenAttributes, "UintAttributeSet") - .withArgs( - ownedCollection.address, - tokenId, - "health", - BigNumber.from(95) - ); - expect( - await tokenAttributes.setUintAttribute( - ownedCollection.address, - tokenId, - "health", - BigNumber.from(80) - ) - ) - .to.emit(tokenAttributes, "UintAttributeSet") - .withArgs( - ownedCollection.address, - tokenId, - "health", - BigNumber.from(80) - ); - expect( - await tokenAttributes.setBytesAttribute( - ownedCollection.address, - tokenId, - "data", - "0x1234" - ) - ) - .to.emit(tokenAttributes, "BytesAttributeSet") - .withArgs(ownedCollection.address, tokenId, "data", "0x1234"); - - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId, - "description" - ) - ).to.eql("test description"); - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId, - "description1" - ) - ).to.eql("test description"); - expect( - await tokenAttributes.getBoolTokenAttribute( - ownedCollection.address, - tokenId, - "rare" - ) - ).to.eql(true); - expect( - await tokenAttributes.getAddressTokenAttribute( - ownedCollection.address, - tokenId, - "owner" - ) - ).to.eql(owner.address); - expect( - await tokenAttributes.getUintTokenAttribute( - ownedCollection.address, - tokenId, - "atk" - ) - ).to.eql(BigNumber.from(100)); - expect( - await tokenAttributes.getUintTokenAttribute( - ownedCollection.address, - tokenId, - "health" - ) - ).to.eql(BigNumber.from(80)); - expect( - await tokenAttributes.getBytesTokenAttribute( - ownedCollection.address, - tokenId, - "data" - ) - ).to.eql("0x1234"); - - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId, - "description", - "test description update" - ); - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId, - "description" - ) - ).to.eql("test description update"); - }); - - it("can set multiple attributes of multiple types at the same time", async function () { - await expect( - tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [ - { key: "string1", value: "value1" }, - { key: "string2", value: "value2" }, - ], - [ - { key: "uint1", value: BigNumber.from(1) }, - { key: "uint2", value: BigNumber.from(2) }, - ], - [ - { key: "bool1", value: true }, - { key: "bool2", value: false }, - ], - [ - { key: "address1", value: owner.address }, - { key: "address2", value: issuer.address }, - ], - [ - { key: "bytes1", value: "0x1234" }, - { key: "bytes2", value: "0x5678" }, - ] - ) - ) - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string1", "value1") - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string2", "value2") - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint1", BigNumber.from(1)) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint2", BigNumber.from(2)) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool1", true) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool2", false) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address1", owner.address) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address2", issuer.address) - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes1", "0x1234") - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes2", "0x5678"); - }); - - it("can update multiple attributes of multiple types at the same time", async function () { - await tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [ - { key: "string1", value: "value0" }, - { key: "string2", value: "value1" }, - ], - [ - { key: "uint1", value: BigNumber.from(0) }, - { key: "uint2", value: BigNumber.from(1) }, - ], - [ - { key: "bool1", value: false }, - { key: "bool2", value: true }, - ], - [ - { key: "address1", value: issuer.address }, - { key: "address2", value: owner.address }, - ], - [ - { key: "bytes1", value: "0x5678" }, - { key: "bytes2", value: "0x1234" }, - ] - ); - - await expect( - tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [ - { key: "string1", value: "value1" }, - { key: "string2", value: "value2" }, - ], - [ - { key: "uint1", value: BigNumber.from(1) }, - { key: "uint2", value: BigNumber.from(2) }, - ], - [ - { key: "bool1", value: true }, - { key: "bool2", value: false }, - ], - [ - { key: "address1", value: owner.address }, - { key: "address2", value: issuer.address }, - ], - [ - { key: "bytes1", value: "0x1234" }, - { key: "bytes2", value: "0x5678" }, - ] - ) - ) - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string1", "value1") - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string2", "value2") - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint1", BigNumber.from(1)) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint2", BigNumber.from(2)) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool1", true) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool2", false) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address1", owner.address) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address2", issuer.address) - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes1", "0x1234") - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes2", "0x5678"); - }); - - it("can set and update multiple attributes of multiple types at the same time even if not all types are updated at the same time", async function () { - await tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [{ key: "string1", value: "value0" }], - [ - { key: "uint1", value: BigNumber.from(0) }, - { key: "uint2", value: BigNumber.from(1) }, - ], - [ - { key: "bool1", value: false }, - { key: "bool2", value: true }, - ], - [ - { key: "address1", value: issuer.address }, - { key: "address2", value: owner.address }, - ], - [] - ); - - await expect( - tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [], - [ - { key: "uint1", value: BigNumber.from(1) }, - { key: "uint2", value: BigNumber.from(2) }, - ], - [ - { key: "bool1", value: true }, - { key: "bool2", value: false }, - ], - [ - { key: "address1", value: owner.address }, - { key: "address2", value: issuer.address }, - ], - [ - { key: "bytes1", value: "0x1234" }, - { key: "bytes2", value: "0x5678" }, - ] - ) - ) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint1", BigNumber.from(1)) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint2", BigNumber.from(2)) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool1", true) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool2", false) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address1", owner.address) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address2", issuer.address) - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes1", "0x1234") - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes2", "0x5678"); - - await expect( - tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [], - [], - [ - { key: "bool1", value: false }, - { key: "bool2", value: true }, - ], - [], - [] - ) - ) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool1", false) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool2", true); - }); - - it("can set and update multiple attributes of multiple types at the same time", async function () { - await expect( - tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [ - { key: "string1", value: "value1" }, - { key: "string2", value: "value2" }, - ], - [ - { key: "uint1", value: BigNumber.from(1) }, - { key: "uint2", value: BigNumber.from(2) }, - ], - [ - { key: "bool1", value: true }, - { key: "bool2", value: false }, - ], - [ - { key: "address1", value: owner.address }, - { key: "address2", value: issuer.address }, - ], - [ - { key: "bytes1", value: "0x1234" }, - { key: "bytes2", value: "0x5678" }, - ] - ) - ) - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string1", "value1") - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string2", "value2") - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint1", BigNumber.from(1)) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint2", BigNumber.from(2)) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool1", true) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool2", false) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address1", owner.address) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address2", issuer.address) - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes1", "0x1234") - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes2", "0x5678"); - }); - - it("should allow to retrieve multiple attributes at once", async function () { - await tokenAttributes.setTokenAttributes( - ownedCollection.address, - tokenId, - [ - { key: "string1", value: "value1" }, - { key: "string2", value: "value2" }, - ], - [ - { key: "uint1", value: BigNumber.from(1) }, - { key: "uint2", value: BigNumber.from(2) }, - ], - [ - { key: "bool1", value: true }, - { key: "bool2", value: false }, - ], - [ - { key: "address1", value: owner.address }, - { key: "address2", value: issuer.address }, - ], - [ - { key: "bytes1", value: "0x1234" }, - { key: "bytes2", value: "0x5678" }, - ] - ); - - expect( - await tokenAttributes.getTokenAttributes( - ownedCollection.address, - tokenId, - ["string1", "string2"], - ["uint1", "uint2"], - ["bool1", "bool2"], - ["address1", "address2"], - ["bytes1", "bytes2"] - ) - ).to.eql([ - [ - ["string1", "value1"], - ["string2", "value2"], - ], - [ - ["uint1", BigNumber.from(1)], - ["uint2", BigNumber.from(2)], - ], - [ - ["bool1", true], - ["bool2", false], - ], - [ - ["address1", owner.address], - ["address2", issuer.address], - ], - [ - ["bytes1", "0x1234"], - ["bytes2", "0x5678"], - ], - ]); - }); - - it("can set multiple string attributes at the same time", async function () { - await expect( - tokenAttributes.setStringAttributes(ownedCollection.address, tokenId, [ - { key: "string1", value: "value1" }, - { key: "string2", value: "value2" }, - ]) - ) - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string1", "value1") - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "string2", "value2"); - - expect( - await tokenAttributes.getTokenAttributes( - ownedCollection.address, - tokenId, - ["string1", "string2"], - [], - [], - [], - [] - ) - ).to.eql([ - [ - ["string1", "value1"], - ["string2", "value2"], - ], - [], - [], - [], - [], - ]); - }); - - it("can set multiple uint attributes at the same time", async function () { - await expect( - tokenAttributes.setUintAttributes(ownedCollection.address, tokenId, [ - { key: "uint1", value: BigNumber.from(1) }, - { key: "uint2", value: BigNumber.from(2) }, - ]) - ) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint1", BigNumber.from(1)) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "uint2", BigNumber.from(2)); - - expect( - await tokenAttributes.getTokenAttributes( - ownedCollection.address, - tokenId, - [], - ["uint1", "uint2"], - [], - [], - [] - ) - ).to.eql([ - [], - [ - ["uint1", BigNumber.from(1)], - ["uint2", BigNumber.from(2)], - ], - [], - [], - [], - ]); - }); - - it("can set multiple bool attributes at the same time", async function () { - await expect( - tokenAttributes.setBoolAttributes(ownedCollection.address, tokenId, [ - { key: "bool1", value: true }, - { key: "bool2", value: false }, - ]) - ) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool1", true) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bool2", false); - - expect( - await tokenAttributes.getTokenAttributes( - ownedCollection.address, - tokenId, - [], - [], - ["bool1", "bool2"], - [], - [] - ) - ).to.eql([ - [], - [], - [ - ["bool1", true], - ["bool2", false], - ], - [], - [], - ]); - }); - - it("can set multiple address attributes at the same time", async function () { - await expect( - tokenAttributes.setAddressAttributes(ownedCollection.address, tokenId, [ - { key: "address1", value: owner.address }, - { key: "address2", value: issuer.address }, - ]) - ) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address1", owner.address) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "address2", issuer.address); - - expect( - await tokenAttributes.getTokenAttributes( - ownedCollection.address, - tokenId, - [], - [], - [], - ["address1", "address2"], - [] - ) - ).to.eql([ - [], - [], - [], - [ - ["address1", owner.address], - ["address2", issuer.address], - ], - [], - ]); - }); - - it("can set multiple bytes attributes at the same time", async function () { - await expect( - tokenAttributes.setBytesAttributes(ownedCollection.address, tokenId, [ - { key: "bytes1", value: "0x1234" }, - { key: "bytes2", value: "0x5678" }, - ]) - ) - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes1", "0x1234") - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, tokenId, "bytes2", "0x5678"); - - expect( - await tokenAttributes.getTokenAttributes( - ownedCollection.address, - tokenId, - [], - [], - [], - [], - ["bytes1", "bytes2"] - ) - ).to.eql([ - [], - [], - [], - [], - [ - ["bytes1", "0x1234"], - ["bytes2", "0x5678"], - ], - ]); - }); - - it("can reuse keys and values are fine", async function () { - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId, - "X", - "X1" - ); - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId2, - "X", - "X2" - ); - - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId, - "X" - ) - ).to.eql("X1"); - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId2, - "X" - ) - ).to.eql("X2"); - }); - - it("can reuse keys among different attributes and values are fine", async function () { - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId, - "X", - "test description" - ); - await tokenAttributes.setBoolAttribute( - ownedCollection.address, - tokenId, - "X", - true - ); - await tokenAttributes.setAddressAttribute( - ownedCollection.address, - tokenId, - "X", - owner.address - ); - await tokenAttributes.setUintAttribute( - ownedCollection.address, - tokenId, - "X", - BigNumber.from(100) - ); - await tokenAttributes.setBytesAttribute( - ownedCollection.address, - tokenId, - "X", - "0x1234" - ); - - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId, - "X" - ) - ).to.eql("test description"); - expect( - await tokenAttributes.getBoolTokenAttribute( - ownedCollection.address, - tokenId, - "X" - ) - ).to.eql(true); - expect( - await tokenAttributes.getAddressTokenAttribute( - ownedCollection.address, - tokenId, - "X" - ) - ).to.eql(owner.address); - expect( - await tokenAttributes.getUintTokenAttribute( - ownedCollection.address, - tokenId, - "X" - ) - ).to.eql(BigNumber.from(100)); - expect( - await tokenAttributes.getBytesTokenAttribute( - ownedCollection.address, - tokenId, - "X" - ) - ).to.eql("0x1234"); - }); - - it("can reuse string values and values are fine", async function () { - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId, - "X", - "common string" - ); - await tokenAttributes.setStringAttribute( - ownedCollection.address, - tokenId2, - "X", - "common string" - ); - - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId, - "X" - ) - ).to.eql("common string"); - expect( - await tokenAttributes.getStringTokenAttribute( - ownedCollection.address, - tokenId2, - "X" - ) - ).to.eql("common string"); - }); - - it("should not allow to set string values to unauthorized caller", async function () { - await expect( - tokenAttributes - .connect(owner) - .setStringAttribute( - ownedCollection.address, - tokenId, - "X", - "test description" - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should not allow to set uint values to unauthorized caller", async function () { - await expect( - tokenAttributes - .connect(owner) - .setUintAttribute( - ownedCollection.address, - tokenId, - "X", - BigNumber.from(42) - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should not allow to set boolean values to unauthorized caller", async function () { - await expect( - tokenAttributes - .connect(owner) - .setBoolAttribute(ownedCollection.address, tokenId, "X", true) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should not allow to set address values to unauthorized caller", async function () { - await expect( - tokenAttributes - .connect(owner) - .setAddressAttribute( - ownedCollection.address, - tokenId, - "X", - owner.address - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should not allow to set bytes values to unauthorized caller", async function () { - await expect( - tokenAttributes - .connect(owner) - .setBytesAttribute(ownedCollection.address, tokenId, "X", "0x1234") - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - }); - - describe("Token attributes access control", async function () { - let issuer: SignerWithAddress; - let owner: SignerWithAddress; - const tokenId = 1; - const tokenId2 = 2; - - beforeEach(async function () { - ({ tokenAttributes } = await loadFixture(tokenAttributesFixture)); - ({ ownedCollection } = await loadFixture(ownedCollectionFixture)); - - const signers = await ethers.getSigners(); - issuer = signers[0]; - owner = signers[1]; - - ownedCollection.owner.returns(issuer.address); - }); - - it("should not allow registering an already registered collection", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - await expect( - tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ) - ).to.be.revertedWithCustomError( - tokenAttributes, - "CollectionAlreadyRegistered" - ); - }); - - it("should not allow to register a collection if caller is not the owner of the collection", async function () { - await expect( - tokenAttributes - .connect(owner) - .registerAccessControl(ownedCollection.address, issuer.address, true) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should not allow to register a collection without Ownable implemented", async function () { - ownedCollection.owner.reset(); - - await expect( - tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ) - ).to.be.revertedWithCustomError(tokenAttributes, "OwnableNotImplemented"); - }); - - it("should allow to manage access control for registered collections", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - expect( - await tokenAttributes - .connect(issuer) - .manageAccessControl(ownedCollection.address, "X", 2, owner.address) - ) - .to.emit(tokenAttributes, "AccessControlUpdate") - .withArgs(ownedCollection.address, "X", 2, owner); - }); - - it("should allow issuer to manage collaborators", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - expect( - await tokenAttributes - .connect(issuer) - .manageCollaborators(ownedCollection.address, [owner.address], [true]) - ) - .to.emit(tokenAttributes, "CollaboratorUpdate") - .withArgs(ownedCollection.address, [owner.address], [true]); - }); - - it("should not allow to manage collaborators of an unregistered collection", async function () { - await expect( - tokenAttributes - .connect(issuer) - .manageCollaborators(ownedCollection.address, [owner.address], [true]) - ).to.be.revertedWithCustomError( - tokenAttributes, - "CollectionNotRegistered" - ); - }); - - it("should not allow to manage collaborators if the caller is not the issuer", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - await expect( - tokenAttributes - .connect(owner) - .manageCollaborators(ownedCollection.address, [owner.address], [true]) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should not allow to manage collaborators for registered collections if collaborator arrays are not of equal length", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - await expect( - tokenAttributes - .connect(issuer) - .manageCollaborators( - ownedCollection.address, - [owner.address, issuer.address], - [true] - ) - ).to.be.revertedWithCustomError( - tokenAttributes, - "CollaboratorArraysNotEqualLength" - ); - }); - - it("should not allow to manage access control for unregistered collections", async function () { - await expect( - tokenAttributes - .connect(issuer) - .manageAccessControl(ownedCollection.address, "X", 2, owner.address) - ).to.be.revertedWithCustomError( - tokenAttributes, - "CollectionNotRegistered" - ); - }); - - it("should not allow to manage access control if the caller is not issuer", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - await expect( - tokenAttributes - .connect(owner) - .manageAccessControl(ownedCollection.address, "X", 2, owner.address) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should not allow to manage access control if the caller is not returned as collection owner when using ownable", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - true - ); - - await expect( - tokenAttributes - .connect(owner) - .manageAccessControl(ownedCollection.address, "X", 2, owner.address) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should return the expected value when checking for collaborators", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - expect( - await tokenAttributes.isCollaborator( - owner.address, - ownedCollection.address - ) - ).to.be.false; - - await tokenAttributes - .connect(issuer) - .manageCollaborators(ownedCollection.address, [owner.address], [true]); - - expect( - await tokenAttributes.isCollaborator( - owner.address, - ownedCollection.address - ) - ).to.be.true; - }); - - it("should return the expected value when checking for specific addresses", async function () { - await tokenAttributes.registerAccessControl( - ownedCollection.address, - issuer.address, - false - ); - - expect( - await tokenAttributes.isSpecificAddress( - owner.address, - ownedCollection.address, - "X" - ) - ).to.be.false; - - await tokenAttributes - .connect(issuer) - .manageAccessControl(ownedCollection.address, "X", 2, owner.address); - - expect( - await tokenAttributes.isSpecificAddress( - owner.address, - ownedCollection.address, - "X" - ) - ).to.be.true; - }); - - it("should use the issuer returned from the collection when using only issuer when only issuer is allowed to manage parameter", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, true); - - await tokenAttributes - .connect(issuer) - .manageAccessControl( - ownedCollection.address, - "X", - 0, - ethers.constants.AddressZero - ); - - await expect( - tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - - ownedCollection.owner.returns(owner.address); - - await expect( - tokenAttributes - .connect(issuer) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - - it("should only allow collaborator to modify the parameters if only collaborator is allowed to modify them", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, false); - - await tokenAttributes - .connect(issuer) - .manageAccessControl( - ownedCollection.address, - "X", - 1, - ethers.constants.AddressZero - ); - - await tokenAttributes - .connect(issuer) - .manageCollaborators(ownedCollection.address, [owner.address], [true]); - - await tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address); - - await expect( - tokenAttributes - .connect(issuer) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError( - tokenAttributes, - "NotCollectionCollaborator" - ); - }); - - it("should only allow issuer and collaborator to modify the parameters if only issuer and collaborator is allowed to modify them", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, false); - - await tokenAttributes - .connect(issuer) - .manageAccessControl( - ownedCollection.address, - "X", - 2, - ethers.constants.AddressZero - ); - - await tokenAttributes - .connect(issuer) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address); - - await expect( - tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError( - tokenAttributes, - "NotCollectionIssuerOrCollaborator" - ); - - await tokenAttributes - .connect(issuer) - .manageCollaborators(ownedCollection.address, [owner.address], [true]); - - await tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address); - }); - - it("should only allow issuer and collaborator to modify the parameters if only issuer and collaborator is allowed to modify them even when using the ownable", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, true); - - await tokenAttributes - .connect(issuer) - .manageAccessControl( - ownedCollection.address, - "X", - 2, - ethers.constants.AddressZero - ); - - ownedCollection.owner.returns(owner.address); - - await tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address); - - await expect( - tokenAttributes - .connect(issuer) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError( - tokenAttributes, - "NotCollectionIssuerOrCollaborator" - ); - - await tokenAttributes - .connect(owner) - .manageCollaborators(ownedCollection.address, [issuer.address], [true]); - - await tokenAttributes - .connect(issuer) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address); - }); - - it("should only allow token owner to modify the parameters if only token owner is allowed to modify them", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, false); - - await tokenAttributes - .connect(issuer) - .manageAccessControl( - ownedCollection.address, - "X", - 3, - ethers.constants.AddressZero - ); - - await expect( - tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError(tokenAttributes, "NotTokenOwner"); - - ownedCollection.ownerOf.returns(owner.address); - - await tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address); - - await expect( - tokenAttributes - .connect(issuer) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError(tokenAttributes, "NotTokenOwner"); - }); - - it("should only allow specific address to modify the parameters if only specific address is allowed to modify them", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, false); - - await tokenAttributes - .connect(issuer) - .manageAccessControl( - ownedCollection.address, - "X", - 4, - ethers.constants.AddressZero - ); - - await expect( - tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address) - ).to.be.revertedWithCustomError(tokenAttributes, "NotSpecificAddress"); - - await tokenAttributes - .connect(issuer) - .manageAccessControl(ownedCollection.address, "X", 4, owner.address); - - await tokenAttributes - .connect(owner) - .setAddressAttribute(ownedCollection.address, 1, "X", owner.address); - }); - - it("should allow to use presigned message to modify the parameters", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, false); - - const uintMessage = - await tokenAttributes.prepareMessageToPresignUintAttribute( - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(9999999999) - ); - const stringMessage = - await tokenAttributes.prepareMessageToPresignStringAttribute( - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(9999999999) - ); - const boolMessage = - await tokenAttributes.prepareMessageToPresignBoolAttribute( - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(9999999999) - ); - const bytesMessage = - await tokenAttributes.prepareMessageToPresignBytesAttribute( - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(9999999999) - ); - const addressMessage = - await tokenAttributes.prepareMessageToPresignAddressAttribute( - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(9999999999) - ); - - const uintSignature = await issuer.signMessage( - ethers.utils.arrayify(uintMessage) - ); - const stringSignature = await issuer.signMessage( - ethers.utils.arrayify(stringMessage) - ); - const boolSignature = await issuer.signMessage( - ethers.utils.arrayify(boolMessage) - ); - const bytesSignature = await issuer.signMessage( - ethers.utils.arrayify(bytesMessage) - ); - const addressSignature = await issuer.signMessage( - ethers.utils.arrayify(addressMessage) - ); - - const uintR: string = uintSignature.substring(0, 66); - const uintS: string = "0x" + uintSignature.substring(66, 130); - const uintV: string = parseInt(uintSignature.substring(130, 132), 16); - - const stringR: string = stringSignature.substring(0, 66); - const stringS: string = "0x" + stringSignature.substring(66, 130); - const stringV: string = parseInt(stringSignature.substring(130, 132), 16); - - const boolR: string = boolSignature.substring(0, 66); - const boolS: string = "0x" + boolSignature.substring(66, 130); - const boolV: string = parseInt(boolSignature.substring(130, 132), 16); - - const bytesR: string = bytesSignature.substring(0, 66); - const bytesS: string = "0x" + bytesSignature.substring(66, 130); - const bytesV: string = parseInt(bytesSignature.substring(130, 132), 16); - - const addressR: string = addressSignature.substring(0, 66); - const addressS: string = "0x" + addressSignature.substring(66, 130); - const addressV: string = parseInt( - addressSignature.substring(130, 132), - 16 - ); - - await expect( - tokenAttributes - .connect(owner) - .presignedSetUintAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(9999999999), - uintV, - uintR, - uintS - ) - ) - .to.emit(tokenAttributes, "UintAttributeUpdated") - .withArgs(ownedCollection.address, 1, "X", 1); - await expect( - tokenAttributes - .connect(owner) - .presignedSetStringAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(9999999999), - stringV, - stringR, - stringS - ) - ) - .to.emit(tokenAttributes, "StringAttributeUpdated") - .withArgs(ownedCollection.address, 1, "X", "test"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBoolAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(9999999999), - boolV, - boolR, - boolS - ) - ) - .to.emit(tokenAttributes, "BoolAttributeUpdated") - .withArgs(ownedCollection.address, 1, "X", true); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBytesAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(9999999999), - bytesV, - bytesR, - bytesS - ) - ) - .to.emit(tokenAttributes, "BytesAttributeUpdated") - .withArgs(ownedCollection.address, 1, "X", "0x1234"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetAddressAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(9999999999), - addressV, - addressR, - addressS - ) - ) - .to.emit(tokenAttributes, "AddressAttributeUpdated") - .withArgs(ownedCollection.address, 1, "X", owner.address); - }); - - it("should not allow to use presigned message to modify the parameters if the deadline has elapsed", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, false); - - await mine(1000, { interval: 15 }); - - const uintMessage = - await tokenAttributes.prepareMessageToPresignUintAttribute( - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(10) - ); - const stringMessage = - await tokenAttributes.prepareMessageToPresignStringAttribute( - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(10) - ); - const boolMessage = - await tokenAttributes.prepareMessageToPresignBoolAttribute( - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(10) - ); - const bytesMessage = - await tokenAttributes.prepareMessageToPresignBytesAttribute( - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(10) - ); - const addressMessage = - await tokenAttributes.prepareMessageToPresignAddressAttribute( - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(10) - ); - - const uintSignature = await issuer.signMessage( - ethers.utils.arrayify(uintMessage) - ); - const stringSignature = await issuer.signMessage( - ethers.utils.arrayify(stringMessage) - ); - const boolSignature = await issuer.signMessage( - ethers.utils.arrayify(boolMessage) - ); - const bytesSignature = await issuer.signMessage( - ethers.utils.arrayify(bytesMessage) - ); - const addressSignature = await issuer.signMessage( - ethers.utils.arrayify(addressMessage) - ); - - const uintR: string = uintSignature.substring(0, 66); - const uintS: string = "0x" + uintSignature.substring(66, 130); - const uintV: string = parseInt(uintSignature.substring(130, 132), 16); - - const stringR: string = stringSignature.substring(0, 66); - const stringS: string = "0x" + stringSignature.substring(66, 130); - const stringV: string = parseInt(stringSignature.substring(130, 132), 16); - - const boolR: string = boolSignature.substring(0, 66); - const boolS: string = "0x" + boolSignature.substring(66, 130); - const boolV: string = parseInt(boolSignature.substring(130, 132), 16); - - const bytesR: string = bytesSignature.substring(0, 66); - const bytesS: string = "0x" + bytesSignature.substring(66, 130); - const bytesV: string = parseInt(bytesSignature.substring(130, 132), 16); - - const addressR: string = addressSignature.substring(0, 66); - const addressS: string = "0x" + addressSignature.substring(66, 130); - const addressV: string = parseInt( - addressSignature.substring(130, 132), - 16 - ); - - await expect( - tokenAttributes - .connect(owner) - .presignedSetUintAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(10), - uintV, - uintR, - uintS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "ExpiredDeadline"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetStringAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(10), - stringV, - stringR, - stringS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "ExpiredDeadline"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBoolAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(10), - boolV, - boolR, - boolS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "ExpiredDeadline"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBytesAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(10), - bytesV, - bytesR, - bytesS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "ExpiredDeadline"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetAddressAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(10), - addressV, - addressR, - addressS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "ExpiredDeadline"); - }); - - it("should not allow to use presigned message to modify the parameters if the setter does not match the actual signer", async function () { - await tokenAttributes - .connect(issuer) - .registerAccessControl(ownedCollection.address, issuer.address, false); - - const uintMessage = - await tokenAttributes.prepareMessageToPresignUintAttribute( - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(9999999999) - ); - const stringMessage = - await tokenAttributes.prepareMessageToPresignStringAttribute( - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(9999999999) - ); - const boolMessage = - await tokenAttributes.prepareMessageToPresignBoolAttribute( - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(9999999999) - ); - const bytesMessage = - await tokenAttributes.prepareMessageToPresignBytesAttribute( - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(9999999999) - ); - const addressMessage = - await tokenAttributes.prepareMessageToPresignAddressAttribute( - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(9999999999) - ); - - const uintSignature = await owner.signMessage( - ethers.utils.arrayify(uintMessage) - ); - const stringSignature = await owner.signMessage( - ethers.utils.arrayify(stringMessage) - ); - const boolSignature = await owner.signMessage( - ethers.utils.arrayify(boolMessage) - ); - const bytesSignature = await owner.signMessage( - ethers.utils.arrayify(bytesMessage) - ); - const addressSignature = await owner.signMessage( - ethers.utils.arrayify(addressMessage) - ); - - const uintR: string = uintSignature.substring(0, 66); - const uintS: string = "0x" + uintSignature.substring(66, 130); - const uintV: string = parseInt(uintSignature.substring(130, 132), 16); - - const stringR: string = stringSignature.substring(0, 66); - const stringS: string = "0x" + stringSignature.substring(66, 130); - const stringV: string = parseInt(stringSignature.substring(130, 132), 16); - - const boolR: string = boolSignature.substring(0, 66); - const boolS: string = "0x" + boolSignature.substring(66, 130); - const boolV: string = parseInt(boolSignature.substring(130, 132), 16); - - const bytesR: string = bytesSignature.substring(0, 66); - const bytesS: string = "0x" + bytesSignature.substring(66, 130); - const bytesV: string = parseInt(bytesSignature.substring(130, 132), 16); - - const addressR: string = addressSignature.substring(0, 66); - const addressS: string = "0x" + addressSignature.substring(66, 130); - const addressV: string = parseInt( - addressSignature.substring(130, 132), - 16 - ); - - await expect( - tokenAttributes - .connect(owner) - .presignedSetUintAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(9999999999), - uintV, - uintR, - uintS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "InvalidSignature"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetStringAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(9999999999), - stringV, - stringR, - stringS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "InvalidSignature"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBoolAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(9999999999), - boolV, - boolR, - boolS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "InvalidSignature"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBytesAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(9999999999), - bytesV, - bytesR, - bytesS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "InvalidSignature"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetAddressAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(9999999999), - addressV, - addressR, - addressS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "InvalidSignature"); - }); - - it("should not allow to use presigned message to modify the parameters if the signer is not authorized to modify them", async function () { - const uintMessage = - await tokenAttributes.prepareMessageToPresignUintAttribute( - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(9999999999) - ); - const stringMessage = - await tokenAttributes.prepareMessageToPresignStringAttribute( - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(9999999999) - ); - const boolMessage = - await tokenAttributes.prepareMessageToPresignBoolAttribute( - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(9999999999) - ); - const bytesMessage = - await tokenAttributes.prepareMessageToPresignBytesAttribute( - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(9999999999) - ); - const addressMessage = - await tokenAttributes.prepareMessageToPresignAddressAttribute( - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(9999999999) - ); - - const uintSignature = await issuer.signMessage( - ethers.utils.arrayify(uintMessage) - ); - const stringSignature = await issuer.signMessage( - ethers.utils.arrayify(stringMessage) - ); - const boolSignature = await issuer.signMessage( - ethers.utils.arrayify(boolMessage) - ); - const bytesSignature = await issuer.signMessage( - ethers.utils.arrayify(bytesMessage) - ); - const addressSignature = await issuer.signMessage( - ethers.utils.arrayify(addressMessage) - ); - - const uintR: string = uintSignature.substring(0, 66); - const uintS: string = "0x" + uintSignature.substring(66, 130); - const uintV: string = parseInt(uintSignature.substring(130, 132), 16); - - const stringR: string = stringSignature.substring(0, 66); - const stringS: string = "0x" + stringSignature.substring(66, 130); - const stringV: string = parseInt(stringSignature.substring(130, 132), 16); - - const boolR: string = boolSignature.substring(0, 66); - const boolS: string = "0x" + boolSignature.substring(66, 130); - const boolV: string = parseInt(boolSignature.substring(130, 132), 16); - - const bytesR: string = bytesSignature.substring(0, 66); - const bytesS: string = "0x" + bytesSignature.substring(66, 130); - const bytesV: string = parseInt(bytesSignature.substring(130, 132), 16); - - const addressR: string = addressSignature.substring(0, 66); - const addressS: string = "0x" + addressSignature.substring(66, 130); - const addressV: string = parseInt( - addressSignature.substring(130, 132), - 16 - ); - - await expect( - tokenAttributes - .connect(owner) - .presignedSetUintAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - 1, - BigNumber.from(9999999999), - uintV, - uintR, - uintS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetStringAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "test", - BigNumber.from(9999999999), - stringV, - stringR, - stringS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBoolAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - true, - BigNumber.from(9999999999), - boolV, - boolR, - boolS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetBytesAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - "0x1234", - BigNumber.from(9999999999), - bytesV, - bytesR, - bytesS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - await expect( - tokenAttributes - .connect(owner) - .presignedSetAddressAttribute( - issuer.address, - ownedCollection.address, - 1, - "X", - owner.address, - BigNumber.from(9999999999), - addressV, - addressR, - addressS - ) - ).to.be.revertedWithCustomError(tokenAttributes, "NotCollectionIssuer"); - }); - }); -}); - -async function shouldBehaveLikeTokenAttributesRepositoryInterface() { - it("can support IERC165", async function () { - expect(await this.tokenAttributes.supportsInterface(IERC165)).to.equal( - true - ); - }); - - it("can support IAttributesRepository", async function () { - expect( - await this.tokenAttributes.supportsInterface(IAttributesRepository) - ).to.equal(true); - }); -} diff --git a/assets/eip-7512/example_use_case.png b/assets/eip-7512/example_use_case.png deleted file mode 100644 index 6de76cce714d67..00000000000000 Binary files a/assets/eip-7512/example_use_case.png and /dev/null differ diff --git a/assets/eip-7522/workflow.png b/assets/eip-7522/workflow.png deleted file mode 100644 index 1dd242eac757bb..00000000000000 Binary files a/assets/eip-7522/workflow.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-1024px.png deleted file mode 100644 index e9c0e6ecec1dbd..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-192px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-192px.png deleted file mode 100644 index e3cb995daeca32..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-2048px.png deleted file mode 100644 index 1dd2e5c84a453d..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-48px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-48px.png deleted file mode 100644 index 87b0130cf3f360..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-beige-600px.png b/assets/eip-777/logo/png/ERC-777-logo-beige-600px.png deleted file mode 100644 index aeb5e2eb0ce02d..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-beige-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-black-1024px.png deleted file mode 100644 index e6392144de10af..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-192px.png b/assets/eip-777/logo/png/ERC-777-logo-black-192px.png deleted file mode 100644 index 4130b57b8e4bc1..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-black-2048px.png deleted file mode 100644 index 2485d96505624b..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-48px.png b/assets/eip-777/logo/png/ERC-777-logo-black-48px.png deleted file mode 100644 index 1e76fda2f31858..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-black-600px.png b/assets/eip-777/logo/png/ERC-777-logo-black-600px.png deleted file mode 100644 index 07dc6e6db76ab2..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-black-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-1024px.png deleted file mode 100644 index 961eb373d3a5ca..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-192px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-192px.png deleted file mode 100644 index 1f56130b55cccf..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-2048px.png deleted file mode 100644 index 016df2254d7b1b..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png deleted file mode 100644 index 6bfe87c0927d7e..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-600px.png b/assets/eip-777/logo/png/ERC-777-logo-dark_grey-600px.png deleted file mode 100644 index 70afecf7b512a0..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-dark_grey-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-1024px.png deleted file mode 100644 index c678ace88101a9..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-192px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-192px.png deleted file mode 100644 index e7cd059ef9804b..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-2048px.png deleted file mode 100644 index ccb78fc694a778..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png deleted file mode 100644 index 870e21d8624826..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-light_grey-600px.png b/assets/eip-777/logo/png/ERC-777-logo-light_grey-600px.png deleted file mode 100644 index 074d0a05222962..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-light_grey-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-1024px.png b/assets/eip-777/logo/png/ERC-777-logo-white-1024px.png deleted file mode 100644 index b2bdcbdba7825b..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-1024px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-192px.png b/assets/eip-777/logo/png/ERC-777-logo-white-192px.png deleted file mode 100644 index f253d5c9a22404..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-192px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-2048px.png b/assets/eip-777/logo/png/ERC-777-logo-white-2048px.png deleted file mode 100644 index be68567096d1ae..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-2048px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-48px.png b/assets/eip-777/logo/png/ERC-777-logo-white-48px.png deleted file mode 100644 index 32c34340e1d118..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-48px.png and /dev/null differ diff --git a/assets/eip-777/logo/png/ERC-777-logo-white-600px.png b/assets/eip-777/logo/png/ERC-777-logo-white-600px.png deleted file mode 100644 index b6157f503baf7c..00000000000000 Binary files a/assets/eip-777/logo/png/ERC-777-logo-white-600px.png and /dev/null differ diff --git a/assets/eip-777/logo/svg/ERC-777-logo-beige.svg b/assets/eip-777/logo/svg/ERC-777-logo-beige.svg deleted file mode 100644 index 6af82af0d40dc1..00000000000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-beige.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-black.svg b/assets/eip-777/logo/svg/ERC-777-logo-black.svg deleted file mode 100644 index 53869e23eef1f3..00000000000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-black.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-dark_grey.svg b/assets/eip-777/logo/svg/ERC-777-logo-dark_grey.svg deleted file mode 100644 index 9821736b52596f..00000000000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-dark_grey.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-light_grey.svg b/assets/eip-777/logo/svg/ERC-777-logo-light_grey.svg deleted file mode 100644 index 1e29b8631a3ecd..00000000000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-light_grey.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-777/logo/svg/ERC-777-logo-white.svg b/assets/eip-777/logo/svg/ERC-777-logo-white.svg deleted file mode 100644 index fae203bddd8b57..00000000000000 --- a/assets/eip-777/logo/svg/ERC-777-logo-white.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - diff --git a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-1.png b/assets/eip-823/eip-823-token-exchange-standard-visual-representation-1.png deleted file mode 100644 index 97fe0c176d17a5..00000000000000 Binary files a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-1.png and /dev/null differ diff --git a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-2.png b/assets/eip-823/eip-823-token-exchange-standard-visual-representation-2.png deleted file mode 100644 index bfaa387e5d5007..00000000000000 Binary files a/assets/eip-823/eip-823-token-exchange-standard-visual-representation-2.png and /dev/null differ